From 5717f513696c4d926327df2af2ccdbfc80adbc29 Mon Sep 17 00:00:00 2001 From: Tim Deegan Date: Tue, 26 Oct 2010 11:56:31 +0100 Subject: "kdd" Windows debugger stub. kdd runs in dom0, attaches to a domain and speaks the Windows kd serial protocol over a TCP connection (which should go to kd or windbg, e.g. by having another VM with its virtual COM1 set up as a TCP listener). It doesn't do breakpoints &c yet, and windbg can get quite confused since the kernel debugger's not actually running, but it's good enough to extract backtraces from wedged VMs. Signed-off-by: Tim Deegan --- .hgignore | 1 + MAINTAINERS | 5 + tools/Makefile | 11 + tools/debugger/kdd/Makefile | 22 + tools/debugger/kdd/kdd-xen.c | 625 +++++++++++++++++++++++++ tools/debugger/kdd/kdd.c | 1045 ++++++++++++++++++++++++++++++++++++++++++ tools/debugger/kdd/kdd.h | 520 +++++++++++++++++++++ 7 files changed, 2229 insertions(+) create mode 100644 tools/debugger/kdd/Makefile create mode 100644 tools/debugger/kdd/kdd-xen.c create mode 100644 tools/debugger/kdd/kdd.c create mode 100644 tools/debugger/kdd/kdd.h diff --git a/.hgignore b/.hgignore index 77353002ca..503840b025 100644 --- a/.hgignore +++ b/.hgignore @@ -129,6 +129,7 @@ ^tools/debugger/gdb/gdb-6\.2\.1/.*$ ^tools/debugger/gdb/gdb-6\.2\.1\.tar\.bz2$ ^tools/debugger/gdbsx/gdbsx$ +^tools/debugger/kdd/kdd$ ^tools/debugger/xenitp/xenitp$ ^tools/firmware/.*/biossums$ ^tools/firmware/.*\.bin$ diff --git a/MAINTAINERS b/MAINTAINERS index 6a7bf47a1c..938c79b3fa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -107,6 +107,11 @@ S: Supported F: xen/arch/x86/debug.c F: tools/debugger/gdbsx/ +KDD DEBUGGER +M: Tim Deegan +S: Supported +F: tools/debugger/kdd/ + INTEL(R) TRUSTED EXECUTION TECHNOLOGY (TXT) M: Joseph Cihula M: Shane Wang diff --git a/tools/Makefile b/tools/Makefile index a6cdad2765..97bfecf862 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -36,6 +36,7 @@ SUBDIRS-y += libxl SUBDIRS-y += remus SUBDIRS-$(CONFIG_X86) += xenpaging SUBDIRS-$(CONFIG_X86) += debugger/gdbsx +SUBDIRS-$(CONFIG_X86) += debugger/kdd # These don't cross-compile ifeq ($(XEN_COMPILE_ARCH),$(XEN_TARGET_ARCH)) @@ -128,5 +129,15 @@ subdir-install-debugger/gdbsx: .phony subdir-all-debugger/gdbsx: .phony $(MAKE) -C debugger/gdbsx all + +subdir-clean-debugger/kdd subdir-distclean-debugger/kdd: .phony + $(MAKE) -C debugger/kdd clean + +subdir-install-debugger/kdd: .phony + $(MAKE) -C debugger/kdd install + +subdir-all-debugger/kdd: .phony + $(MAKE) -C debugger/kdd all + subdir-distclean-firmware: .phony $(MAKE) -C firmware distclean diff --git a/tools/debugger/kdd/Makefile b/tools/debugger/kdd/Makefile new file mode 100644 index 0000000000..0211c008dd --- /dev/null +++ b/tools/debugger/kdd/Makefile @@ -0,0 +1,22 @@ +XEN_ROOT = ../../.. +include $(XEN_ROOT)/tools/Rules.mk + +CFLAGS += $(CFLAGS_libxenctrl) +LDLIBS += $(LDLIBS_libxenctrl) + +CFILES := kdd.c kdd-xen.c +OBJS := $(CFILES:.c=.o) + +all: kdd + +kdd: $(OBJS) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +.PHONY: clean +clean: + rm -f $(OBJS) kdd + +.PHONY: install +install: all + [ -d $(DESTDIR)$(SBINDIR) ] || $(INSTALL_DIR) $(DESTDIR)$(SBINDIR) + $(INSTALL_PROG) kdd $(DESTDIR)$(SBINDIR)/kdd diff --git a/tools/debugger/kdd/kdd-xen.c b/tools/debugger/kdd/kdd-xen.c new file mode 100644 index 0000000000..b2488c8ecf --- /dev/null +++ b/tools/debugger/kdd/kdd-xen.c @@ -0,0 +1,625 @@ +/* + * kdd-xen.c -- xen-specific functions for the kdd debugging stub + * + * Tim Deegan + * + * Copyright (c) 2007-2010, Citrix Systems Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kdd.h" + +#define MAPSIZE 4093 /* Prime */ + +#define PAGE_SHIFT 12 +#define PAGE_SIZE (1U << PAGE_SHIFT) + +struct kdd_guest { + struct xentoollog_logger xc_log; /* Must be first for xc log callbacks */ + xc_interface *xc_handle; + uint32_t domid; + char id[80]; + FILE *log; + int verbosity; + void *hvm_buf; + uint32_t hvm_sz; + uint32_t pfns[MAPSIZE]; + void * maps[MAPSIZE]; +}; + + +/* Flush any mappings we have of the guest memory: it's not polite + * top hold on to them while the guest is running */ +static void flush_maps(kdd_guest *g) +{ + int i; + for (i = 0; i < MAPSIZE; i++) { + if (g->maps[i] != NULL) + munmap(g->maps[i], PAGE_SIZE); + g->maps[i] = NULL; + } +} + + +/* Halt the guest so we can debug it */ +void kdd_halt(kdd_guest *g) +{ + uint32_t sz; + void *buf; + + xc_domain_pause(g->xc_handle, g->domid); + + /* How much space do we need for the HVM state? */ + sz = xc_domain_hvm_getcontext(g->xc_handle, g->domid, 0, 0); + if (sz == (uint32_t) -1) { + KDD_LOG(g, "Can't get HVM state size for domid %"PRIu32": %s\n", + g->domid, strerror(errno)); + return; + } + buf = realloc(g->hvm_buf, sz); + if (!buf) { + KDD_LOG(g, "Couldn't allocate %"PRIu32" for HVM buffer\n", sz); + return; + } + g->hvm_buf = buf; + g->hvm_sz = sz; + memset(buf, 0, sz); + + /* Get the HVM state */ + sz = xc_domain_hvm_getcontext(g->xc_handle, g->domid, buf, sz); + if (sz == (uint32_t) -1) { + KDD_LOG(g, "Can't get HVM state for domid %"PRIu32": %s\n", + g->domid, strerror(errno)); + } +} + +/* Check whether the guest has stopped. Returns 'interesting' vcpu or -1 */ +/* TODO: open DEBUG_VIRQ if it's free and wait for events rather than + * always polling the guest state */ +int kdd_poll_guest(kdd_guest *g) +{ + /* TODO: finish plumbing through polling for breakpoints */ + return 0; +} + +/* Update the HVM state */ +static void hvm_writeback(kdd_guest *g) +{ + if (g->hvm_buf && xc_domain_hvm_setcontext(g->xc_handle, g->domid, + g->hvm_buf, g->hvm_sz)) + KDD_LOG(g, "Can't set HVM state for domid %"PRIu32": %s\n", + g->domid, strerror(errno)); +} + +/* Start the guest running again */ +void kdd_run(kdd_guest *g) +{ + flush_maps(g); + hvm_writeback(g); + xc_domain_unpause(g->xc_handle, g->domid); +} + +/* How many CPUs are there in this guest? */ +int kdd_count_cpus(kdd_guest *g) +{ + struct hvm_save_descriptor *desc; + int maxcpu = 0; + + if (!g->hvm_buf) + return 0; + + /* Scan the CPU save records. */ + for (desc = g->hvm_buf; + (void *) desc >= g->hvm_buf && (void *) desc < g->hvm_buf + g->hvm_sz; + desc = ((void *)desc) + (sizeof *desc) + desc->length) { + if (desc->typecode == HVM_SAVE_CODE(CPU)) { + if (maxcpu < desc->instance) + maxcpu = desc->instance; + } + } + return maxcpu + 1; +} + +/* Helper fn: get CPU state from cached HVM state */ +static struct hvm_hw_cpu *get_cpu(kdd_guest *g, int cpuid) +{ + struct hvm_save_descriptor *desc; + struct hvm_hw_cpu *cpu; + + if (!g->hvm_buf) + return NULL; + + /* Find the right CPU */ + for (desc = g->hvm_buf; + (void *) desc >= g->hvm_buf && (void *) desc < g->hvm_buf + g->hvm_sz; + desc = ((void *)desc) + (sizeof *desc) + desc->length) { + if (desc->typecode == HVM_SAVE_CODE(CPU) && desc->instance == cpuid) { + cpu = ((void *)desc) + (sizeof *desc); + if ((void *) cpu + sizeof (*cpu) <= g->hvm_buf + g->hvm_sz) + return cpu; + } + } + + KDD_LOG(g, "Dom %"PRIu32" has no CPU %i\n", g->domid, cpuid); + return NULL; +} + +/* Helper fn: get APIC state from cached HVM state */ +static struct hvm_hw_lapic_regs *get_lapic(kdd_guest *g, int cpuid) +{ + struct hvm_save_descriptor *desc; + struct hvm_hw_lapic_regs *regs; + + if (!g->hvm_buf) + return NULL; + + /* Find the right CPU's LAPIC */ + for (desc = g->hvm_buf; + (void *) desc >= g->hvm_buf && (void *) desc < g->hvm_buf + g->hvm_sz; + desc = ((void *)desc) + (sizeof *desc) + desc->length) { + if (desc->typecode == HVM_SAVE_CODE(LAPIC_REGS) && desc->instance == cpuid) { + regs = ((void *)desc) + (sizeof *desc); + if ((void *) regs + sizeof (*regs) <= g->hvm_buf + g->hvm_sz) + return regs; + } + } + + KDD_LOG(g, "Dom %"PRIu32" has no LAPIC %i\n", g->domid, cpuid); + return NULL; +} + + +/* Accessors for guest user registers */ +static void kdd_get_regs_x86_32(struct hvm_hw_cpu *cpu, kdd_regs_x86_32 *r) +{ + r->gs = cpu->gs_sel; + r->fs = cpu->fs_sel; + r->es = cpu->es_sel; + r->ds = cpu->ds_sel; + r->edi = cpu->rdi; + r->esi = cpu->rsi; + r->ebx = cpu->rbx; + r->edx = cpu->rdx; + r->ecx = cpu->rcx; + r->eax = cpu->rax; + r->ebp = cpu->rbp; + r->eip = cpu->rip; + r->cs = cpu->cs_sel; + r->eflags = cpu->rflags; + r->esp = cpu->rsp; + r->ss = cpu->ss_sel; + memcpy(r->fp, cpu->fpu_regs, 112); // 108 save area + 4 of ??? +} + +static void kdd_set_regs_x86_32(struct hvm_hw_cpu *cpu, kdd_regs_x86_32 *r) +{ + cpu->gs_sel = r->gs; + cpu->fs_sel = r->fs; + cpu->es_sel = r->es; + cpu->ds_sel = r->ds; + cpu->rdi = r->edi; + cpu->rsi = r->esi; + cpu->rbx = r->ebx; + cpu->rdx = r->edx; + cpu->rcx = r->ecx; + cpu->rax = r->eax; + cpu->rbp = r->ebp; + cpu->rip = r->eip; + cpu->cs_sel = r->cs; + cpu->rflags = r->eflags; + cpu->rsp = r->esp; + cpu->ss_sel = r->ss; + memcpy(cpu->fpu_regs, r->fp, 112); // 108 save area + 4 of ??? +} + +static void kdd_get_regs_x86_64(struct hvm_hw_cpu *cpu, kdd_regs_x86_64 *r) +{ + // XXX debug pattern + uint16_t i; + for (i = 0 ; i < (sizeof *r / 2) ; i++) + ((uint16_t *)r)[i] = i; + + r->cs = cpu->cs_sel; + r->ds = cpu->ds_sel; + r->es = cpu->es_sel; + r->fs = cpu->fs_sel; + r->gs = cpu->gs_sel; + r->ss = cpu->ss_sel; + r->rflags = cpu->rflags; + r->dr0 = cpu->dr0; + r->dr1 = cpu->dr1; + r->dr2 = cpu->dr2; + r->dr3 = cpu->dr3; + r->dr6 = cpu->dr6; + r->dr7 = cpu->dr7; + r->rax = cpu->rax; + r->rcx = cpu->rcx; + r->rdx = cpu->rdx; + r->rbx = cpu->rbx; + r->rsp = cpu->rsp; + r->rbp = cpu->rbp; + r->rsi = cpu->rsi; + r->rdi = cpu->rdi; + r->r8 = cpu->r8; + r->r9 = cpu->r9; + r->r10 = cpu->r10; + r->r11 = cpu->r11; + r->r12 = cpu->r12; + r->r13 = cpu->r13; + r->r14 = cpu->r14; + r->r15 = cpu->r15; + r->rip = cpu->rip; + memcpy(r->fp, cpu->fpu_regs, 112); // Definitely not right +} + +static void kdd_set_regs_x86_64(struct hvm_hw_cpu *cpu, kdd_regs_x86_64 *r) +{ + cpu->cs_sel = r->cs; + cpu->ds_sel = r->ds; + cpu->es_sel = r->es; + cpu->fs_sel = r->fs; + cpu->gs_sel = r->gs; + cpu->ss_sel = r->ss; + cpu->rflags = r->rflags; + cpu->dr0 = r->dr0; + cpu->dr1 = r->dr1; + cpu->dr2 = r->dr2; + cpu->dr3 = r->dr3; + cpu->dr6 = r->dr6; + cpu->dr7 = r->dr7; + cpu->rax = r->rax; + cpu->rcx = r->rcx; + cpu->rdx = r->rdx; + cpu->rbx = r->rbx; + cpu->rsp = r->rsp; + cpu->rbp = r->rbp; + cpu->rsi = r->rsi; + cpu->rdi = r->rdi; + cpu->r8 = r->r8; + cpu->r9 = r->r9; + cpu->r10 = r->r10; + cpu->r11 = r->r11; + cpu->r12 = r->r12; + cpu->r13 = r->r13; + cpu->r14 = r->r14; + cpu->r15 = r->r15; + cpu->rip = r->rip; + memcpy(r->fp, cpu->fpu_regs, 112); // Definitely not right +} + + +int kdd_get_regs(kdd_guest *g, int cpuid, kdd_regs *r, int w64) +{ + struct hvm_hw_cpu *cpu; + + cpu = get_cpu(g, cpuid); + if (!cpu) + return -1; + + memset(r, 0, sizeof(r)); + + if (w64) + kdd_get_regs_x86_64(cpu, &r->r64); + else + kdd_get_regs_x86_32(cpu, &r->r32); + + return 0; +} + +int kdd_set_regs(kdd_guest *g, int cpuid, kdd_regs *r, int w64) +{ + struct hvm_hw_cpu *cpu; + + cpu = get_cpu(g, cpuid); + if (!cpu) + return -1; + + if (w64) + kdd_set_regs_x86_64(cpu, &r->r64); + else + kdd_set_regs_x86_32(cpu, &r->r32); + + hvm_writeback(g); + return 0; +} + + +/* Accessors for guest control registers */ +static void kdd_get_ctrl_x86_32(struct hvm_hw_cpu *cpu, kdd_ctrl_x86_32 *c) +{ + c->cr0 = cpu->cr0; + c->cr2 = cpu->cr2; + c->cr3 = cpu->cr3; + c->cr4 = cpu->cr4; + c->dr0 = cpu->dr0; + c->dr1 = cpu->dr1; + c->dr2 = cpu->dr2; + c->dr3 = cpu->dr3; + c->dr6 = cpu->dr6; + c->dr7 = cpu->dr7; + c->gdt_base = cpu->gdtr_base; + c->gdt_limit = cpu->gdtr_limit; + c->idt_base = cpu->idtr_base; + c->idt_limit = cpu->idtr_limit; + c->tss_sel = cpu->tr_sel; + c->ldt_sel = cpu->ldtr_sel; +} + +static void kdd_get_ctrl_x86_64(struct hvm_hw_cpu *cpu, + struct hvm_hw_lapic_regs *lapic, + kdd_ctrl_x86_64 *c) +{ + c->cr0 = cpu->cr0; + c->cr2 = cpu->cr2; + c->cr3 = cpu->cr3; + c->cr4 = cpu->cr4; + c->dr0 = cpu->dr0; + c->dr1 = cpu->dr1; + c->dr2 = cpu->dr2; + c->dr3 = cpu->dr3; + c->dr6 = cpu->dr6; + c->dr7 = cpu->dr7; + c->gdt_base = cpu->gdtr_base; + c->gdt_limit = cpu->gdtr_limit; + c->idt_base = cpu->idtr_base; + c->idt_limit = cpu->idtr_limit; + c->tss_sel = cpu->tr_sel; + c->ldt_sel = cpu->ldtr_sel; + c->cr8 = lapic->data[0x80] >> 4; /* Top half of the low byte of the TPR */ +} + + +int kdd_get_ctrl(kdd_guest *g, int cpuid, kdd_ctrl *ctrl, int w64) +{ + struct hvm_hw_cpu *cpu; + struct hvm_hw_lapic_regs *lapic; + + cpu = get_cpu(g, cpuid); + if (!cpu) + return -1; + + if (w64) { + lapic = get_lapic(g, cpuid); + if (!lapic) + return -1; + kdd_get_ctrl_x86_64(cpu, lapic, &ctrl->c64); + } else { + kdd_get_ctrl_x86_32(cpu, &ctrl->c32); + } + + return 0; +} + +int kdd_wrmsr(kdd_guest *g, int cpuid, uint32_t msr, uint64_t value) +{ + struct hvm_hw_cpu *cpu; + + cpu = get_cpu(g, cpuid); + if (!cpu) + return -1; + + switch (msr) { + case 0x00000174: cpu->sysenter_cs = value; break; + case 0x00000175: cpu->sysenter_esp = value; break; + case 0x00000176: cpu->sysenter_eip = value; break; + case 0xc0000080: cpu->msr_efer = value; break; + case 0xc0000081: cpu->msr_star = value; break; + case 0xc0000082: cpu->msr_lstar = value; break; + case 0xc0000083: cpu->msr_cstar = value; break; + case 0xc0000084: cpu->msr_syscall_mask = value; break; + case 0xc0000100: cpu->fs_base = value; break; + case 0xc0000101: cpu->gs_base = value; break; + case 0xc0000102: cpu->shadow_gs = value; break; + default: + return -1; + } + + hvm_writeback(g); + return 0; +} + +int kdd_rdmsr(kdd_guest *g, int cpuid, uint32_t msr, uint64_t *value) +{ + struct hvm_hw_cpu *cpu; + + cpu = get_cpu(g, cpuid); + if (!cpu) + return -1; + + switch (msr) { + case 0x00000174: *value = cpu->sysenter_cs; break; + case 0x00000175: *value = cpu->sysenter_esp; break; + case 0x00000176: *value = cpu->sysenter_eip; break; + case 0xc0000080: *value = cpu->msr_efer; break; + case 0xc0000081: *value = cpu->msr_star; break; + case 0xc0000082: *value = cpu->msr_lstar; break; + case 0xc0000083: *value = cpu->msr_cstar; break; + case 0xc0000084: *value = cpu->msr_syscall_mask; break; + case 0xc0000100: *value = cpu->fs_base; break; + case 0xc0000101: *value = cpu->gs_base; break; + case 0xc0000102: *value = cpu->shadow_gs; break; + default: + return -1; + } + + return 0; +} + + +/* Accessor for guest physical memory */ +static uint32_t kdd_access_physical_page(kdd_guest *g, uint64_t addr, + uint32_t len, uint8_t *buf, int write) +{ + uint32_t map_pfn, map_offset; + uint8_t *map; + + map_pfn = (addr >> PAGE_SHIFT); + map_offset = addr & (PAGE_SIZE - 1); + + /* Evict any mapping of the wrong frame from our slot */ + if (g->pfns[map_pfn % MAPSIZE] != map_pfn + && g->maps[map_pfn % MAPSIZE] != NULL) { + munmap(g->maps[map_pfn % MAPSIZE], PAGE_SIZE); + g->maps[map_pfn % MAPSIZE] = NULL; + } + g->pfns[map_pfn % MAPSIZE] = map_pfn; + + /* Now map the frame if it's not already there */ + if (g->maps[map_pfn % MAPSIZE] != NULL) + map = g->maps[map_pfn % MAPSIZE]; + else { + map = xc_map_foreign_range(g->xc_handle, + g->domid, + PAGE_SIZE, + PROT_READ|PROT_WRITE, + map_pfn); + + KDD_DEBUG(g, "map: %u, 0x%16.16"PRIx32": %p +0x%"PRIx32"\n", + write ? PROT_READ|PROT_WRITE : PROT_READ, + map_pfn, map, map_offset); + + if (!map) + return 0; + g->maps[map_pfn % MAPSIZE] = map; + } + + if (write) + memcpy(map + map_offset, buf, len); + else + memcpy(buf, map + map_offset, len); + + return len; +} + +uint32_t kdd_access_physical(kdd_guest *g, uint64_t addr, + uint32_t len, uint8_t *buf, int write) +{ + uint32_t chunk, rv, done = 0; + while (len > 0) { + chunk = PAGE_SIZE - (addr & (PAGE_SIZE - 1)); + if (chunk > len) + chunk = len; + rv = kdd_access_physical_page(g, addr, chunk, buf, write); + done += rv; + if (rv != chunk) + return done; + addr += chunk; + buf += chunk; + len -= chunk; + } + return done; +} + + +/* Plumb libxc log messages into our own logging */ +static void kdd_xc_log(struct xentoollog_logger *logger, + xentoollog_level level, + int errnoval /* or -1 */, + const char *context /* eg "xc", "xl", may be 0 */, + const char *format /* without level, context, \n */, + va_list al) +{ + kdd_guest *g = (kdd_guest *) logger; + /* Suppress most libxc levels unless we're logging at debug level */ + if (g->verbosity < 1 || (level < XTL_WARN && g->verbosity < 3)) + return; + fprintf(g->log, "libxc[%s:%i:%i]: ", context ? : "?", level, errnoval); + vfprintf(g->log, format, al); + fprintf(g->log, "\n"); + (void) fflush(g->log); +} + + +/* Set up guest-specific state */ +kdd_guest *kdd_guest_init(char *arg, FILE *log, int verbosity) +{ + kdd_guest *g = NULL; + xc_interface *xch = NULL; + uint32_t domid; + xc_dominfo_t info; + + g = calloc(1, sizeof (kdd_guest)); + if (!g) + goto err; + g->log = log; + g->verbosity = verbosity; + g->xc_log.vmessage = kdd_xc_log; + + xch = xc_interface_open(&g->xc_log, NULL, 0); + if (!xch) + goto err; + g->xc_handle = xch; + + domid = strtoul(arg, NULL, 0); + if (domid == 0) + goto err; + g->domid = domid; + + /* Check that the domain exists and is HVM */ + if (xc_domain_getinfo(xch, domid, 1, &info) != 1 || !info.hvm) + goto err; + + snprintf(g->id, (sizeof g->id) - 1, + "a xen guest with domain id %i", g->domid); + + return g; + + err: + free(g); + if (xch) + xc_interface_close(xch); + return NULL; +} + +/* Say what kind of guest this is */ +char *kdd_guest_identify(kdd_guest *g) +{ + return g->id; +} + +/* Tear down guest-specific state */ +void kdd_guest_teardown(kdd_guest *g) +{ + flush_maps(g); + xc_interface_close(g->xc_handle); + free(g->id); + free(g->hvm_buf); + free(g); +} diff --git a/tools/debugger/kdd/kdd.c b/tools/debugger/kdd/kdd.c new file mode 100644 index 0000000000..9a0225b807 --- /dev/null +++ b/tools/debugger/kdd/kdd.c @@ -0,0 +1,1045 @@ +/* + * kdd.c -- stub for debugging guest OSes with the windows kernel debugger. + * + * Tim Deegan + * + * Copyright (c) 2007-2010, Citrix Systems Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "kdd.h" + +/* Windows version details */ +typedef struct { + uint32_t build; + int w64; + int mp; + char *name; + uint64_t base; /* KernBase: start looking here */ + uint32_t range; /* | and search an area this size */ + uint32_t version; /* +-> NtBuildNumber */ + uint32_t modules; /* +-> PsLoadedModuleList */ + uint32_t prcbs; /* +-> KiProcessorBlock */ +} kdd_os; + +/* State of the debugger stub */ +typedef struct { + union { + uint8_t txb[sizeof (kdd_hdr) + 65536]; /* Marshalling area for tx */ + kdd_pkt txp; /* Also readable as a packet structure */ + }; + union { + uint8_t rxb[sizeof (kdd_hdr) + 65536]; /* Marshalling area for rx */ + kdd_pkt rxp; /* Also readable as a packet structure */ + }; + unsigned int cur; /* Offset into rx where we'll put the next byte */ + uint32_t next_id; /* ID of next packet we will send */ + int running; /* Are the guest's processors active? */ + int cpuid; /* Current selected CPU */ + int fd; /* TCP socket for client comms */ + FILE *log; /* For tracing output */ + int verbosity; /* How much detail to trace */ + kdd_guest *guest; /* Arch-specific state for guest control */ + kdd_os os; /* OS-specific magic numbers */ +} kdd_state; + +/***************************************************************************** + * Utility functions + */ + +/* Get the instruction pointer */ +static uint64_t kdd_get_ip(kdd_state *s) +{ + kdd_regs r; + if (!s->os.w64 && kdd_get_regs(s->guest, s->cpuid, &r, 0) == 0) + return r.r32.eip; + else if (s->os.w64 && kdd_get_regs(s->guest, s->cpuid, &r, 1) == 0) + return r.r64.rip; + else + return -1ULL; +} + +/* Turn write(2) into a proper blocking write. */ +static size_t blocking_write(int fd, const void *buf, size_t count) +{ + size_t left = count; + ssize_t r; + while (left > 0 && ((r = write(fd, buf, left)) >= 0 || errno == EINTR)) { + buf += r; + left -= r; + } + return count - left; +} + +/* Dump the contents of a complete serial packet into a log file. */ +static void kdd_log_pkt(kdd_state *s, char *name, kdd_pkt *p) +{ + uint32_t sum = 0; + unsigned int i, j; + uint8_t ascii[17] = {0}; + FILE *f = s->log; + + if (s->verbosity < 2) + return; + + /* Re-check the checksum */ + for (i = 0; i < p->h.len; i++) + sum += p->payload[i]; + + fprintf(f, "\n" + "%s: %s type 0x%4.4"PRIx16" len 0x%4.4"PRIx16 + " id 0x%8.8"PRIx32" sum 0x%"PRIx32" (%s: 0x%"PRIx32")\n", + name, + p->h.dir == KDD_DIR_PKT ? "pkt" : + p->h.dir == KDD_DIR_ACK ? "ack" : "???", + (unsigned) p->h.type, p->h.len, p->h.id, p->h.sum, + sum == p->h.sum ? "OK" : "BAD", sum); + + /* Hexdump the payload in "canonical" format*/ + for (i = 0; i < p->h.len; i ++) { + if (i % 16 == 0) { + memset(ascii, 0, 17); + fprintf(f, "%8.8x ", i); + } else if (i % 8 == 0) + fprintf(f, " "); + fprintf(f, " %2.2x", p->payload[i]); + ascii[i % 16] = (isprint(((int)p->payload[i])) ? p->payload[i] : 0x2e); + if (i % 16 == 15) + fprintf(f, " |%s|\n", ascii); + } + if (i % 16 != 0) { + for (j = i % 16 ; j < 16; j++) { + fprintf(f, "%s", (j == 8) ? " " : " "); + } + fprintf(f, " |%s|\n%8.8x\n", ascii, i); + } + + fprintf(f, "\n"); + (void) fflush(f); +} + + +/***************************************************************************** + * Memory access: virtual addresses and syntactic sugar. + */ + +#define PAGE_SHIFT (12) +#define PAGE_SIZE (1ULL << PAGE_SHIFT) + +static uint32_t kdd_read_physical(kdd_state *s, uint64_t addr, + uint32_t len, void *buf) +{ + return kdd_access_physical(s->guest, addr, len, buf, 0); +} + +static uint32_t kdd_write_physical(kdd_state *s, uint64_t addr, + uint32_t len, void *buf) +{ + return kdd_access_physical(s->guest, addr, len, buf, 1); +} + + +/* VA->PA conversion. Returns -1ULL for failure. */ +static uint64_t v2p(kdd_state *s, int cpuid, uint64_t va) +{ + int pg, pae, pse, lma; + int levels, width, bits, shift, offset, i; + uint64_t efer, entry = 0, mask, pa; + kdd_ctrl ctrl; + + if (kdd_get_ctrl(s->guest, cpuid, &ctrl, s->os.w64) != 0 + || kdd_rdmsr(s->guest, cpuid, 0xc0000080, &efer) != 0) + return -1ULL; + + if (s->os.w64) { + pg = !!(ctrl.c64.cr0 & 0x80000000); + lma = !!(efer & 0x00000400); + pae = !!(ctrl.c64.cr4 & 0x00000020); + pse = !!(ctrl.c64.cr4 & 0x00000010) || pae || lma; + pa = ctrl.c64.cr3 & ~0x0fULL; + } else { + pg = !!(ctrl.c32.cr0 & 0x80000000); + pae = !!(ctrl.c32.cr4 & 0x00000020); + lma = 0; + pse = !!(ctrl.c32.cr4 & 0x00000010) || pae; + pa = ctrl.c32.cr3 & ~0x0fULL; + } + KDD_DEBUG(s, "w64 = %u, pg = %u, pae = %u, pse = %u, lma = %u\n", + s->os.w64, pg, pae, pse, lma); + + /* Paging disabled? */ + if (!pg) + return va; + + /* 32/PAE64? */ + if (lma) { + va &= (1ULL<<48) - 1; + width = 8; levels = 4; bits = 9; + } else { + va &= (1ULL<<32) - 1; + if (pae) { + width = 8; levels = 3; bits = 9; + } else { + width = 4; levels = 2; bits = 10; + } + } + KDD_DEBUG(s, "%i levels, va 0x%16.16"PRIx64"\n", levels, va); + + /* Walk the appropriate number of levels */ + for (i = levels; i > 0; i--) { + shift = PAGE_SHIFT + bits * (i-1); + mask = ((1ULL << bits) - 1) << shift; + offset = ((va & mask) >> shift) * width; + KDD_DEBUG(s, "level %i: mask 0x%16.16"PRIx64" pa 0x%16.16"PRIx64 + " offset %i\n",i, mask, pa, offset); + if (kdd_read_physical(s, pa + offset, width, &entry) != width) + return -1ULL; // Bad entry PA + KDD_DEBUG(s, "level %i: entry 0x%16.16"PRIx64"\n", i, entry); + if (!(entry & 0x1)) + return -1ULL; // Not present + pa = entry & 0x000ffffffffff000ULL; + if (pse && (i == 2) && (entry & 0x80)) { // Superpage + mask = ((1ULL << (PAGE_SHIFT + bits)) - 1); + return (pa & ~mask) + (va & mask); + } + } + + return pa + (va & (PAGE_SIZE - 1)); +} + +static uint32_t kdd_access_virtual(kdd_state *s, int cpuid, uint64_t addr, + uint32_t len, void *buf, int write) +{ + uint64_t pa; + uint32_t chunk, rv, done = 0; + + /* Process one page at a time */ + while (len > 0) { + chunk = PAGE_SIZE - (addr & (PAGE_SIZE - 1)); + if (chunk > len) + chunk = len; + pa = v2p(s, cpuid, addr); + KDD_DEBUG(s, "va 0x%"PRIx64" -> pa 0x%"PRIx64"\n", addr, pa); + if (pa == (uint64_t) -1ULL) + return done; + rv = kdd_access_physical(s->guest, pa, chunk, buf, write); + done += rv; + if (rv != chunk) + return done; + addr += chunk; + buf += chunk; + len -= chunk; + } + return done; +} + +static uint32_t kdd_read_virtual(kdd_state *s, int cpuid, uint64_t addr, + uint32_t len, void *buf) +{ + return kdd_access_virtual(s, cpuid, addr, len, buf, 0); +} + +static uint32_t kdd_write_virtual(kdd_state *s, int cpuid, uint64_t addr, + uint32_t len, void *buf) +{ + return kdd_access_virtual(s, cpuid, addr, len, buf, 1); +} + + +/***************************************************************************** + * Version information and related runes for different Windows flavours + */ + +static kdd_os os[] = { + /* Build 64 MP Name &Kernel search base Range +Version +Modules +PRCBs (64b) */ + {2195, 0, 0, "w2k sp4 x32 UP", 0xffffffff80400000ULL, 0x00000000, 0x0006d57c, 0x0006e1b8, 0x0}, + {2195, 0, 1, "w2k sp4 x32 SMP", 0xffffffff80400000ULL, 0x00000000, 0x0006fa1c, 0x00084520, 0x0}, + // PAE/UP, PAE/SMP + + {2600, 0, 0, "xp sp2 x32 UP", 0xffffffff804d7000ULL, 0x00000000, 0x00075568, 0x00083b20, 0x0}, + {2600, 0, 1, "xp sp2 x32 SMP", 0xffffffff804d7000ULL, 0x00000000, 0x0007d0e8, 0x0008d4a0, 0x0}, + // PAE/UP, PAE/SMP + + {2600, 0, 0, "xp sp3 x32 UP", 0xffffffff804d7000ULL, 0x00000000, 0x00075be8, 0x000841c0, 0x0}, + {2600, 0, 1, "xp sp3 x32 SMP", 0xffffffff804d7000ULL, 0x00000000, 0x0007c0e8, 0x0008c4c0, 0x0}, + {2600, 0, 0, "xp sp3 x32p UP", 0xffffffff804d7000ULL, 0x00000000, 0x0006e8e8, 0x0007cfc0, 0x0}, + {2600, 0, 1, "xp sp3 x32p SMP", 0xffffffff804d7000ULL, 0x00000000, 0x000760e8, 0x00086720, 0x0}, + + {3790, 0, 0, "w2k3 sp2 x32 UP", 0xffffffff80800000ULL, 0x00000000, 0x00097128, 0x000a8e48, 0x0}, + {3790, 0, 1, "w2k3 sp2 x32 SMP", 0xffffffff80800000ULL, 0x00000000, 0x0009d128, 0x000af9c8, 0x0}, + {3790, 0, 0, "w2k3 sp2 x32p UP", 0xffffffff80800000ULL, 0x00000000, 0x0008e128, 0x0009ffa8, 0x0}, + {3790, 0, 1, "w2k3 sp2 x32p SMP", 0xffffffff80800000ULL, 0x00000000, 0x00094128, 0x000a6ea8, 0x0}, + {3790, 1, 0, "w2k3 sp2 x64 UP", 0xfffff80001000000ULL, 0x00000000, 0x001765d0, 0x0019aae0, 0x0017b100}, + {3790, 1, 1, "w2k3 sp2 x64 SMP", 0xfffff80001000000ULL, 0x00000000, 0x001b05e0, 0x001d5100, 0x001b5300}, + + {6000, 0, 1, "vista sp0 x32p", 0xffffffff81800000ULL, 0x00000000, 0x000a4de4, 0x00111db0, 0x0}, + {6001, 0, 1, "vista sp1 x32p", 0xffffffff81000000ULL, 0x0f000000, 0x000af0c4, 0x00117c70, 0x0}, + + {6001, 1, 1, "w2k8 sp0 x64", 0xfffff80001000000ULL, 0x0f000000, 0x00140bf0, 0x001c5db0, 0x00229640}, + + {7600, 1, 1, "win7 sp0 x64", 0xfffff80001000000ULL, 0x0f000000, 0x001af770, 0x0023de50, 0x002a8900}, + + {7601, 0, 1, "win7 sp1 x32p", 0xffffffff81800000ULL, 0x0f000000, 0x000524c4, 0x00149850, 0x0}, + {7601, 1, 1, "win7 sp1 x64", 0xfffff80001000000ULL, 0x0f000000, 0x001b2770, 0x00240e90, 0x002ab900}, +}; + +// 1381, 0, 0, "NT4 sp?", 0xffffffff80100000, ?, ? + +static kdd_os unknown_os = {0, 0, 0, "unknown OS", 0, 0, 0, 0, 0}; + +static int check_os(kdd_state *s) +{ + kdd_os *v = &s->os; + uint64_t addr, val; + uint32_t width; + int i; + + /* Kernel address must be a DOS executable */ + val = 0; + if (kdd_read_virtual(s, 0, v->base, 2, &val) != 2 || val != 0x5a4d) { + KDD_DEBUG(s, "not %s: krnl 0x%"PRIx64"\n", v->name, val); + return 0; + } + + /* OS version must match. */ + val = 0; + if (kdd_read_virtual(s, 0, v->base + v->version, 4, &val) != 4 + || val != (v->build | 0xf0000000) ) { + KDD_DEBUG(s, "not %s: version 0x%"PRIx64"\n", v->name, val); + return 0; + } + + /* Module list address must be a circular linked list */ + addr = v->base + v->modules; + val = 0; + width = v->w64 ? 8 : 4; + for (i = 0; val != v->base + v->modules && i < 250; i++) { + val = 0; + if (kdd_read_virtual(s, 0, addr, width, &val) != width) { + KDD_DEBUG(s, "not %s: bad module list\n", v->name); + return 0; + } + addr = val; + } + + return 1; +} + +/* Figure out what OS we're dealing with */ +static void find_os(kdd_state *s) +{ + int i; + uint64_t limit; + + /* We may already have the right one */ + if (check_os(s)) + return; + + /* Try each OS we know about */ + for (i = 0; i < (sizeof os / sizeof os[0]); i++) { + s->os = os[i]; + /* Try each page in the potential range of kernel load addresses */ + for (limit = s->os.base + s->os.range; + s->os.base <= limit; + s->os.base += PAGE_SIZE) + if (check_os(s)) + return; + } + s->os = unknown_os; +} + + +/***************************************************************************** + * How to send packets and acks. + */ + + +/* Send a serial packet */ +static void kdd_tx(kdd_state *s) +{ + uint32_t sum = 0; + size_t len; + int i; + + /* Fix up the checksum before we send */ + for (i = 0; i < s->txp.h.len; i++) + sum += s->txp.payload[i]; + s->txp.h.sum = sum; + + kdd_log_pkt(s, "TX", &s->txp); + + len = s->txp.h.len + sizeof (kdd_hdr); + if (s->txp.h.dir == KDD_DIR_PKT) + /* Append the mysterious 0xaa byte to each packet */ + s->txb[len++] = 0xaa; + + (void) blocking_write(s->fd, s->txb, len); +} + + +/* Send an acknowledgement to the client */ +static void kdd_send_ack(kdd_state *s, uint32_t id, uint16_t type) +{ + s->txp.h.dir = KDD_DIR_ACK; + s->txp.h.type = type; + s->txp.h.len = 0; + s->txp.h.id = id; + s->txp.h.sum = 0; + kdd_tx(s); +} + +/* Send a command_packet to the client */ +static void kdd_send_cmd(kdd_state *s, uint32_t subtype, size_t extra) +{ + s->txp.h.dir = KDD_DIR_PKT; + s->txp.h.type = KDD_PKT_CMD; + s->txp.h.len = sizeof (kdd_cmd) + extra; + s->txp.h.id = (s->next_id ^= 1); + s->txp.h.sum = 0; + s->txp.cmd.subtype = subtype; + kdd_tx(s); +} + +/* Cause the client to print a string */ +static void kdd_send_string(kdd_state *s, char *fmt, ...) +{ + uint32_t len = 0xffff - sizeof (kdd_msg); + char *buf = (char *) s->txb + sizeof (kdd_hdr) + sizeof (kdd_msg); + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(buf, len, fmt, ap); + va_end(ap); + + s->txp.h.dir = KDD_DIR_PKT; + s->txp.h.type = KDD_PKT_MSG; + s->txp.h.len = sizeof (kdd_msg) + len; + s->txp.h.id = (s->next_id ^= 1); + s->txp.h.sum = 0; + s->txp.msg.subtype = KDD_MSG_PRINT; + s->txp.msg.length = len; + kdd_tx(s); +} + + +/* Stop the guest and prepare for debugging */ +static void kdd_break(kdd_state *s) +{ + uint16_t ilen; + KDD_LOG(s, "Break\n"); + + if (s->running) + kdd_halt(s->guest); + s->running = 0; + + { + unsigned int i; + /* XXX debug pattern */ + for (i = 0; i < 0x100 ; i++) + s->txb[sizeof (kdd_hdr) + i] = i; + } + + /* Send a state-change message to the client so it knows we've stopped */ + s->txp.h.dir = KDD_DIR_PKT; + s->txp.h.type = KDD_PKT_STC; + s->txp.h.len = sizeof (kdd_stc); + s->txp.h.id = (s->next_id ^= 1); + s->txp.stc.subtype = KDD_STC_STOP; + s->txp.stc.stop.cpu = s->cpuid; + s->txp.stc.stop.ncpus = kdd_count_cpus(s->guest); + s->txp.stc.stop.kthread = 0; /* Let the debugger figure it out */ + s->txp.stc.stop.status = KDD_STC_STATUS_BREAKPOINT; + s->txp.stc.stop.rip1 = s->txp.stc.stop.rip2 = kdd_get_ip(s); + s->txp.stc.stop.nparams = 0; + s->txp.stc.stop.first_chance = 1; + ilen = kdd_read_virtual(s, s->cpuid, s->txp.stc.stop.rip1, + sizeof s->txp.stc.stop.inst, s->txp.stc.stop.inst); + s->txp.stc.stop.ilen = ilen; + /* XXX other fields */ + + kdd_tx(s); +} + +/* Handle an acknowledgement received from the client */ +static void kdd_handle_ack(kdd_state *s, uint32_t id, uint16_t type) +{ + switch (type) { + case KDD_ACK_OK: + case KDD_ACK_BAD: + break; + case KDD_ACK_RST: + if (id == 0) { + KDD_LOG(s, "Client requests a reset\n"); + kdd_send_ack(s, 0xdeadbeef, KDD_ACK_RST); + kdd_send_string(s, "[kdd: connected to %s]\r\n", + kdd_guest_identify(s->guest)); + kdd_break(s); + } + break; + default: + KDD_LOG(s, "Unhandled ACK type 0x%4.4x\n", type); + break; + } +} + +/***************************************************************************** + * Handlers for each kind of client packet + */ + + +/* Handle the initial handshake */ +static void kdd_handle_handshake(kdd_state *s) +{ + /* Figure out what we're looking at */ + find_os(s); + kdd_send_string(s, "[kdd: %s @0x%"PRIx64"]\r\n", s->os.name, s->os.base); + + /* Respond with some details about the debugger stub we simulate */ + s->txp.cmd.shake.u1 = 0x01010101; + s->txp.cmd.shake.status = KDD_STATUS_SUCCESS; + s->txp.cmd.shake.u2 = 0x02020202; + s->txp.cmd.shake.v_major = 0xf; + s->txp.cmd.shake.v_minor = s->os.build; + s->txp.cmd.shake.proto = 6; + s->txp.cmd.shake.flags = (0x02 /* ??? */ + | (s->os.mp ? KDD_FLAGS_MP : 0) + | (s->os.w64 ? KDD_FLAGS_64 : 0)); + s->txp.cmd.shake.machine = s->os.w64 ? KDD_MACH_x64 : KDD_MACH_x32; + s->txp.cmd.shake.pkts = KDD_PKT_MAX; + s->txp.cmd.shake.states = 0xc; /* ??? */ + s->txp.cmd.shake.manips = 0x2e; /* ??? */ + s->txp.cmd.shake.u3[0] = 0x33; + s->txp.cmd.shake.u3[1] = 0x44; + s->txp.cmd.shake.u3[2] = 0x55; + s->txp.cmd.shake.kern_addr = s->os.base; + s->txp.cmd.shake.mods_addr = s->os.base + s->os.modules; + s->txp.cmd.shake.data_addr = 0; /* Debugger data probably doesn't exist */ + + KDD_LOG(s, "Client initial handshake: %s\n", s->os.name); + kdd_send_cmd(s, KDD_CMD_SHAKE, 0); +} + +/* Handle set-cpu command */ +static void kdd_handle_setcpu(kdd_state *s) +{ + KDD_LOG(s, "Switch to CPU %u\n", s->rxp.cmd.setcpu.cpu); + + /* This command doesn't get a direct response; instead we send a STOP. */ + s->cpuid = s->rxp.cmd.setcpu.cpu; + kdd_break(s); + + /* XXX find out whether kd will be happier if we respond to this command after the break. */ +} + +/* Handle breakpoint commands */ +static void kdd_handle_soft_breakpoint(kdd_state *s) +{ + KDD_LOG(s, "Soft breakpoint %#"PRIx32" op %#"PRIx32"/%#"PRIx32"\n", + s->rxp.cmd.sbp.bp, s->rxp.cmd.sbp.u1, s->rxp.cmd.sbp.u2); + + /* Pretend we did something */ + s->txp.cmd.sbp.u1 = s->rxp.cmd.sbp.u1; + s->txp.cmd.sbp.status = KDD_STATUS_SUCCESS; + s->txp.cmd.sbp.u2 = s->rxp.cmd.sbp.u2; + s->txp.cmd.sbp.bp = s->rxp.cmd.sbp.bp; + kdd_send_cmd(s, KDD_CMD_SOFT_BP, 0); +} + +static void kdd_handle_hard_breakpoint(kdd_state *s) +{ + KDD_LOG(s, "Hard breakpoint @%#"PRIx64"\n", s->rxp.cmd.hbp.address); + + kdd_send_string(s, "[kdd: breakpoints aren't implemented yet]\r\n"); + + s->txp.cmd.hbp.status = KDD_STATUS_FAILURE; + s->txp.cmd.hbp.address = s->rxp.cmd.hbp.address; + kdd_send_cmd(s, KDD_CMD_HARD_BP, 0); +} + +/* Register access */ +static void kdd_handle_read_regs(kdd_state *s) +{ + kdd_regs regs; + uint32_t len = s->os.w64 ? sizeof regs.r64 : sizeof regs.r32; + int cpuid = s->rxp.cmd.regs.cpu; + + KDD_LOG(s, "Read CPU %i register state\n", cpuid); + if (kdd_get_regs(s->guest, cpuid, ®s, s->os.w64) == 0) { + memcpy(s->txb + sizeof (kdd_hdr) + sizeof (kdd_cmd), ®s, len); + s->txp.cmd.regs.status = KDD_STATUS_SUCCESS; + } else { + len = 0; + s->txp.cmd.regs.status = KDD_STATUS_FAILURE; + } + s->txp.cmd.regs.cpu = cpuid; + kdd_send_cmd(s, KDD_CMD_READ_REGS, len); +} + +static void kdd_handle_write_regs(kdd_state *s) +{ + kdd_regs regs; + uint32_t len = s->rxp.h.len - sizeof (kdd_cmd); + uint32_t regsz = s->os.w64 ? sizeof regs.r64 : sizeof regs.r32; + int cpuid = s->rxp.cmd.regs.cpu; + + KDD_LOG(s, "Write CPU %i register state\n", cpuid); + s->txp.cmd.regs.status = KDD_STATUS_FAILURE; + if (len >= regsz) { + memcpy(®s, s->rxb + sizeof (kdd_hdr) + sizeof (kdd_cmd), regsz); + if (kdd_set_regs(s->guest, cpuid, ®s, s->os.w64) == 0) + s->txp.cmd.regs.status = KDD_STATUS_SUCCESS; + } + s->txp.cmd.regs.cpu = cpuid; + kdd_send_cmd(s, KDD_CMD_WRITE_REGS, 0); +} + +/* Report control state to the guest */ +static void kdd_handle_read_ctrl(kdd_state *s) +{ + int i; + kdd_ctrl ctrl; + uint8_t *buf = s->txb + sizeof (kdd_hdr) + sizeof (kdd_cmd); + uint32_t len = s->rxp.cmd.mem.length_req; + uint64_t val, addr = s->rxp.cmd.mem.addr; + KDD_LOG(s, "Read control state: %"PRIu32" bytes @ 0x%"PRIx64"\n", + len, addr); + + if (len > (65536 - sizeof(kdd_cmd))) + len = 65536 - sizeof(kdd_cmd); + + /* Default contents: a debug-friendly pattern */ + for (i = 0; i < len; i++) + ((uint8_t*)buf)[i] = (uint8_t) (addr + i); + + if (kdd_get_ctrl(s->guest, s->cpuid, &ctrl, s->os.w64)) { + len = 0; + } else if (s->os.w64) { + /* Annoyingly, 64-bit kd relies on the kernel to point it at + * datastructures it could easily find itself with VA reads. */ + switch (addr) { + case 0x0: /* KPCR */ + case 0x1: /* KPRCB */ + case 0x3: /* KTHREAD */ + /* First find the PCRB's address */ + len = kdd_read_virtual(s, s->cpuid, + s->os.base + s->os.prcbs + 8 * s->cpuid, + 8, &val); + if (len != 8) + break; + /* The PCR lives 0x180 bytes before the PRCB */ + if (addr == 0) + val -= 0x180; + /* The current thread's address is at offset 0x8 into the PRCB. */ + else if (addr == 3) + len = kdd_read_virtual(s, s->cpuid, val + 8, 8, &val); + *(uint64_t *)buf = val; + break; + case 0x2: /* Control registers */ + if (len > sizeof ctrl.c64) + len = sizeof ctrl.c64; + memcpy(buf, (uint8_t *)&ctrl, len); + break; + default: + KDD_LOG(s, "Unknown control space 0x%"PRIx64"\n", addr); + len = 0; + } + } else { + /* 32-bit control-register space starts at 0x[2]cc, for 84 bytes */ + uint64_t offset = addr; + if (offset > 0x200) + offset -= 0x200; + offset -= 0xcc; + if (offset > sizeof ctrl.c32 || offset + len > sizeof ctrl.c32) { + KDD_LOG(s, "Request outside of known control space\n"); + len = 0; + } else { + memcpy(buf, ((uint8_t *)&ctrl.c32) + offset, len); + } + } + + s->txp.cmd.mem.addr = addr; + s->txp.cmd.mem.length_req = s->rxp.cmd.mem.length_req; + s->txp.cmd.mem.length_rsp = len; + s->txp.cmd.mem.status = ((len) ? KDD_STATUS_SUCCESS : KDD_STATUS_FAILURE); + kdd_send_cmd(s, KDD_CMD_READ_CTRL, len); +} + +/* MSR access */ +static void kdd_handle_read_msr(kdd_state *s) +{ + uint32_t msr = s->rxp.cmd.msr.msr; + int ok; + KDD_LOG(s, "Read MSR 0x%"PRIx32"\n", msr); + + ok = (kdd_rdmsr(s->guest, s->cpuid, msr, &s->txp.cmd.msr.val) == 0); + s->txp.cmd.msr.msr = msr; + s->txp.cmd.msr.status = (ok ? KDD_STATUS_SUCCESS : KDD_STATUS_FAILURE); + kdd_send_cmd(s, KDD_CMD_READ_MSR, 0); +} + +static void kdd_handle_write_msr(kdd_state *s) +{ + uint32_t msr = s->rxp.cmd.msr.msr; + uint64_t val = s->rxp.cmd.msr.val; + int ok; + KDD_LOG(s, "Write MSR 0x%"PRIx32" = 0x%"PRIx64"\n", msr, val); + + ok = (kdd_wrmsr(s->guest, s->cpuid, msr, val) == 0); + s->txp.cmd.msr.msr = msr; + s->txp.cmd.msr.status = (ok ? KDD_STATUS_SUCCESS : KDD_STATUS_FAILURE); + kdd_send_cmd(s, KDD_CMD_WRITE_MSR, 0); +} + +/* Read and write guest memory */ +static void kdd_handle_memory_access(kdd_state *s) +{ + uint32_t len = s->rxp.cmd.mem.length_req; + uint64_t addr = s->rxp.cmd.mem.addr; + uint8_t *buf; + + KDD_LOG(s, "Memory access \"%c%c\" (%s): %"PRIu32" bytes" + " @ 0x%"PRIx64"\n", + s->rxp.cmd.subtype & 0xff, (s->rxp.cmd.subtype >>8) & 0xff, + s->rxp.cmd.subtype == KDD_CMD_READ_VA ? "read virt" : + s->rxp.cmd.subtype == KDD_CMD_WRITE_VA ? "write virt" : + s->rxp.cmd.subtype == KDD_CMD_READ_PA ? "read phys" : + s->rxp.cmd.subtype == KDD_CMD_WRITE_PA ? "write phys" : "unknown", + len, addr); + + if (len > (65536 - sizeof(kdd_cmd))) + len = 65536 - sizeof(kdd_cmd); + + switch(s->rxp.cmd.subtype) { + case KDD_CMD_READ_VA: + buf = s->txb + sizeof (kdd_hdr) + sizeof (kdd_cmd); + len = kdd_read_virtual(s, s->cpuid, addr, len, buf); + break; + case KDD_CMD_WRITE_VA: + buf = s->rxb + sizeof (kdd_hdr) + sizeof (kdd_cmd); + len = kdd_write_virtual(s, s->cpuid, addr, len, buf); + break; + case KDD_CMD_READ_PA: + buf = s->txb + sizeof (kdd_hdr) + sizeof (kdd_cmd); + len = kdd_read_physical(s, addr, len, buf); + break; + case KDD_CMD_WRITE_PA: + buf = s->rxb + sizeof (kdd_hdr) + sizeof (kdd_cmd); + len = kdd_write_physical(s, addr, len, buf); + break; + } + KDD_DEBUG(s, "access returned %"PRIu32"\n", len); + + s->txp.cmd.mem.addr = addr; + s->txp.cmd.mem.length_req = s->rxp.cmd.mem.length_req; + s->txp.cmd.mem.length_rsp = len; + s->txp.cmd.mem.status = (len) ? KDD_STATUS_SUCCESS : KDD_STATUS_FAILURE; + kdd_send_cmd(s, s->rxp.cmd.subtype, len); +} + + +/* Handle a packet received from the client */ +static void kdd_handle_pkt(kdd_state *s, kdd_pkt *p) +{ + uint32_t sum = 0; + int i; + + /* Simple checksum: add all the bytes */ + for (i = 0; i < p->h.len; i++) + sum += p->payload[i]; + if (p->h.sum != sum) { + kdd_send_ack(s, p->h.id, KDD_ACK_BAD); + return; + } + + /* We only understand one kind of packet from the client */ + if (p->h.type != KDD_PKT_CMD) { + KDD_LOG(s, "Unhandled PKT type 0x%4.4x\n", p->h.type); + kdd_send_ack(s, p->h.id, KDD_ACK_BAD); + return; + } + + /* Ack the packet */ + kdd_send_ack(s, p->h.id, KDD_ACK_OK); + + /* Clear the TX buffer just for sanity */ + memset(s->txb, 0, sizeof(s->txb)); + + switch (p->cmd.subtype) { + case KDD_CMD_CONT1: + case KDD_CMD_CONT2: + KDD_LOG(s, "Continue: 0x%8.8"PRIx32"\n", p->cmd.cont.reason1); + if (!s->running) + kdd_run(s->guest); + s->running = 1; + /* No reply, just carry on running */ + break; + case KDD_CMD_SHAKE: + kdd_handle_handshake(s); + break; + case KDD_CMD_SOFT_BP: + kdd_handle_soft_breakpoint(s); + break; + case KDD_CMD_HARD_BP: + kdd_handle_hard_breakpoint(s); + break; + case KDD_CMD_READ_REGS: + kdd_handle_read_regs(s); + break; + case KDD_CMD_WRITE_REGS: + kdd_handle_write_regs(s); + break; + case KDD_CMD_READ_CTRL: + kdd_handle_read_ctrl(s); + break; + case KDD_CMD_READ_MSR: + kdd_handle_read_msr(s); + break; + case KDD_CMD_WRITE_MSR: + kdd_handle_write_msr(s); + break; + case KDD_CMD_READ_VA: + case KDD_CMD_WRITE_VA: + case KDD_CMD_READ_PA: + case KDD_CMD_WRITE_PA: + kdd_handle_memory_access(s); + break; + case KDD_CMD_WRITE_Z: + /* No response */ + break; + case KDD_CMD_SETCPU: + kdd_handle_setcpu(s); + break; + case KDD_CMD_WRITE_CTRL: + default: + KDD_LOG(s, "Unhandled CMD subtype 0x%8.8x\n", p->cmd.subtype); + /* Send back a mirror of the request saying we failed to do + * whatever it was. */ + memcpy(s->txb, p, sizeof (kdd_hdr) + sizeof (kdd_cmd)); + s->txp.h.len = sizeof (kdd_cmd); + s->txp.cmd.mem.status = KDD_STATUS_FAILURE; + s->txp.h.id = (s->next_id ^= 1); + kdd_tx(s); + break; + } +} + + +/***************************************************************************** + * Scaffolding to get packets from the client. + */ + + +/* Set up the debugger state ready for use. Returns a file descriptor and + * a state pointer for use in select() loops. */ +static int kdd_init(kdd_state **sp, struct addrinfo *addr, + kdd_guest *guest, FILE *log, int verbosity) +{ + kdd_state *s = NULL; + int opt, fd = -1; + + s = malloc(sizeof *s); + if (s == NULL) { + fprintf(stderr, "Could not allocate state for kdd: %s\n", + strerror(errno)); + goto fail; + } + memset(s, 0, sizeof *s); + s->log = log; + s->verbosity = verbosity; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + KDD_LOG(s, "Could not open a socket for kdd: %s\n", + strerror(errno)); + goto fail; + } + + /* Try to connect to the tcp/serial gateway. */ + again: + if (connect(fd, addr->ai_addr, sizeof *addr) != 0) { + if (errno == EINTR) + goto again; + if (addr->ai_next) { + addr = addr->ai_next; + goto again; + } + KDD_LOG(s, "Could not connect TCP stream for kdd: %s\n", + strerror(errno)); + goto fail; + } + + opt = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + + s->next_id = 0x80800001; + s->fd = fd; + s->running = 1; + s->cpuid = 0; + s->guest = guest; + s->os = unknown_os; + + *sp = s; + KDD_LOG(s, "KDD starts\n"); + + kdd_break(s); + + return fd; + + fail: + if (fd >= 0) + close(fd); + free(s); + return -1; +} + +/* Callback when the fd is readable, to parse packet data from the byte + * stream. When a complete packet is seen, handle it. The packet can + * then be read in the marshalling buffer, but only until the next call + * to kdd_parse_byte(). */ +void kdd_select_callback(kdd_state *s) +{ + kdd_pkt *p = &s->rxp; + unsigned int pkt_len = (unsigned) -1; + ssize_t rc, to_read; + + /* For easy parsing, read single bytes until we can check the packet + * length, then read in one go to the end. */ + if (s->cur < 8 + || (p->h.dir != KDD_DIR_PKT && p->h.dir != KDD_DIR_ACK)) + to_read = 1; + else { + /* Extract payload length from the header */ + pkt_len = p->h.len + sizeof (kdd_hdr); + + /* For some reason, packets always have a trailing 0xAA byte */ + if (p->h.dir == KDD_DIR_PKT) + pkt_len++; + + to_read = pkt_len - s->cur; + } + + rc = read(s->fd, s->rxb + s->cur, to_read); + + KDD_DEBUG(s, "read(%i) returns %i\n", (int) to_read, (int) rc); + + if (rc <= 0) + /* XXX ignoring failures for now */ + return; + + /* Break command comes as a single byte */ + if (s->cur == 0 && s->rxb[0] == 'b') { + kdd_break(s); + return; + } + + /* Remember the bytes we just read */ + s->cur += rc; + + /* Sync to packet start, which will be "0000" or "iiii" */ + if (s->cur < 4) + return; + if (p->h.dir != KDD_DIR_PKT && p->h.dir != KDD_DIR_ACK) { + KDD_LOG(s, "Bad hdr 0x%8.8x: resyncing\n", p->h.dir); + memmove(s->rxb, s->rxb + 1, --s->cur); + return; + } + + /* Process complete packets/acks */ + if (s->cur >= pkt_len) { + kdd_log_pkt(s, "RX", p); + if (p->h.dir == KDD_DIR_PKT) + kdd_handle_pkt(s, p); + else + kdd_handle_ack(s, p->h.id, p->h.type); + s->cur = 0; + } +} + + +static void usage(void) +{ + fprintf(stderr, +" usage: kdd [-v]
\n" +" \n" +" Makes a TCP connection to
: and speaks the kd serial\n" +" protocol over it, to debug Xen domain .\n" +" To connect a debugger, set up a Windows VM with it serial port confgured\n" +" as \"serial='tcp:
:,server,nodelay,nowait'\". Run\n" +" windbg or kd in that VM, connecting to COM1; then run kdd.\n\n"); + exit(1); +} + + +int main(int argc, char **argv) +{ + int fd; + int verbosity = 0; + kdd_state *s; + kdd_guest *g; + struct addrinfo *addr; + fd_set fds; + + while (argc > 4) + if (!strcmp(argv[1], "-v")) { + verbosity++; + argc--; + argv++; + } + + if (argc != 4 + || !(g = kdd_guest_init(argv[1], stdout, verbosity)) + || getaddrinfo(argv[2], argv[3], NULL, &addr) != 0 + || (fd = kdd_init(&s, addr, g, stdout, verbosity)) < 0) + usage(); + + while (1) { + FD_ZERO(&fds); + FD_SET(fd, &fds); + if (select(fd + 1, &fds, NULL, NULL, NULL) > 0) + kdd_select_callback(s); + } + + return 0; +} diff --git a/tools/debugger/kdd/kdd.h b/tools/debugger/kdd/kdd.h new file mode 100644 index 0000000000..bfb00ba5c5 --- /dev/null +++ b/tools/debugger/kdd/kdd.h @@ -0,0 +1,520 @@ +/* + * kdd.h -- Structures, constants and descriptions of the Windows + * kd serial debugger protocol, for the kdd debugging stub. + * + * Tim Deegan + * + * Copyright (c) 2007-2010, Citrix Systems Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _KDD_H_ +#define _KDD_H_ + +#include + +#define PACKED __attribute__((packed)) + +/***************************************************************************** + * Serial line protocol: Sender sends a 16-byte header with an optional + * payload following it. Receiver responds to each packet with an + * acknowledgment (16-byte header only). + * + * Packet headers start with ASCII "0000" and there is a trailing byte + * 0xAA after the (optional) payload. Ack headers start with ASCII + * "iiii"; no trailing byte). Each packet and ack has a major type in + * the packet header; for packets with payload, a minor type is encoded + * in ASCII in the first four bytes of the payload. + * + * Packet IDs seem to start at 0x80800000 and alternate between that and + * 0x80800001; not clear whether the client's ID is always the ID of the + * last packet from the kernel or whether they're just oscillating in + * phase. Either way there's clearly some state machine in the kernel + * that requires this exact behaviour from the client. + * + * All acks have length 0, id = id of the packet they ack. + */ + +#define KDD_DIR_PKT 0x30303030 /* "0000" */ +#define KDD_DIR_ACK 0x69696969 /* "iiii" */ + +typedef struct { + uint32_t dir; /* KDD_DIR_PKT or KDD_DIR_ACK */ + uint16_t type; /* Major type. */ + uint16_t len; /* Payload length, excl. header and trailing byte */ + uint32_t id; /* Echoed in responses */ + uint32_t sum; /* Unsigned sum of all payload bytes */ + uint8_t payload[0]; +} PACKED kdd_hdr; + +#define KDD_PKT_CMD 0x0002 /* Debugger commands (and replies to them) */ +#define KDD_PKT_MSG 0x0003 /* Kernel messages for the user */ +#define KDD_PKT_STC 0x0007 /* State change notification */ +#define KDD_PKT_REG 0x000b /* Registry change notification (?) */ +#define KDD_PKT_MAX 0x000b + +#define KDD_ACK_OK 0x0004 /* Checksum, ID and type all fine */ +#define KDD_ACK_BAD 0x0005 /* Something is bogus */ +#define KDD_ACK_RST 0x0006 /* Not really an ack; one each way to resync */ + + +/***************************************************************************** + * Debugger commands, carried over the serial line. In this protocol, + * we ignore the serial-level acking; when we talk about a response, + * it's another packet, sent after the request was acked, and which will + * itself be acked. + * + * The debugger client sends commands to the kernel, all of which have + * major type 2 and are 56 bytes long (not including the serial header). + * Not all the 56 bytes are used in every command, but the client + * doesn't bother to zero unused fields. Most commands are responded to + * by a packet with the same subtype, containing at least a status code + * to indicate success or failure. + */ + +#define KDD_STATUS_SUCCESS 0x00000000 +#define KDD_STATUS_FAILURE 0xc0000001 +#define KDD_STATUS_PENDING 0x00000103 + +/* Memory access. Read commands are echoed in the response with the + * status and length_rsp fields updated, and the read data appended to the + * packet. Writes are the same, but with the data appended to the + * write command, not the response. */ + +#define KDD_CMD_READ_VA 0x00003130 /* "01" */ +#define KDD_CMD_WRITE_VA 0x00003131 /* "11" */ +#define KDD_CMD_READ_CTRL 0x00003137 /* "71" */ +#define KDD_CMD_WRITE_CTRL 0x00003138 /* "81" */ +#define KDD_CMD_READ_PA 0x0000313D /* "=1" */ +#define KDD_CMD_WRITE_PA 0x0000313E /* ">1" */ + +/* Not sure what this is, but it doesn't require a response */ +#define KDD_CMD_WRITE_Z 0x0000315A /* "Z1" */ + +typedef struct { + uint32_t u1; + uint32_t status; /* IN: STATUS_PENDING; OUT: result status. */ + uint32_t u2; + uint64_t addr; /* IN: address of start of read/write */ + uint32_t length_req; /* IN: bytes to read/write */ + uint32_t length_rsp; /* OUT: bytes successfully read/written */ +} PACKED kdd_cmd_mem; + +/* CPU register access. As for memory accesses, but the data is a + * fixed-length block of register info. */ + +#define KDD_CMD_READ_REGS 0x00003132 /* "21" */ +#define KDD_CMD_WRITE_REGS 0x00003133 /* "31" */ + +typedef struct { + uint16_t u1; + uint16_t cpu; /* IN: Zero-based processor ID */ + uint32_t status; /* IN: STATUS_PENDING; OUT: result status. */ +} PACKED kdd_cmd_regs; + +#define KDD_CMD_READ_MSR 0x00003152 /* "R1" */ +#define KDD_CMD_WRITE_MSR 0x00003153 /* "S1" */ + +typedef struct { + uint32_t u1; + uint32_t status; /* IN: STATUS_PENDING; OUT: result status. */ + uint32_t u2; + uint32_t msr; /* IN/OUT: MSR number */ + uint64_t val; /* IN/OUT: MSR contents */ +} PACKED kdd_cmd_msr; + +/* Breakpoint commands. */ + +#define KDD_CMD_SOFT_BP 0x00003135 /* "51" */ + +typedef struct { + uint32_t u1; + uint32_t status; /* IN: STATUS_PENDING; OUT: result status. */ + uint32_t u2; + uint32_t bp; /* IN: ID of breakpoint to operate on */ +} PACKED kdd_cmd_soft_bp; + +#define KDD_CMD_HARD_BP 0x0000315C /* "\1" */ + +typedef struct { + uint32_t u1; + uint32_t status; /* IN: STATUS_PENDING; OUT: result status. */ + uint32_t u2; + uint64_t address; /* IN: Address to trap on */ + uint64_t u3; + uint64_t u4; + uint64_t u5; + uint64_t u6; +} PACKED kdd_cmd_hard_bp; + +/* Flow control commands. These commands are _not_ responded to. */ + +#define KDD_CMD_CONT1 0x00003136 /* "61" */ +#define KDD_CMD_CONT2 0x0000313c /* "<1" */ + +#define KDD_DBG_EXCEPTION_HANDLED 0x00010001 +#define KDD_DBG_CONTINUE 0x00010002 + +typedef struct { + uint32_t u1; + uint32_t reason1; /* IN: KDD_DBG_* */ + uint32_t u2; + uint64_t reason2; /* IN: always same as reason1 */ +} PACKED kdd_cmd_cont; + +/* Handshake command. */ + +#define KDD_CMD_SHAKE 0x00003146 /* "F1" */ + +#define KDD_MACH_x32 0x014c +#define KDD_MACH_x64 0x8664 + +#define KDD_FLAGS_MP 0x0001 +#define KDD_FLAGS_64 0x0008 + +typedef struct { + uint32_t u1; + uint32_t status; /* IN: STATUS_PENDING; OUT: result status. */ + uint32_t u2; + uint16_t v_major; /* OUT: OS major version (0xf for NT) */ + uint16_t v_minor; /* OUT: OS minor version (NT build number) */ + uint16_t proto; /* OUT: Protocol version (6) */ + uint16_t flags; /* OUT: Some flags (at least 0x3) */ + uint16_t machine; /* OUT: Machine type */ + uint8_t pkts; /* OUT: Number of packet types understood */ + uint8_t states; /* OUT: Number of state-change types used */ + uint8_t manips; /* OUT: number of "manipulation" types used */ + uint8_t u3[3]; + int64_t kern_addr; /* OUT: KernBase */ + int64_t mods_addr; /* OUT: PsLoadedModuleList */ + int64_t data_addr; /* OUT: DebuggerDataList */ +} PACKED kdd_cmd_shake; + +/* Change active CPU. This command is _not_ responded to */ + +#define KDD_CMD_SETCPU 0x00003150 /* "P1" */ + +typedef struct { + uint16_t u1; + uint16_t cpu; /* IN: Zero-based processor ID */ + uint32_t status; /* IN: STATUS_PENDING */ +} PACKED kdd_cmd_setcpu; + +typedef struct { + uint32_t subtype; /* IN: KDD_CMD_x */ + union { + kdd_cmd_mem mem; + kdd_cmd_regs regs; + kdd_cmd_msr msr; + kdd_cmd_soft_bp sbp; + kdd_cmd_hard_bp hbp; + kdd_cmd_cont cont; + kdd_cmd_shake shake; + kdd_cmd_setcpu setcpu; + uint8_t pad[52]; + }; + uint8_t data[0]; +} PACKED kdd_cmd; + + +/***************************************************************************** + * Kernel messages to the debugger. The debugger does not respond to these + * beyond ACKing them and printing approprate things on the debugger + * console. + */ + +/* Messages for the console */ + +#define KDD_MSG_PRINT 0x00003230 /* "02" */ + +typedef struct { + uint32_t subtype; /* KDD_MSG_PRINT */ + uint32_t u1; + uint32_t length; /* Length in bytes of trailing string */ + uint32_t u2; + uint8_t string[0]; /* Non-terminated character string */ +} PACKED kdd_msg; + +/* Registry updates (Hive loads?) */ + +#define KDD_REG_CHANGE 0x00003430 /* "04" */ + +typedef struct { + uint32_t subtype; /* KDD_REG_CHANGE */ + uint32_t u1[15]; + uint16_t string[0]; /* Null-terminated wchar string */ +} PACKED kdd_reg; + +/* State changes. After sending a state-change message the kernel halts + * until it receives a continue command from the debugger. */ + +#define KDD_STC_STOP 0x00003030 /* "00" : Bug-check */ +#define KDD_STC_LOAD 0x00003031 /* "01" : Loaded a module */ + +#define KDD_STC_STATUS_BREAKPOINT 0x80000003 + +typedef struct { + uint16_t u1; + uint16_t cpu; /* Zero-based processor ID */ + uint32_t ncpus; /* Number of processors */ + uint32_t u2; + int64_t kthread; /* Kernel thread structure */ + int64_t rip1; /* Instruction pointer, sign-extended */ + uint64_t status; /* KDD_STC_STATUS_x */ + uint64_t u3; + int64_t rip2; /* Same as rip1 */ + uint64_t nparams; /* Number of stopcode parameters */ + uint64_t params[15]; /* Stopcode parameters */ + uint64_t first_chance; /* OS exn handlers not yet been run? */ + uint32_t u4[2]; + uint32_t ilen; /* Number of bytes of instruction following */ + uint8_t inst[36]; /* VA contents from %eip onwards */ +} PACKED kdd_stc_stop; + +typedef struct { + uint32_t u1[3]; + uint64_t u2; + uint64_t rip; /* Instruction pointer, sign-extended */ + uint64_t u3[26]; + uint8_t path[0]; /* Null-terminated ASCII path to loaded mod. */ +} PACKED kdd_stc_load; + +typedef struct { + uint32_t subtype; /* KDD_STC_x */ + union { + kdd_stc_stop stop; + kdd_stc_load load; + }; +} PACKED kdd_stc; + + +/***************************************************************************** + * Overall packet type + */ + +typedef struct { + kdd_hdr h; /* Major type disambiguates union below */ + union { + kdd_cmd cmd; + kdd_msg msg; + kdd_reg reg; + kdd_stc stc; + uint8_t payload[0]; + }; +} PACKED kdd_pkt; + + +/***************************************************************************** + * Processor state layouts + */ + +/* User-visible register files */ +typedef union { + uint32_t pad[179]; + struct { + uint32_t u1[7]; /* Flags, DRx?? */ + uint8_t fp[112]; /* FP save state (why 112 not 108?) */ + int32_t gs; + int32_t fs; + int32_t es; + int32_t ds; + int32_t edi; + int32_t esi; + int32_t ebx; + int32_t edx; + int32_t ecx; + int32_t eax; + int32_t ebp; + int32_t eip; + int32_t cs; + int32_t eflags; + int32_t esp; + int32_t ss; + uint32_t sp2[37]; /* More 0x20202020. fp? */ + uint32_t sp3; /* 0x00202020 */ + }; +} PACKED kdd_regs_x86_32; + +typedef union { + uint64_t pad[154]; + struct { + + uint64_t u1[7]; + + uint16_t cs; //2*1c + uint16_t ds; + uint16_t es; + uint16_t fs; + uint16_t gs; + uint16_t ss; + uint32_t rflags; + uint64_t dr0; + uint64_t dr1; + uint64_t dr2; + uint64_t dr3; + uint64_t dr6; + uint64_t dr7; + int64_t rax; + int64_t rcx; + int64_t rdx; + int64_t rbx; + int64_t rsp; + int64_t rbp; + int64_t rsi; + int64_t rdi; + int64_t r8; + int64_t r9; + int64_t r10; + int64_t r11; + int64_t r12; + int64_t r13; + int64_t r14; + int64_t r15; + int64_t rip; //2*7c + + uint64_t u2[32]; + + uint8_t fp[512]; // fp @2*100 .. 150 (+ more??) + + uint64_t u3[26]; + }; +} PACKED kdd_regs_x86_64; + +typedef union { + kdd_regs_x86_32 r32; + kdd_regs_x86_64 r64; +} PACKED kdd_regs; + +/* System registers */ +typedef struct { + uint32_t cr0; + uint32_t cr2; + uint32_t cr3; + uint32_t cr4; + uint32_t dr0; + uint32_t dr1; + uint32_t dr2; + uint32_t dr3; + uint32_t dr6; + uint32_t dr7; + uint16_t gdt_pad; + uint16_t gdt_limit; + uint32_t gdt_base; + uint16_t idt_pad; + uint16_t idt_limit; + uint32_t idt_base; + uint16_t tss_sel; + uint16_t ldt_sel; + uint8_t u1[24]; +} PACKED kdd_ctrl_x86_32; + +typedef struct { + uint64_t cr0; + uint64_t cr2; + uint64_t cr3; + uint64_t cr4; + uint64_t dr0; + uint64_t dr1; + uint64_t dr2; + uint64_t dr3; + uint64_t dr6; + uint64_t dr7; + uint8_t gdt_pad[6]; + uint16_t gdt_limit; + uint64_t gdt_base; + uint8_t idt_pad[6]; + uint16_t idt_limit; + uint64_t idt_base; + uint16_t tss_sel; + uint16_t ldt_sel; + uint8_t u1[44]; + uint64_t cr8; + uint8_t u2[40]; + uint64_t efer; // XXX find out where EFER actually goes +} PACKED kdd_ctrl_x86_64; + +typedef union { + kdd_ctrl_x86_32 c32; + kdd_ctrl_x86_64 c64; +} kdd_ctrl; + +/***************************************************************************** + * Functions required from the emulator/hypervisor for the stub to work. + */ + +typedef struct kdd_guest kdd_guest; + +/* Init and teardown guest-specific state */ +extern kdd_guest *kdd_guest_init(char *arg, FILE *log, int verbosity); +extern void kdd_guest_teardown(kdd_guest *g); +extern char *kdd_guest_identify(kdd_guest *g); + +/* Halt and restart the running guest */ +extern void kdd_halt(kdd_guest *g); +extern void kdd_run(kdd_guest *g); + +/* How many CPUs are there? */ +extern int kdd_count_cpus(kdd_guest *g); + +/* Accessor for guest physical memory, returning bytes read/written */ +extern uint32_t kdd_access_physical(kdd_guest *g, uint64_t addr, + uint32_t len, uint8_t *buf, int write); + +/* Accessors for guest registers, returning 0 for success */ +extern int kdd_get_regs(kdd_guest *g, int cpuid, kdd_regs *r, int w64); +extern int kdd_set_regs(kdd_guest *g, int cpuid, kdd_regs *r, int w64); + +/* Accessors for guest control registers, returning 0 for success */ +extern int kdd_get_ctrl(kdd_guest *g, int cpuid, kdd_ctrl *ctrl, int w64); +extern int kdd_set_ctrl(kdd_guest *g, int cpuid, kdd_ctrl *ctrl, int w64); + +/* Accessors for guest MSRs, returning 0 for success */ +extern int kdd_wrmsr(kdd_guest *g, int cpuid, uint32_t msr, uint64_t value); +extern int kdd_rdmsr(kdd_guest *g, int cpuid, uint32_t msr, uint64_t *value); + + +/***************************************************************************** + * Logfile usefulness + */ + +/* Verbosity: + * 0: errors (default) + * 1: operations + * 2: packets + * 3: _everything_ */ + +#define KDD_LOG_IF(_v, _s, _fmt, _a...) do { \ + if ((_s)->verbosity >= (_v)) { \ + fprintf((_s)->log, (_fmt), ##_a); \ + (void) fflush((_s)->log); \ + } \ +} while (0) + +#define KDD_LOG(_s, _fmt, _a...) KDD_LOG_IF(1, (_s), (_fmt), ##_a) +#define KDD_DEBUG(_s, _fmt, _a...) KDD_LOG_IF(3, (_s), (_fmt), ##_a) + +#endif /* _KDD_H_ */ -- cgit v1.2.3