From 770a63792b2c9a3f9565b68b0a7ef0be6883f551 Mon Sep 17 00:00:00 2001 From: David Hildenbrand Date: Mon, 3 Sep 2012 13:09:10 +0200 Subject: [PATCH] s390x/kvm: hw debugging support via guest PER facility This patch makes use of the hw debugging support in kvm (provided by the guest's PER facility) on s390. It enables the following features, available using the gdbserver: - single-stepping - hw breakpoints - hw watchpoints Signed-off-by: David Hildenbrand Signed-off-by: Jens Freimann Signed-off-by: Cornelia Huck --- target-s390x/cpu-qom.h | 1 + target-s390x/helper.c | 12 ++++ target-s390x/kvm.c | 146 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/target-s390x/cpu-qom.h b/target-s390x/cpu-qom.h index ac0460eb30..f9c96d13a9 100644 --- a/target-s390x/cpu-qom.h +++ b/target-s390x/cpu-qom.h @@ -86,6 +86,7 @@ int s390_cpu_write_elf64_note(WriteCoreDumpFunction f, CPUState *cs, int s390_cpu_write_elf64_qemunote(WriteCoreDumpFunction f, CPUState *cpu, void *opaque); hwaddr s390_cpu_get_phys_page_debug(CPUState *cpu, vaddr addr); +hwaddr s390_cpu_get_phys_addr_debug(CPUState *cpu, vaddr addr); int s390_cpu_gdb_read_register(CPUState *cpu, uint8_t *buf, int reg); int s390_cpu_gdb_write_register(CPUState *cpu, uint8_t *buf, int reg); diff --git a/target-s390x/helper.c b/target-s390x/helper.c index 7c76fc149b..3d756cae6c 100644 --- a/target-s390x/helper.c +++ b/target-s390x/helper.c @@ -489,6 +489,18 @@ hwaddr s390_cpu_get_phys_page_debug(CPUState *cs, vaddr vaddr) return raddr; } +hwaddr s390_cpu_get_phys_addr_debug(CPUState *cs, vaddr vaddr) +{ + hwaddr phys_addr; + target_ulong page; + + page = vaddr & TARGET_PAGE_MASK; + phys_addr = cpu_get_phys_page_debug(cs, page); + phys_addr += (vaddr & ~TARGET_PAGE_MASK); + + return phys_addr; +} + void load_psw(CPUS390XState *env, uint64_t mask, uint64_t addr) { if (mask & PSW_MASK_WAIT) { diff --git a/target-s390x/kvm.c b/target-s390x/kvm.c index 6d47637d78..7a07f9d753 100644 --- a/target-s390x/kvm.c +++ b/target-s390x/kvm.c @@ -36,6 +36,7 @@ #include "sysemu/device_tree.h" #include "qapi/qmp/qjson.h" #include "monitor/monitor.h" +#include "exec/gdbstub.h" #include "trace.h" /* #define DEBUG_KVM */ @@ -86,6 +87,14 @@ #define ICPT_CPU_STOP 0x28 #define ICPT_IO 0x40 +static CPUWatchpoint hw_watchpoint; +/* + * We don't use a list because this structure is also used to transmit the + * hardware breakpoints to the kernel. + */ +static struct kvm_hw_breakpoint *hw_breakpoints; +static int nb_hw_breakpoints; + const KVMCapabilityInfo kvm_arch_required_capabilities[] = { KVM_CAP_LAST_INFO }; @@ -351,24 +360,126 @@ int kvm_arch_remove_sw_breakpoint(CPUState *cs, struct kvm_sw_breakpoint *bp) return 0; } +static struct kvm_hw_breakpoint *find_hw_breakpoint(target_ulong addr, + int len, int type) +{ + int n; + + for (n = 0; n < nb_hw_breakpoints; n++) { + if (hw_breakpoints[n].addr == addr && hw_breakpoints[n].type == type && + (hw_breakpoints[n].len == len || len == -1)) { + return &hw_breakpoints[n]; + } + } + + return NULL; +} + +static int insert_hw_breakpoint(target_ulong addr, int len, int type) +{ + int size; + + if (find_hw_breakpoint(addr, len, type)) { + return -EEXIST; + } + + size = (nb_hw_breakpoints + 1) * sizeof(struct kvm_hw_breakpoint); + + if (!hw_breakpoints) { + nb_hw_breakpoints = 0; + hw_breakpoints = (struct kvm_hw_breakpoint *)g_try_malloc(size); + } else { + hw_breakpoints = + (struct kvm_hw_breakpoint *)g_try_realloc(hw_breakpoints, size); + } + + if (!hw_breakpoints) { + nb_hw_breakpoints = 0; + return -ENOMEM; + } + + hw_breakpoints[nb_hw_breakpoints].addr = addr; + hw_breakpoints[nb_hw_breakpoints].len = len; + hw_breakpoints[nb_hw_breakpoints].type = type; + + nb_hw_breakpoints++; + + return 0; +} + int kvm_arch_insert_hw_breakpoint(target_ulong addr, target_ulong len, int type) { - return -ENOSYS; + switch (type) { + case GDB_BREAKPOINT_HW: + type = KVM_HW_BP; + break; + case GDB_WATCHPOINT_WRITE: + if (len < 1) { + return -EINVAL; + } + type = KVM_HW_WP_WRITE; + break; + default: + return -ENOSYS; + } + return insert_hw_breakpoint(addr, len, type); } int kvm_arch_remove_hw_breakpoint(target_ulong addr, target_ulong len, int type) { - return -ENOSYS; + int size; + struct kvm_hw_breakpoint *bp = find_hw_breakpoint(addr, len, type); + + if (bp == NULL) { + return -ENOENT; + } + + nb_hw_breakpoints--; + if (nb_hw_breakpoints > 0) { + /* + * In order to trim the array, move the last element to the position to + * be removed - if necessary. + */ + if (bp != &hw_breakpoints[nb_hw_breakpoints]) { + *bp = hw_breakpoints[nb_hw_breakpoints]; + } + size = nb_hw_breakpoints * sizeof(struct kvm_hw_breakpoint); + hw_breakpoints = + (struct kvm_hw_breakpoint *)g_realloc(hw_breakpoints, size); + } else { + g_free(hw_breakpoints); + hw_breakpoints = NULL; + } + + return 0; } void kvm_arch_remove_all_hw_breakpoints(void) { + nb_hw_breakpoints = 0; + g_free(hw_breakpoints); + hw_breakpoints = NULL; } void kvm_arch_update_guest_debug(CPUState *cpu, struct kvm_guest_debug *dbg) { + int i; + + if (nb_hw_breakpoints > 0) { + dbg->arch.nr_hw_bp = nb_hw_breakpoints; + dbg->arch.hw_bp = hw_breakpoints; + + for (i = 0; i < nb_hw_breakpoints; ++i) { + hw_breakpoints[i].phys_addr = s390_cpu_get_phys_addr_debug(cpu, + hw_breakpoints[i].addr); + } + dbg->control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_HW_BP; + } else { + dbg->arch.nr_hw_bp = 0; + dbg->arch.hw_bp = NULL; + } } void kvm_arch_pre_run(CPUState *cpu, struct kvm_run *run) @@ -853,7 +964,36 @@ static int handle_tsch(S390CPU *cpu) static int kvm_arch_handle_debug_exit(S390CPU *cpu) { - return -ENOSYS; + CPUState *cs = CPU(cpu); + struct kvm_run *run = cs->kvm_run; + + int ret = 0; + struct kvm_debug_exit_arch *arch_info = &run->debug.arch; + + switch (arch_info->type) { + case KVM_HW_WP_WRITE: + if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) { + cs->watchpoint_hit = &hw_watchpoint; + hw_watchpoint.vaddr = arch_info->addr; + hw_watchpoint.flags = BP_MEM_WRITE; + ret = EXCP_DEBUG; + } + break; + case KVM_HW_BP: + if (find_hw_breakpoint(arch_info->addr, -1, arch_info->type)) { + ret = EXCP_DEBUG; + } + break; + case KVM_SINGLESTEP: + if (cs->singlestep_enabled) { + ret = EXCP_DEBUG; + } + break; + default: + ret = -ENOSYS; + } + + return ret; } int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run)