qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 1/2] Guest debugging support for KVM - v2


From: Jan Kiszka
Subject: [Qemu-devel] [PATCH 1/2] Guest debugging support for KVM - v2
Date: Sun, 25 Jan 2009 19:11:48 +0100
User-agent: Mozilla/5.0 (X11; U; Linux i686 (x86_64); de; rv:1.8.1.12) Gecko/20080226 SUSE/2.0.0.12-1.1 Thunderbird/2.0.0.12 Mnenhy/0.7.5.666

[ Recent kvm headers no longer require CONFIG_<ARCH>, so I dropped the
related configure change. Moreover, this version is not capable of
setting soft breakpoint in ROM memory. ]

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

Signed-off-by: Jan Kiszka <address@hidden>
---

 exec.c            |   10 ++-
 gdbstub.c         |   29 ++++++--
 gdbstub.h         |    7 ++
 kvm-all.c         |  172 ++++++++++++++++++++++++++++++++++++++++++++++++
 kvm.h             |   41 +++++++++++
 target-i386/kvm.c |  191 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 440 insertions(+), 10 deletions(-)

diff --git a/exec.c b/exec.c
index 56e5e48..84c82ec 100644
--- a/exec.c
+++ b/exec.c
@@ -1456,9 +1456,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 b4b8292..0a91c7d 100644
--- a/gdbstub.c
+++ b/gdbstub.c
@@ -38,6 +38,7 @@
 #define MAX_PACKET_LENGTH 4096
 
 #include "qemu_socket.h"
+#include "kvm.h"
 
 
 enum {
@@ -1416,13 +1417,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,
@@ -1436,6 +1430,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:
@@ -1467,6 +1464,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:
@@ -1496,6 +1496,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
@@ -1536,6 +1541,8 @@ 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;
+            if (kvm_enabled())
+                kvm_put_registers(s->c_cpu);
 #elif defined (TARGET_PPC)
             s->c_cpu->nip = addr;
 #elif defined (TARGET_SPARC)
@@ -1577,6 +1584,8 @@ 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;
+            if (kvm_enabled())
+                kvm_put_registers(s->c_cpu);
 #elif defined (TARGET_PPC)
             s->c_cpu->nip = addr;
 #elif defined (TARGET_SPARC)
@@ -1622,6 +1631,8 @@ static int gdb_handle_packet(GDBState *s, const char 
*line_buf)
         }
         break;
     case 'g':
+        if (kvm_enabled())
+            kvm_get_registers(s->g_cpu);
         len = 0;
         for (addr = 0; addr < num_g_regs; addr++) {
             reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr);
@@ -1639,6 +1650,8 @@ static int gdb_handle_packet(GDBState *s, const char 
*line_buf)
             len -= reg_size;
             registers += reg_size;
         }
+        if (kvm_enabled())
+            kvm_put_registers(s->g_cpu);
         put_packet(s, "OK");
         break;
     case 'm':
@@ -1797,6 +1810,8 @@ 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) {
+                    if (kvm_enabled())
+                        kvm_get_registers(env);
                     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 c7d1c4b..5740041 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 9fb295c..51bb6e3 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 */
@@ -156,6 +157,16 @@ int kvm_sync_vcpus(void)
     return 0;
 }
 
+void kvm_put_registers(CPUState *env)
+{
+    kvm_arch_put_registers(env);
+}
+
+void kvm_get_registers(CPUState *env)
+{
+    kvm_arch_get_registers(env);
+}
+
 /*
  * dirty pages logging control
  */
@@ -511,6 +522,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");
@@ -663,3 +684,154 @@ int kvm_has_sync_mmu(void)
 
     return 0;
 }
+
+#ifdef KVM_CAP_SET_GUEST_DEBUG
+struct kvm_sw_breakpoint_head kvm_sw_breakpoints =
+    TAILQ_HEAD_INITIALIZER(kvm_sw_breakpoints);
+
+struct kvm_sw_breakpoint *kvm_find_sw_breakpoint(target_ulong pc)
+{
+    struct kvm_sw_breakpoint *bp;
+
+    TAILQ_FOREACH(bp, &kvm_sw_breakpoints, entry) {
+        if (bp->pc == pc)
+            return bp;
+    }
+    return NULL;
+}
+
+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(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(&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(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(&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;
+    CPUState *env;
+
+    TAILQ_FOREACH_SAFE(bp, &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 efce145..0f5f612 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,16 @@ 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);
 
+void kvm_get_registers(CPUState *env);
+void kvm_put_registers(CPUState *env);
+
+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 +87,34 @@ 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);
+
+extern struct kvm_sw_breakpoint_head kvm_sw_breakpoints;
+
+int kvm_arch_debug(struct kvm_debug_exit_arch *arch_info);
+
+struct kvm_sw_breakpoint *kvm_find_sw_breakpoint(target_ulong pc);
+
+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);
+
 #endif
diff --git a/target-i386/kvm.c b/target-i386/kvm.c
index 2412ae4..88c3137 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
 
@@ -637,3 +638,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(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 (!TAILQ_EMPTY(&kvm_sw_breakpoints))
+        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 */




reply via email to

[Prev in Thread] Current Thread [Next in Thread]