This patch add support for guest debugging with qemu gdbstub. Now hardware breakpoint and watchpoint can be set/unset. This patch also support for software beakpoint. Signed-off-by: Bharat Bhushan <bharat.bhushan@xxxxxxxxxxxxx> --- hw/ppc/e500.c | 3 + target-ppc/kvm.c | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++ target-ppc/kvm_ppc.h | 1 + 3 files changed, 246 insertions(+), 0 deletions(-) diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c index f07be08..83a2972 100644 --- a/hw/ppc/e500.c +++ b/hw/ppc/e500.c @@ -588,5 +588,8 @@ void ppce500_init(PPCE500Params *params) if (kvm_enabled()) { kvmppc_init(); +#ifdef KVM_CAP_SET_GUEST_DEBUG + kvmppc_e500_hw_breakpoint_init(); +#endif } } diff --git a/target-ppc/kvm.c b/target-ppc/kvm.c index f70e7a6..f980700 100644 --- a/target-ppc/kvm.c +++ b/target-ppc/kvm.c @@ -33,6 +33,7 @@ #include "hw/sysbus.h" #include "hw/spapr.h" #include "hw/watchdog.h" +#include "gdbstub.h" #include "hw/sysbus.h" #include "hw/spapr.h" @@ -805,6 +806,236 @@ static int kvmppc_handle_dcr_write(CPUPPCState *env, uint32_t dcrn, uint32_t dat return 0; } +#ifdef KVM_CAP_SET_GUEST_DEBUG +int kvm_arch_insert_sw_breakpoint(CPUPPCState *env, + struct kvm_sw_breakpoint *bp) +{ + uint32_t sc = tswap32(KVM_INST_GUESTGDB); + + if (cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&bp->saved_insn, 4, 0) || + cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&sc, 4, 1)) { + return -EINVAL; + } + + return 0; +} + +int kvm_arch_remove_sw_breakpoint(CPUPPCState *env, + struct kvm_sw_breakpoint *bp) +{ + uint32_t sc; + + if (cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&sc, 4, 0) || + sc != tswap32(KVM_INST_GUESTGDB) || + cpu_memory_rw_debug(env, bp->pc, (uint8_t *)&bp->saved_insn, 4, 1)) { + return -EINVAL; + } + + return 0; +} + +static struct HWBreakpoint { + target_ulong addr; + int type; +} hw_breakpoint[6]; + +static int nb_hw_breakpoint; +static int nb_hw_watchpoint; +static int max_hw_breakpoint = 4; +static int max_hw_watchpoint = 2; + +void kvmppc_e500_hw_breakpoint_init(void) +{ + max_hw_breakpoint = 2; + max_hw_watchpoint = 2; +} + +static int find_hw_breakpoint(target_ulong addr, int type) +{ + int n; + + for (n = 0; n < nb_hw_breakpoint + nb_hw_watchpoint; n++) { + if (hw_breakpoint[n].addr == addr && hw_breakpoint[n].type == type) { + return n; + } + } + + return -1; +} + +static int find_hw_watchpoint(target_ulong addr, int *flag) +{ + int n; + + n = find_hw_breakpoint(addr, GDB_WATCHPOINT_ACCESS); + if (n >= 0) { + *flag = BP_MEM_ACCESS; + return n; + } + + n = find_hw_breakpoint(addr, KVMPPC_DEBUG_WATCH_WRITE); + if (n >= 0) { + *flag = BP_MEM_WRITE; + return n; + } + + n = find_hw_breakpoint(addr, KVMPPC_DEBUG_WATCH_READ); + if (n >= 0) { + *flag = BP_MEM_READ; + return n; + } + + return -1; +} + +int kvm_arch_insert_hw_breakpoint(target_ulong addr, + target_ulong len, int type) +{ + hw_breakpoint[nb_hw_breakpoint + nb_hw_watchpoint].addr = addr; + hw_breakpoint[nb_hw_breakpoint + nb_hw_watchpoint].type = type; + + switch (type) { + case GDB_BREAKPOINT_HW: + if (nb_hw_breakpoint >= max_hw_breakpoint) { + return -ENOBUFS; + } + + if (find_hw_breakpoint(addr, type) >= 0) { + return -EEXIST; + } + + nb_hw_breakpoint++; + break; + + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_ACCESS: + if (nb_hw_watchpoint >= max_hw_watchpoint) { + return -ENOBUFS; + } + + if (find_hw_breakpoint(addr, type) >= 0) { + return -EEXIST; + } + + nb_hw_watchpoint++; + break; + + default: + return -ENOSYS; + } + + return 0; +} + +int kvm_arch_remove_hw_breakpoint(target_ulong addr, + target_ulong len, int type) +{ + int n; + + n = find_hw_breakpoint(addr, type); + if (n < 0) { + return -ENOENT; + } + + switch (type) { + case GDB_BREAKPOINT_HW: + nb_hw_breakpoint--; + break; + + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_ACCESS: + nb_hw_watchpoint--; + break; + + default: + return -ENOSYS; + } + hw_breakpoint[n] = hw_breakpoint[nb_hw_breakpoint + nb_hw_watchpoint]; + + return 0; +} + +void kvm_arch_remove_all_hw_breakpoints(void) +{ + nb_hw_breakpoint = nb_hw_watchpoint = 0; +} + +static CPUWatchpoint hw_watchpoint; + + +static int kvm_handle_debug(struct kvm_debug_exit_arch *arch_info) +{ + int handle = 0; + int n; + int flag = 0; + + if (cpu_single_env->singlestep_enabled) { + handle = 1; + } else if (arch_info->status) { + if (arch_info->status & KVMPPC_DEBUG_BREAKPOINT) { + n = find_hw_breakpoint(arch_info->pc, GDB_BREAKPOINT_HW); + if (n >= 0) { + handle = 1; + } + } else if (arch_info->status & (KVMPPC_DEBUG_WATCH_READ | + KVMPPC_DEBUG_WATCH_WRITE)) { + n = find_hw_watchpoint(arch_info->pc, &flag); + if (n >= 0) { + handle = 1; + cpu_single_env->watchpoint_hit = &hw_watchpoint; + hw_watchpoint.vaddr = hw_breakpoint[n].addr; + hw_watchpoint.flags = flag; + } + } + } else if (kvm_find_sw_breakpoint(cpu_single_env, arch_info->pc)) { + handle = 1; + } + + /* XXX inject guest debug exception */ + if (!handle) { + fprintf(stderr, "Unhandled debug exception!\n"); + } + + return handle; +} + +void kvm_arch_update_guest_debug(CPUPPCState *env, struct kvm_guest_debug *dbg) +{ + if (kvm_sw_breakpoints_active(env)) { + dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP; + } + + if (nb_hw_breakpoint + nb_hw_watchpoint > 0) { + int n; + + dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; + memset(dbg->arch.bp, 0, sizeof(dbg->arch.bp)); + for (n = 0; n < nb_hw_breakpoint + nb_hw_watchpoint; n++) { + switch (hw_breakpoint[n].type) { + case GDB_BREAKPOINT_HW: + dbg->arch.bp[n].type = KVMPPC_DEBUG_BREAKPOINT; + break; + case GDB_WATCHPOINT_WRITE: + dbg->arch.bp[n].type = KVMPPC_DEBUG_WATCH_WRITE; + break; + case GDB_WATCHPOINT_READ: + dbg->arch.bp[n].type = KVMPPC_DEBUG_WATCH_READ; + break; + case GDB_WATCHPOINT_ACCESS: + dbg->arch.bp[n].type = KVMPPC_DEBUG_WATCH_WRITE | + KVMPPC_DEBUG_WATCH_READ; + break; + default: + cpu_abort(env, "Unsupported breakpoint type\n"); + } + dbg->arch.bp[n].addr = hw_breakpoint[n].addr; + } + } +} +#endif /* KVM_CAP_SET_GUEST_DEBUG */ + int kvm_arch_handle_exit(CPUPPCState *env, struct kvm_run *run) { int ret; @@ -838,6 +1069,17 @@ int kvm_arch_handle_exit(CPUPPCState *env, struct kvm_run *run) ret = 0; break; #endif +#ifdef KVM_CAP_SET_GUEST_DEBUG + case KVM_EXIT_DEBUG: + dprintf("kvm_exit_debug\n"); + if (kvm_handle_debug(&run->debug.arch)) { + ret = EXCP_DEBUG; + break; + } + /* re-enter, this exception was guest-internal */ + ret = 0; + break; +#endif default: fprintf(stderr, "KVM: unknown exit reason %d\n", run->exit_reason); ret = -1; diff --git a/target-ppc/kvm_ppc.h b/target-ppc/kvm_ppc.h index e2f8703..6e34cf3 100644 --- a/target-ppc/kvm_ppc.h +++ b/target-ppc/kvm_ppc.h @@ -30,6 +30,7 @@ int kvmppc_remove_spapr_tce(void *table, int pfd, uint32_t window_size); #endif /* !CONFIG_USER_ONLY */ const ppc_def_t *kvmppc_host_cpu_def(void); int kvmppc_fixup_cpu(CPUPPCState *env); +void kvmppc_e500_hw_breakpoint_init(void); #else -- 1.7.0.4 -- To unsubscribe from this list: send the line "unsubscribe kvm-ppc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html