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 <dahi@linux.vnet.ibm.com>
Signed-off-by: Jens Freimann <jfrei@linux.vnet.ibm.com>
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
This commit is contained in:
David Hildenbrand 2012-09-03 13:09:10 +02:00 committed by Cornelia Huck
parent b30f4dfbda
commit 770a63792b
3 changed files with 156 additions and 3 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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)