diff --git a/exec.c b/exec.c index c6490ff074..6010bc70c1 100644 --- a/exec.c +++ b/exec.c @@ -1457,9 +1457,13 @@ void cpu_single_step(CPUState *env, int enabled) #if defined(TARGET_HAS_ICE) if (env->singlestep_enabled != enabled) { env->singlestep_enabled = enabled; - /* must flush all the translated code to avoid inconsistancies */ - /* XXX: only flush what is necessary */ - tb_flush(env); + if (kvm_enabled()) + kvm_update_guest_debug(env, 0); + else { + /* must flush all the translated code to avoid inconsistancies */ + /* XXX: only flush what is necessary */ + tb_flush(env); + } } #endif } diff --git a/gdbstub.c b/gdbstub.c index addff2ee7a..8876c1d9a0 100644 --- a/gdbstub.c +++ b/gdbstub.c @@ -39,6 +39,7 @@ #define MAX_PACKET_LENGTH 4096 #include "qemu_socket.h" +#include "kvm.h" enum { @@ -1418,13 +1419,6 @@ void gdb_register_coprocessor(CPUState * env, } } -/* GDB breakpoint/watchpoint types */ -#define GDB_BREAKPOINT_SW 0 -#define GDB_BREAKPOINT_HW 1 -#define GDB_WATCHPOINT_WRITE 2 -#define GDB_WATCHPOINT_READ 3 -#define GDB_WATCHPOINT_ACCESS 4 - #ifndef CONFIG_USER_ONLY static const int xlat_gdb_type[] = { [GDB_WATCHPOINT_WRITE] = BP_GDB | BP_MEM_WRITE, @@ -1438,6 +1432,9 @@ static int gdb_breakpoint_insert(target_ulong addr, target_ulong len, int type) CPUState *env; int err = 0; + if (kvm_enabled()) + return kvm_insert_breakpoint(gdbserver_state->c_cpu, addr, len, type); + switch (type) { case GDB_BREAKPOINT_SW: case GDB_BREAKPOINT_HW: @@ -1469,6 +1466,9 @@ static int gdb_breakpoint_remove(target_ulong addr, target_ulong len, int type) CPUState *env; int err = 0; + if (kvm_enabled()) + return kvm_remove_breakpoint(gdbserver_state->c_cpu, addr, len, type); + switch (type) { case GDB_BREAKPOINT_SW: case GDB_BREAKPOINT_HW: @@ -1498,6 +1498,11 @@ static void gdb_breakpoint_remove_all(void) { CPUState *env; + if (kvm_enabled()) { + kvm_remove_all_breakpoints(gdbserver_state->c_cpu); + return; + } + for (env = first_cpu; env != NULL; env = env->next_cpu) { cpu_breakpoint_remove_all(env, BP_GDB); #ifndef CONFIG_USER_ONLY @@ -1538,6 +1543,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) addr = strtoull(p, (char **)&p, 16); #if defined(TARGET_I386) s->c_cpu->eip = addr; + cpu_synchronize_state(s->c_cpu, 1); #elif defined (TARGET_PPC) s->c_cpu->nip = addr; #elif defined (TARGET_SPARC) @@ -1579,6 +1585,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) addr = strtoull(p, (char **)&p, 16); #if defined(TARGET_I386) s->c_cpu->eip = addr; + cpu_synchronize_state(s->c_cpu, 1); #elif defined (TARGET_PPC) s->c_cpu->nip = addr; #elif defined (TARGET_SPARC) @@ -1624,6 +1631,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) } break; case 'g': + cpu_synchronize_state(s->g_cpu, 0); len = 0; for (addr = 0; addr < num_g_regs; addr++) { reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr); @@ -1641,6 +1649,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) len -= reg_size; registers += reg_size; } + cpu_synchronize_state(s->g_cpu, 1); put_packet(s, "OK"); break; case 'm': @@ -1799,6 +1808,7 @@ static int gdb_handle_packet(GDBState *s, const char *line_buf) thread = strtoull(p+16, (char **)&p, 16); for (env = first_cpu; env != NULL; env = env->next_cpu) if (env->cpu_index + 1 == thread) { + cpu_synchronize_state(env, 0); len = snprintf((char *)mem_buf, sizeof(mem_buf), "CPU#%d [%s]", env->cpu_index, env->halted ? "halted " : "running"); diff --git a/gdbstub.h b/gdbstub.h index c7d1c4b43f..5740041c76 100644 --- a/gdbstub.h +++ b/gdbstub.h @@ -3,6 +3,13 @@ #define DEFAULT_GDBSTUB_PORT "1234" +/* GDB breakpoint/watchpoint types */ +#define GDB_BREAKPOINT_SW 0 +#define GDB_BREAKPOINT_HW 1 +#define GDB_WATCHPOINT_WRITE 2 +#define GDB_WATCHPOINT_READ 3 +#define GDB_WATCHPOINT_ACCESS 4 + typedef void (*gdb_syscall_complete_cb)(CPUState *env, target_ulong ret, target_ulong err); diff --git a/kvm-all.c b/kvm-all.c index 28c9c07ccb..3cfc7b5d8b 100644 --- a/kvm-all.c +++ b/kvm-all.c @@ -22,6 +22,7 @@ #include "qemu-common.h" #include "sysemu.h" +#include "gdbstub.h" #include "kvm.h" /* KVM uses PAGE_SIZE in it's definition of COALESCED_MMIO_MAX */ @@ -56,6 +57,9 @@ struct KVMState int fd; int vmfd; int coalesced_mmio; +#ifdef KVM_CAP_SET_GUEST_DEBUG + struct kvm_sw_breakpoint_head kvm_sw_breakpoints; +#endif }; static KVMState *kvm_state; @@ -291,6 +295,9 @@ int kvm_init(int smp_cpus) s = qemu_mallocz(sizeof(KVMState)); +#ifdef KVM_CAP_SET_GUEST_DEBUG + TAILQ_INIT(&s->kvm_sw_breakpoints); +#endif for (i = 0; i < ARRAY_SIZE(s->slots); i++) s->slots[i].slot = i; @@ -504,6 +511,16 @@ int kvm_cpu_exec(CPUState *env) break; case KVM_EXIT_DEBUG: dprintf("kvm_exit_debug\n"); +#ifdef KVM_CAP_SET_GUEST_DEBUG + if (kvm_arch_debug(&run->debug.arch)) { + gdb_set_stop_cpu(env); + vm_stop(EXCP_DEBUG); + env->exception_index = EXCP_DEBUG; + return 0; + } + /* re-enter, this exception was guest-internal */ + ret = 1; +#endif /* KVM_CAP_SET_GUEST_DEBUG */ break; default: dprintf("kvm_arch_handle_exit\n"); @@ -656,3 +673,159 @@ int kvm_has_sync_mmu(void) return 0; } + +#ifdef KVM_CAP_SET_GUEST_DEBUG +struct kvm_sw_breakpoint *kvm_find_sw_breakpoint(CPUState *env, + target_ulong pc) +{ + struct kvm_sw_breakpoint *bp; + + TAILQ_FOREACH(bp, &env->kvm_state->kvm_sw_breakpoints, entry) { + if (bp->pc == pc) + return bp; + } + return NULL; +} + +int kvm_sw_breakpoints_active(CPUState *env) +{ + return !TAILQ_EMPTY(&env->kvm_state->kvm_sw_breakpoints); +} + +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap) +{ + struct kvm_guest_debug dbg; + + dbg.control = 0; + if (env->singlestep_enabled) + dbg.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP; + + kvm_arch_update_guest_debug(env, &dbg); + dbg.control |= reinject_trap; + + return kvm_vcpu_ioctl(env, KVM_SET_GUEST_DEBUG, &dbg); +} + +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + struct kvm_sw_breakpoint *bp; + CPUState *env; + int err; + + if (type == GDB_BREAKPOINT_SW) { + bp = kvm_find_sw_breakpoint(current_env, addr); + if (bp) { + bp->use_count++; + return 0; + } + + bp = qemu_malloc(sizeof(struct kvm_sw_breakpoint)); + if (!bp) + return -ENOMEM; + + bp->pc = addr; + bp->use_count = 1; + err = kvm_arch_insert_sw_breakpoint(current_env, bp); + if (err) { + free(bp); + return err; + } + + TAILQ_INSERT_HEAD(¤t_env->kvm_state->kvm_sw_breakpoints, + bp, entry); + } else { + err = kvm_arch_insert_hw_breakpoint(addr, len, type); + if (err) + return err; + } + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = kvm_update_guest_debug(env, 0); + if (err) + return err; + } + return 0; +} + +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + struct kvm_sw_breakpoint *bp; + CPUState *env; + int err; + + if (type == GDB_BREAKPOINT_SW) { + bp = kvm_find_sw_breakpoint(current_env, addr); + if (!bp) + return -ENOENT; + + if (bp->use_count > 1) { + bp->use_count--; + return 0; + } + + err = kvm_arch_remove_sw_breakpoint(current_env, bp); + if (err) + return err; + + TAILQ_REMOVE(¤t_env->kvm_state->kvm_sw_breakpoints, bp, entry); + qemu_free(bp); + } else { + err = kvm_arch_remove_hw_breakpoint(addr, len, type); + if (err) + return err; + } + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = kvm_update_guest_debug(env, 0); + if (err) + return err; + } + return 0; +} + +void kvm_remove_all_breakpoints(CPUState *current_env) +{ + struct kvm_sw_breakpoint *bp, *next; + KVMState *s = current_env->kvm_state; + CPUState *env; + + TAILQ_FOREACH_SAFE(bp, &s->kvm_sw_breakpoints, entry, next) { + if (kvm_arch_remove_sw_breakpoint(current_env, bp) != 0) { + /* Try harder to find a CPU that currently sees the breakpoint. */ + for (env = first_cpu; env != NULL; env = env->next_cpu) { + if (kvm_arch_remove_sw_breakpoint(env, bp) == 0) + break; + } + } + } + kvm_arch_remove_all_hw_breakpoints(); + + for (env = first_cpu; env != NULL; env = env->next_cpu) + kvm_update_guest_debug(env, 0); +} + +#else /* !KVM_CAP_SET_GUEST_DEBUG */ + +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap) +{ + return -EINVAL; +} + +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + return -EINVAL; +} + +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + return -EINVAL; +} + +void kvm_remove_all_breakpoints(CPUState *current_env) +{ +} +#endif /* !KVM_CAP_SET_GUEST_DEBUG */ diff --git a/kvm.h b/kvm.h index efce1450d5..0d6bf7e4cb 100644 --- a/kvm.h +++ b/kvm.h @@ -15,6 +15,7 @@ #define QEMU_KVM_H #include "config.h" +#include "sys-queue.h" #ifdef CONFIG_KVM extern int kvm_allowed; @@ -49,6 +50,13 @@ int kvm_has_sync_mmu(void); int kvm_coalesce_mmio_region(target_phys_addr_t start, ram_addr_t size); int kvm_uncoalesce_mmio_region(target_phys_addr_t start, ram_addr_t size); +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type); +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type); +void kvm_remove_all_breakpoints(CPUState *current_env); +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap); + /* internal API */ struct KVMState; @@ -76,4 +84,47 @@ int kvm_arch_init(KVMState *s, int smp_cpus); int kvm_arch_init_vcpu(CPUState *env); +struct kvm_guest_debug; +struct kvm_debug_exit_arch; + +struct kvm_sw_breakpoint { + target_ulong pc; + target_ulong saved_insn; + int use_count; + TAILQ_ENTRY(kvm_sw_breakpoint) entry; +}; + +TAILQ_HEAD(kvm_sw_breakpoint_head, kvm_sw_breakpoint); + +int kvm_arch_debug(struct kvm_debug_exit_arch *arch_info); + +struct kvm_sw_breakpoint *kvm_find_sw_breakpoint(CPUState *env, + target_ulong pc); + +int kvm_sw_breakpoints_active(CPUState *env); + +int kvm_arch_insert_sw_breakpoint(CPUState *current_env, + struct kvm_sw_breakpoint *bp); +int kvm_arch_remove_sw_breakpoint(CPUState *current_env, + struct kvm_sw_breakpoint *bp); +int kvm_arch_insert_hw_breakpoint(target_ulong addr, + target_ulong len, int type); +int kvm_arch_remove_hw_breakpoint(target_ulong addr, + target_ulong len, int type); +void kvm_arch_remove_all_hw_breakpoints(void); + +void kvm_arch_update_guest_debug(CPUState *env, struct kvm_guest_debug *dbg); + +/* generic hooks - to be moved/refactored once there are more users */ + +static inline void cpu_synchronize_state(CPUState *env, int modified) +{ + if (kvm_enabled()) { + if (modified) + kvm_arch_put_registers(env); + else + kvm_arch_get_registers(env); + } +} + #endif diff --git a/target-i386/kvm.c b/target-i386/kvm.c index 86745b1350..5168a02ca7 100644 --- a/target-i386/kvm.c +++ b/target-i386/kvm.c @@ -22,6 +22,7 @@ #include "sysemu.h" #include "kvm.h" #include "cpu.h" +#include "gdbstub.h" //#define DEBUG_KVM @@ -683,3 +684,193 @@ int kvm_arch_handle_exit(CPUState *env, struct kvm_run *run) return ret; } + +#ifdef KVM_CAP_SET_GUEST_DEBUG +static int kvm_patch_opcode_byte(CPUState *env, target_ulong addr, uint8_t val) +{ + target_phys_addr_t phys_page_addr; + unsigned long pd; + uint8_t *ptr; + + phys_page_addr = cpu_get_phys_page_debug(env, addr & TARGET_PAGE_MASK); + if (phys_page_addr == -1) + return -EINVAL; + + pd = cpu_get_physical_page_desc(phys_page_addr); + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM && + (pd & ~TARGET_PAGE_MASK) != IO_MEM_ROM && !(pd & IO_MEM_ROMD)) + return -EINVAL; + + ptr = phys_ram_base + (pd & TARGET_PAGE_MASK) + + (addr & ~TARGET_PAGE_MASK); + *ptr = val; + return 0; +} + +int kvm_arch_insert_sw_breakpoint(CPUState *env, struct kvm_sw_breakpoint *bp) +{ + if (cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&bp->saved_insn, 1, 0) || + kvm_patch_opcode_byte(env, bp->pc, 0xcc)) + return -EINVAL; + return 0; +} + +int kvm_arch_remove_sw_breakpoint(CPUState *env, struct kvm_sw_breakpoint *bp) +{ + uint8_t int3; + + if (cpu_memory_rw_debug(env, bp->pc, &int3, 1, 0) || int3 != 0xcc || + kvm_patch_opcode_byte(env, bp->pc, bp->saved_insn)) + return -EINVAL; + return 0; +} + +static struct { + target_ulong addr; + int len; + int type; +} hw_breakpoint[4]; + +static int nb_hw_breakpoint; + +static int find_hw_breakpoint(target_ulong addr, int len, int type) +{ + int n; + + for (n = 0; n < nb_hw_breakpoint; n++) + if (hw_breakpoint[n].addr == addr && hw_breakpoint[n].type == type && + (hw_breakpoint[n].len == len || len == -1)) + return n; + return -1; +} + +int kvm_arch_insert_hw_breakpoint(target_ulong addr, + target_ulong len, int type) +{ + switch (type) { + case GDB_BREAKPOINT_HW: + len = 1; + break; + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_ACCESS: + switch (len) { + case 1: + break; + case 2: + case 4: + case 8: + if (addr & (len - 1)) + return -EINVAL; + break; + default: + return -EINVAL; + } + break; + default: + return -ENOSYS; + } + + if (nb_hw_breakpoint == 4) + return -ENOBUFS; + + if (find_hw_breakpoint(addr, len, type) >= 0) + return -EEXIST; + + hw_breakpoint[nb_hw_breakpoint].addr = addr; + hw_breakpoint[nb_hw_breakpoint].len = len; + hw_breakpoint[nb_hw_breakpoint].type = type; + nb_hw_breakpoint++; + + return 0; +} + +int kvm_arch_remove_hw_breakpoint(target_ulong addr, + target_ulong len, int type) +{ + int n; + + n = find_hw_breakpoint(addr, (type == GDB_BREAKPOINT_HW) ? 1 : len, type); + if (n < 0) + return -ENOENT; + + nb_hw_breakpoint--; + hw_breakpoint[n] = hw_breakpoint[nb_hw_breakpoint]; + + return 0; +} + +void kvm_arch_remove_all_hw_breakpoints(void) +{ + nb_hw_breakpoint = 0; +} + +static CPUWatchpoint hw_watchpoint; + +int kvm_arch_debug(struct kvm_debug_exit_arch *arch_info) +{ + int handle = 0; + int n; + + if (arch_info->exception == 1) { + if (arch_info->dr6 & (1 << 14)) { + if (cpu_single_env->singlestep_enabled) + handle = 1; + } else { + for (n = 0; n < 4; n++) + if (arch_info->dr6 & (1 << n)) + switch ((arch_info->dr7 >> (16 + n*4)) & 0x3) { + case 0x0: + handle = 1; + break; + case 0x1: + handle = 1; + cpu_single_env->watchpoint_hit = &hw_watchpoint; + hw_watchpoint.vaddr = hw_breakpoint[n].addr; + hw_watchpoint.flags = BP_MEM_WRITE; + break; + case 0x3: + handle = 1; + cpu_single_env->watchpoint_hit = &hw_watchpoint; + hw_watchpoint.vaddr = hw_breakpoint[n].addr; + hw_watchpoint.flags = BP_MEM_ACCESS; + break; + } + } + } else if (kvm_find_sw_breakpoint(cpu_single_env, arch_info->pc)) + handle = 1; + + if (!handle) + kvm_update_guest_debug(cpu_single_env, + (arch_info->exception == 1) ? + KVM_GUESTDBG_INJECT_DB : KVM_GUESTDBG_INJECT_BP); + + return handle; +} + +void kvm_arch_update_guest_debug(CPUState *env, struct kvm_guest_debug *dbg) +{ + const uint8_t type_code[] = { + [GDB_BREAKPOINT_HW] = 0x0, + [GDB_WATCHPOINT_WRITE] = 0x1, + [GDB_WATCHPOINT_ACCESS] = 0x3 + }; + const uint8_t len_code[] = { + [1] = 0x0, [2] = 0x1, [4] = 0x3, [8] = 0x2 + }; + int n; + + if (kvm_sw_breakpoints_active(env)) + dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP; + + if (nb_hw_breakpoint > 0) { + dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; + dbg->arch.debugreg[7] = 0x0600; + for (n = 0; n < nb_hw_breakpoint; n++) { + dbg->arch.debugreg[n] = hw_breakpoint[n].addr; + dbg->arch.debugreg[7] |= (2 << (n * 2)) | + (type_code[hw_breakpoint[n].type] << (16 + n*4)) | + (len_code[hw_breakpoint[n].len] << (18 + n*4)); + } + } +} +#endif /* KVM_CAP_SET_GUEST_DEBUG */