diff options
author | aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> | 2009-03-12 20:12:48 +0000 |
---|---|---|
committer | aliguori <aliguori@c046a42c-6fe2-441c-8c8c-71466251a162> | 2009-03-12 20:12:48 +0000 |
commit | e22a25c9361c44995c9241c24df0e1e2c47a56c8 (patch) | |
tree | 4bf900c761e36db33146e5527baed9663cf9a785 /kvm-all.c | |
parent | eda578e559879b1a6a85f924adf2942070ae7ec3 (diff) |
Guest debugging support for KVM (Jan Kiszka)
This is a backport of the guest debugging support for the KVM
accelerator that is now part of the KVM tree. It implements the reworked
KVM kernel API for guest debugging (KVM_CAP_SET_GUEST_DEBUG) which is
not yet part of any mainline kernel but will probably be 2.6.30 stuff.
So far supported is x86, but PPC is expected to catch up soon.
Core features are:
- unlimited soft-breakpoints via code patching
- hardware-assisted x86 breakpoints and watchpoints
Changes in this version:
- use generic hook cpu_synchronize_state to transfer registers between
user space and kvm
- push kvm_sw_breakpoints into KVMState
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6825 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'kvm-all.c')
-rw-r--r-- | kvm-all.c | 173 |
1 files changed, 173 insertions, 0 deletions
@@ -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 */ |