Goto sanos source index
//
// dbg.c
//
// Remote debugging support
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. 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.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// 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 OWNER 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 <os/krnl.h>
#define DEBUGPORT 0x3F8 // COM1
#define MAX_DBG_CHUNKSIZE PAGESIZE
#define MAX_DBG_PACKETLEN (MAX_DBG_CHUNKSIZE + sizeof(union dbg_body))
extern char *trapnames[]; // Defined in trap.c
int debugging = 0;
int debugger_active = 0;
int debug_nointr = 0;
static char dbgdata[MAX_DBG_PACKETLEN];
struct dbg_evt_trap last_trap;
static char *krnlname = "krnl.dll";
static void init_debug_port() {
// Turn off interrupts
outp(DEBUGPORT + 1, 0);
// Set 115200 baud, 8 bits, no parity, one stopbit
outp(DEBUGPORT + 3, 0x80);
outp(DEBUGPORT + 0, 0x01); // 0x0C = 9600, 0x01 = 115200
outp(DEBUGPORT + 1, 0x00);
outp(DEBUGPORT + 3, 0x03);
outp(DEBUGPORT + 2, 0xC7);
outp(DEBUGPORT + 4, 0x0B);
}
static void dbg_send(void *buffer, int count) {
unsigned char *p = buffer;
while (count-- > 0) {
while ((inp(DEBUGPORT + 5) & 0x20) == 0) {
if (!debug_nointr) check_dpc_queue();
}
outp(DEBUGPORT, *p++);
}
}
static void dbg_recv(void *buffer, int count) {
unsigned char *p = buffer;
while (count-- > 0) {
while ((inp(DEBUGPORT + 5) & 0x01) == 0) {
if (!debug_nointr) check_dpc_queue();
}
*p++ = inp(DEBUGPORT) & 0xFF;
}
}
static void dbg_send_rle(void *data, unsigned int len) {
unsigned char *p = (unsigned char *) data;
unsigned char *q = p;
unsigned int left = len;
while (left > 0) {
if (p[0] == DBG_RLE_ESCAPE || (left > 3 && p[0] == p[1] && p[0] == p[2])) {
unsigned char buf[3];
if (p > q) {
dbg_send(q, p - q);
q = p;
}
while (left > 0 && q - p < 256 && q[0] == *p) {
q++;
left--;
}
buf[0] = DBG_RLE_ESCAPE;
buf[1] = *p;
buf[2] = (unsigned char) (q - p);
dbg_send(buf, 3);
p = q;
} else {
p++;
left--;
}
}
if (p > q) dbg_send(q, p - q);
}
static void dbg_recv_rle(void *data, unsigned int len) {
unsigned char *p = (unsigned char *) data;
unsigned int left = len;
while (left > 0) {
unsigned char ch;
dbg_recv(&ch, 1);
if (ch == DBG_RLE_ESCAPE) {
unsigned char value;
unsigned char count;
int n;
dbg_recv(&value, 1);
dbg_recv(&count, 1);
n = (count == 0) ? 256 : count;
while (n > 0) {
*p++ = value;
left--;
n--;
}
} else {
*p++ = ch;
left--;
}
}
}
static void dbg_send_packet(int cmd, unsigned char id, void *data, unsigned int len) {
unsigned int n;
struct dbg_hdr hdr;
unsigned char checksum;
unsigned char *p;
hdr.signature = DBG_SIGNATURE;
hdr.cmd = (unsigned char) cmd;
hdr.len = len;
hdr.id = id;
hdr.checksum = 0;
checksum = 0;
p = (unsigned char *) &hdr;
for (n = 0; n < sizeof(struct dbg_hdr); n++) checksum += *p++;
p = (unsigned char *) data;
for (n = 0; n < len; n++) checksum += *p++;
hdr.checksum = -checksum;
dbg_send(&hdr, sizeof(struct dbg_hdr));
dbg_send_rle(data, len);
}
static void dbg_send_error(unsigned char errcode, unsigned char id) {
dbg_send_packet(errcode, id, NULL, 0);
}
static int dbg_recv_packet(struct dbg_hdr *hdr, void *data) {
unsigned int n;
unsigned char checksum;
unsigned char *p;
while (1) {
dbg_recv(&hdr->signature, 1);
if (hdr->signature == DBG_SIGNATURE) break;
}
dbg_recv(&hdr->cmd, 1);
dbg_recv(&hdr->id, 1);
dbg_recv(&hdr->checksum, 1);
dbg_recv((unsigned char *) &hdr->len, 4);
if (hdr->len > MAX_DBG_PACKETLEN) return -EBUF;
dbg_recv_rle(data, hdr->len);
checksum = 0;
p = (unsigned char *) hdr;
for (n = 0; n < sizeof(struct dbg_hdr); n++) checksum += *p++;
p = (unsigned char *) data;
for (n = 0; n < hdr->len; n++) checksum += *p++;
if (checksum != 0) return -EIO;
return hdr->len;
}
static void dbg_connect(struct dbg_hdr *hdr, union dbg_body *body) {
struct thread *t = self();
if (body->conn.version != DRPC_VERSION) {
dbg_send_error(DBGERR_VERSION, hdr->id);
} else {
body->conn.version = DRPC_VERSION;
body->conn.trap = last_trap;
body->conn.mod.hmod = (hmodule_t) OSBASE;
body->conn.mod.name = &krnlname;
body->conn.thr.tid = t->id;
body->conn.thr.tib = t->tib;
body->conn.thr.tcb = t;
body->conn.thr.startaddr = t->entrypoint;
body->conn.cpu = cpu;
dbg_send_packet(hdr->cmd + DBGCMD_REPLY, hdr->id, body, sizeof(struct dbg_connect));
}
}
static void dbg_read_memory(struct dbg_hdr *hdr, union dbg_body *body) {
if (!mem_access(body->mem.addr, body->mem.size, PT_PRESENT)) {
dbg_send_error(DBGERR_INVALIDADDR, hdr->id);
} else {
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body->mem.addr, body->mem.size);
}
}
static void dbg_write_memory(struct dbg_hdr *hdr, union dbg_body *body) {
if (!mem_access(body->mem.addr, body->mem.size, PT_PRESENT | PT_WRITABLE)) {
dbg_send_error(DBGERR_INVALIDADDR, hdr->id);
} else {
memcpy(body->mem.addr, body->mem.data, body->mem.size);
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, NULL, 0);
}
}
static void dbg_suspend_thread(struct dbg_hdr *hdr, union dbg_body *body) {
int n;
tid_t tid;
struct thread *t;
for (n = 0; n < body->thr.count; n++) {
tid = body->thr.threadids[n];
t = get_thread(tid);
if (t == NULL) {
body->thr.threadids[n] = -ENOENT;
} else if (t != self()) {
body->thr.threadids[n] = suspend_thread(t);
}
}
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body, hdr->len);
}
static void dbg_resume_thread(struct dbg_hdr *hdr, union dbg_body *body) {
int n;
tid_t tid;
struct thread *t;
for (n = 0; n < body->thr.count; n++) {
tid = body->thr.threadids[n];
t = get_thread(tid);
if (t == NULL) {
body->thr.threadids[n] = -ENOENT;
} else if (t != self()) {
body->thr.threadids[n] = resume_thread(t);
}
}
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body, hdr->len);
}
static void dbg_get_thread_context(struct dbg_hdr *hdr, union dbg_body *body) {
struct thread *t;
t = get_thread(body->ctx.tid);
if (!t) {
dbg_send_error(DBGERR_INVALIDTHREAD, hdr->id);
return;
}
if (t->ctxt) {
memcpy(&body->ctx.ctxt, t->ctxt, sizeof(struct context));
// DS and ES are 16 bit register, clear upper bits
body->ctx.ctxt.ds &= 0xFFFF;
body->ctx.ctxt.es &= 0xFFFF;
// Kernel mode contexts does not have ss:esp in context, fixup
if (KERNELSPACE(body->ctx.ctxt.eip)) {
body->ctx.ctxt.ess = SEL_KDATA | mach.kring;
body->ctx.ctxt.esp = (unsigned long) t->ctxt + sizeof(struct context) - 8;
}
} else {
// Build kernel mini context
struct tcb *tcb= (struct tcb *) t;
struct kernel_context *kctxt = (struct kernel_context *) tcb->esp;
memset(&body->ctx.ctxt, 0, sizeof(struct context));
body->ctx.ctxt.esi = kctxt->esi;
body->ctx.ctxt.edi = kctxt->edi;
body->ctx.ctxt.ebx = kctxt->ebx;
body->ctx.ctxt.ebp = kctxt->ebp;
body->ctx.ctxt.eip = kctxt->eip;
body->ctx.ctxt.esp = (unsigned long) kctxt->stack;
body->ctx.ctxt.ds = SEL_KDATA | mach.kring;
body->ctx.ctxt.es = SEL_KDATA | mach.kring;
body->ctx.ctxt.ess = SEL_KDATA | mach.kring;
body->ctx.ctxt.ecs = SEL_KTEXT | mach.kring;
}
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body, sizeof(struct dbg_context));
}
static void dbg_set_thread_context(struct dbg_hdr *hdr, union dbg_body *body) {
struct thread *t;
t = get_thread(body->ctx.tid);
if (!t) {
dbg_send_error(DBGERR_INVALIDTHREAD, hdr->id);
return;
}
if (!t->ctxt) {
dbg_send_error(DBGERR_NOCONTEXT, hdr->id);
return;
}
// Kernel mode contexts does not have ss:esp in context
if (KERNELSPACE(body->ctx.ctxt.eip)) {
memcpy(t->ctxt, &body->ctx.ctxt, sizeof(struct context) - 8);
} else {
memcpy(t->ctxt, &body->ctx.ctxt, sizeof(struct context));
}
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, NULL, 0);
}
static void dbg_get_selector(struct dbg_hdr *hdr, union dbg_body *body) {
int gdtidx = body->sel.sel >> 3;
if (gdtidx < 0 || gdtidx >= MAXGDT) {
dbg_send_error(DBGERR_INVALIDSEL, hdr->id);
return;
}
memcpy(&body->sel.seg, &syspage->gdt[gdtidx], sizeof(struct segment));
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body, sizeof(struct dbg_selector));
}
static void dbg_get_modules(struct dbg_hdr *hdr, union dbg_body *body) {
struct peb *peb = (struct peb *) PEB_ADDRESS;
struct module *mod;
int n = 0;
mod = kmods.modules;
if (kmods.modules) {
while (1) {
body->modl.mods[n].hmod = mod->hmod;
body->modl.mods[n].name = &mod->name;
n++;
mod = mod->next;
if (mod == kmods.modules) break;
}
}
if (page_mapped(peb) && peb->usermods) {
mod = peb->usermods->modules;
while (1) {
body->modl.mods[n].hmod = mod->hmod;
body->modl.mods[n].name = &mod->name;
n++;
mod = mod->next;
if (mod == peb->usermods->modules) break;
}
}
if (n == 0) {
body->modl.mods[n].hmod = (hmodule_t) OSBASE;
body->modl.mods[n].name = &krnlname;
n++;
}
body->modl.count = n;
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body, sizeof(struct dbg_modulelist) + n * sizeof(struct dbg_moduleinfo));
}
static void dbg_get_threads(struct dbg_hdr *hdr, union dbg_body *body) {
int n = 0;
struct thread *t = threadlist;
while (1) {
body->thl.threads[n].tid = t->id;
body->thl.threads[n].tib = t->tib;
body->thl.threads[n].startaddr = t->entrypoint;
body->thl.threads[n].tcb = t;
n++;
t = t->next;
if (t == threadlist) break;
}
body->thl.count = n;
dbg_send_packet(hdr->cmd | DBGCMD_REPLY, hdr->id, body, sizeof(struct dbg_threadlist) + n * sizeof(struct dbg_threadinfo));
}
static void dbg_main() {
struct dbg_hdr hdr;
union dbg_body *body = (union dbg_body *) dbgdata;
int rc;
if (!debugging) {
init_debug_port();
kprintf("dbg: waiting for remote debugger...\n");
debugging = 1;
}
debugger_active = 1;
while (1) {
rc = dbg_recv_packet(&hdr, dbgdata);
if (rc < 0) {
kprintf("dbg: error %d receiving debugger command\n", rc);
continue;
}
switch (hdr.cmd) {
case DBGCMD_CONNECT:
dbg_connect(&hdr, body);
print_string("dbg: remote debugger connected\n");
break;
case DBGCMD_CONTINUE:
dbg_send_packet(DBGCMD_CONTINUE | DBGCMD_REPLY, hdr.id, NULL, 0);
debugger_active = 0;
return;
case DBGCMD_READ_MEMORY:
dbg_read_memory(&hdr, body);
break;
case DBGCMD_WRITE_MEMORY:
dbg_write_memory(&hdr, body);
break;
case DBGCMD_SUSPEND_THREAD:
dbg_suspend_thread(&hdr, body);
break;
case DBGCMD_RESUME_THREAD:
dbg_resume_thread(&hdr, body);
break;
case DBGCMD_GET_THREAD_CONTEXT:
dbg_get_thread_context(&hdr, body);
break;
case DBGCMD_SET_THREAD_CONTEXT:
dbg_set_thread_context(&hdr, body);
break;
case DBGCMD_GET_SELECTOR:
dbg_get_selector(&hdr, body);
break;
case DBGCMD_GET_MODULES:
dbg_get_modules(&hdr, body);
break;
case DBGCMD_GET_THREADS:
dbg_get_threads(&hdr, body);
break;
default:
kprintf("dbg: invalid command %d %d len=%d\n", hdr.id, hdr.cmd, hdr.len);
dbg_send_error(DBGERR_INVALIDCMD, hdr.id);
}
}
debugger_active = 0;
}
void dumpregs(struct context *ctxt) {
kprintf("EAX = %08X EBX = %08X ECX = %08X EDX = %08X\n", ctxt->eax, ctxt->ebx, ctxt->ecx, ctxt->edx);
kprintf("EDI = %08X ESI = %08X EBP = %08X ESP = %08X\n", ctxt->edi, ctxt->esi, ctxt->ebp, ctxt->esp);
kprintf("CS = %08X DS = %08X ES = %08X SS = %08X\n", ctxt->ecs, ctxt->ds, ctxt->es, ctxt->ess);
kprintf("EIP = %08X EFLG = %08X TRAP = %08X ERR = %08X\n", ctxt->eip, ctxt->eflags, ctxt->traptype, ctxt->errcode);
}
void dbg_enter(struct context *ctxt, void *addr) {
if (!debugging) {
kprintf("dbg: trap %d (%s) thread %d, addr %p\n", ctxt->traptype, trapnames[ctxt->traptype], self()->id, addr);
dumpregs(ctxt);
}
last_trap.tid = self()->id;
last_trap.traptype = ctxt->traptype;
last_trap.errcode = ctxt->errcode;
last_trap.eip = ctxt->eip;
last_trap.addr = addr;
if (!debug_nointr) sti();
if (debugging) {
if (debugger_active) {
kprintf("dbg: trap %d thread %d, addr %p while debugger active, system halted.\n", ctxt->traptype, self()->id, addr);
dumpregs(ctxt);
cli();
halt();
}
dbg_send_packet(DBGEVT_TRAP, 0, &last_trap, sizeof(struct dbg_evt_trap));
}
dbg_main();
if (self()->suspend_count > 0) dispatch();
}
void dbg_notify_create_thread(struct thread *t, void *startaddr) {
struct dbg_evt_create_thread create;
if (debugging && !debugger_active) {
create.tid = t->id;
create.tib = t->tib;
create.startaddr = startaddr;
create.tcb = t;
dbg_send_packet(DBGEVT_CREATE_THREAD, 0, &create, sizeof(struct dbg_evt_create_thread));
dbg_main();
}
}
void dbg_notify_exit_thread(struct thread *t) {
struct dbg_evt_exit_thread exit;
if (debugging && !debugger_active) {
exit.tid = t->id;
exit.exitcode = t->exitcode;
dbg_send_packet(DBGEVT_EXIT_THREAD, 0, &exit, sizeof(struct dbg_evt_exit_thread));
dbg_main();
}
}
void dbg_notify_load_module(hmodule_t hmod, char **name) {
struct dbg_evt_load_module load;
if (debugging && !debugger_active) {
load.hmod = hmod;
load.name = name;
dbg_send_packet(DBGEVT_LOAD_MODULE, 0, &load, sizeof(struct dbg_evt_load_module));
dbg_main();
}
}
void dbg_notify_unload_module(hmodule_t hmod) {
struct dbg_evt_unload_module unload;
if (debugging && !debugger_active) {
unload.hmod = hmod;
dbg_send_packet(DBGEVT_UNLOAD_MODULE, 0, &unload, sizeof(struct dbg_evt_unload_module));
dbg_main();
}
}
void dbg_output(char *msg) {
struct dbg_evt_output output;
if (debugging && !debugger_active) {
output.msgptr = msg;
output.msglen = strlen(msg) + 1;
dbg_send_packet(DBGEVT_OUTPUT, 0, &output, sizeof(struct dbg_evt_output));
dbg_main();
}
}