#include #include #include #include #include "debugger.h" struct dr6 { uint64_t b0 : 1, b1 : 1, b2 : 1, b3 : 1, : 7, bld : 1, : 1, bd : 1, bs : 1, bt : 1, rtm : 1, : 47; }; struct dr7 { uint64_t l0 : 1, g0 : 1, l1 : 1, g1 : 1, l2 : 1, g2 : 1, l3 : 1, g3 : 1, : 8, rw0 : 2, len0 : 2, rw1 : 2, len1 : 2, rw2 : 2, len2 : 2, rw3 : 2, len3 : 2, : 32; }; struct rflags { uint64_t cf : 1, : 1, pf : 1, : 1, af : 1, : 1, zf : 1, sf : 1, tf : 1, if_ : 1, df : 1, of : 1, iopl : 2, nt : 1, md : 1, rf : 1, vm : 1, ac : 1, vif : 1, vip : 1, id : 1, : 42; }; struct breakpoint { uint64_t addr; uint8_t len; uint8_t rw; uint8_t used; uint8_t enable; uint8_t instr_len; }; static int dbg_steps = 0; static int dbg_continue = 0; static struct breakpoint bkps[4] = {{0}, {0}, {0}, {0}}; static size_t debugger_read(char *buf, size_t len) { char *p = buf; size_t result; while (p < buf + len - 1) { char ch = serial_in(); switch (ch) { case '\r': goto end; case '\x7f': if (p > buf) { p--; kputs("\x08 \x08"); } break; default: kputc(ch); *p++ = ch; } } end: result = p - buf; kputc('\n'); for(; p < buf + len; p++) { *p = 0; } return result; } static void debugger_msg(int cause, struct dr6 dr6) { switch (cause) { case DEBUG_INT3: kputs("dbg: reached int3"); break; case DEBUG_FAULT: kputs("dbg: a fault occured"); break; case DEBUG_DBG: if (dr6.bs) kputs("dbg: finished steps"); if (dr6.b0) kputs("dbg: reached breakpoint 0"); else if (dr6.b1) kputs("dbg: reached breakpoint 1"); else if (dr6.b2) kputs("dbg: reached breakpoint 2"); else if (dr6.b3) kputs("dbg: reached breakpoint 3"); } if (dbg_steps > 0) { kprintf(" (%d steps left)", dbg_steps); } if (dbg_continue > 0) { kputs(" (paused continue)"); } kputs("\n"); } static void debugger_print_regs(struct isr_regs *state) { kprintf("rax: %#016lx (%lu)\n", state->rax, state->rax); kprintf("rbx: %#016lx (%lu)\n", state->rbx, state->rbx); kprintf("rcx: %#016lx (%lu)\n", state->rcx, state->rcx); kprintf("rdx: %#016lx (%lu)\n", state->rdx, state->rdx); kprintf("rsi: %#016lx (%lu)\n", state->rsi, state->rsi); kprintf("rdi: %#016lx (%lu)\n", state->rdi, state->rdi); kprintf("rsp: %#016lx (%lu)\n", state->rsp, state->rsp); kprintf("rbp: %#016lx (%lu)\n", state->rbp, state->rbp); kprintf("r8 : %#016lx (%lu)\n", state->r8 , state->r8 ); kprintf("r9 : %#016lx (%lu)\n", state->r9 , state->r9 ); kprintf("r10: %#016lx (%lu)\n", state->r10, state->r10); kprintf("r11: %#016lx (%lu)\n", state->r11, state->r11); kprintf("r12: %#016lx (%lu)\n", state->r12, state->r12); kprintf("r13: %#016lx (%lu)\n", state->r13, state->r13); kprintf("r14: %#016lx (%lu)\n", state->r14, state->r14); kprintf("r15: %#016lx (%lu)\n", state->r15, state->r15); kputs("---\n"); kprintf("rip: %#016lx (%lu)\n", state->rip, state->rip); kputs("---\n"); kprintf("rflags: %#016lx (%lu)\n", state->rflags, state->rflags); struct rflags *rflags = (struct rflags *)state->rflags; kputs("rflags: "); if(rflags->cf) kputs("CF "); if(rflags->pf) kputs("PF "); if(rflags->af) kputs("AF "); if(rflags->zf) kputs("ZF "); if(rflags->sf) kputs("SF "); if(rflags->tf) kputs("TF "); if(rflags->if_) kputs("IF "); if(rflags->df) kputs("DF "); if(rflags->of) kputs("OF "); if(rflags->iopl) kputs("IOPL "); if(rflags->nt) kputs("NT "); if(rflags->md) kputs("MD "); if(rflags->rf) kputs("RF "); if(rflags->vm) kputs("VM "); if(rflags->ac) kputs("AC "); if(rflags->vif) kputs("VIF "); if(rflags->vip) kputs("VIP "); if(rflags->id) kputs("ID "); kputs("\n"); } #define PROMPT_LEN 60 static int debugger_handle_bkp_cmd(char *msg) { if (msg[0] == 'l') { // list breakpoints for (int i = 0; i < 4; i++) { struct breakpoint bkp = bkps[i]; if(!bkp.used) { kprintf("breakpoint %d: unset\n", i); continue; } const char *lenstrs[] = {"1", "2", "8", "4"}; const char *rwstrs[] = {"x ", "w ", "io", "rw"}; kprintf( "breakpoint %d: %#016lx len=%s trigger=%s instrlen=%01d %s\n", i, bkp.addr, lenstrs[bkp.len], rwstrs[bkp.rw], bkp.instr_len, bkp.enable ? " enabled" : " disabled" ); } return 1; } else if (msg[0] == 'd') { // disable breakpoints bkps[0].enable = 0; bkps[1].enable = 0; bkps[2].enable = 0; bkps[3].enable = 0; kputs("disabled breakpoints\n"); return 1; } else if (msg[0] == 'c') { // clear breakpoints bkps[0].used = 0; bkps[1].used = 0; bkps[2].used = 0; bkps[3].used = 0; kputs("cleared breakpoints\n"); return 1; } else if (msg[0] <= '0' && msg[0] >= '3') { kputs("invalid breakpoint command\n"); return 1; } char buf[64]; int id = msg[0] - '0'; switch (msg[1]) { case 'e': bkps[id].enable = bkps[id].used; kprintf("enabled breakpoint %d\n", id); return 1; case 'd': bkps[id].enable = 0; kprintf("disabled breakpoint %d\n", id); return 1; case 'c': bkps[id].used = 0; kprintf("cleared breakpoint %d\n", id); return 1; case 'a': kputs("addr> 0x"); debugger_read(buf, 64); bkps[id].addr = strtoll(buf, NULL, 16); kputs("len (1/2/4/8)> "); debugger_read(buf, 64); switch (buf[0]) { case '1': bkps[id].len = 0x0; break; case '2': bkps[id].len = 0x1; break; case '8': bkps[id].len = 0x2; break; case '4': bkps[id].len = 0x3; break; default: bkps[id].len = 0x0; break; } kputs("cond (x/w/io/rw)> "); debugger_read(buf, 64); switch ((int)buf[0] * 256 + (int)buf[1]) { case (int)'x'*256 + (int)0: bkps[id].rw = 0x0; break; case (int)'w'*256 + (int)0: bkps[id].rw = 0x1; break; case (int)'i'*256 + (int)'o': bkps[id].rw = 0x2; break; case (int)'r'*256 + (int)'w': bkps[id].rw = 0x3; break; default: bkps[id].len = 0x0; break; } kputs("instr len (1..15)> "); debugger_read(buf, 64); bkps[id].instr_len = strtoi(buf, NULL, 10); kputs("enable (y/n)> "); debugger_read(buf, 64); bkps[id].used = 1; bkps[id].enable = !(buf[0] == 'n' || buf[0] == 'N'); kprintf("added breakpoint %d\n", id); return 1; default: kputs("invalid breakpoint command\n"); return 1; } } static void debugger_load_bkps() { struct dr7 dr7; __asm__ volatile ("mov %%dr7, %0" : "=r"(dr7)); dr7.g0 = bkps[0].enable & bkps[0].used; dr7.g1 = bkps[1].enable & bkps[1].used; dr7.g2 = bkps[2].enable & bkps[2].used; dr7.g3 = bkps[3].enable & bkps[3].used; dr7.l0 = 0; dr7.l1 = 0; dr7.l2 = 0; dr7.l3 = 0; dr7.rw0 = bkps[0].rw; dr7.rw1 = bkps[1].rw; dr7.rw2 = bkps[2].rw; dr7.rw3 = bkps[3].rw; dr7.len0 = bkps[0].len; dr7.len1 = bkps[1].len; dr7.len2 = bkps[2].len; dr7.len3 = bkps[3].len; __asm__ volatile ("mov %0, %%dr7" : : "r"(dr7)); __asm__ volatile ("mov %0, %%dr0" : : "r"(bkps[0].addr)); __asm__ volatile ("mov %0, %%dr1" : : "r"(bkps[1].addr)); __asm__ volatile ("mov %0, %%dr2" : : "r"(bkps[2].addr)); __asm__ volatile ("mov %0, %%dr3" : : "r"(bkps[3].addr)); } static int debugger_prompt(struct isr_regs *state) { kputs("dbg> "); char buf[64]; debugger_read(buf, 64); struct rflags *rflags = (struct rflags *)&state->rflags; switch (buf[0]) { case '\0': // nothing entered return 1; case 'h': // help kputs("see debugger.c for help information\n"); return 1; case 'q': // quit return 0; case 'c': // continue rflags->tf = 1; dbg_continue = 1; return 0; case 'r': // print registers debugger_print_regs(state); return 1; case 'k': // backtrace log_backtrace_ex((void *)state->rip, (void *)state->rbp); return 1; case 's': // step (n) rflags->tf = 1; dbg_steps = atol(buf + 1) - 1; //if (dbg_steps < 0) dbg_steps = 0; return 0; case 'm': // memory { kputs("addr> 0x"); debugger_read(buf, 64); uint64_t start = (~0xF) & strtoll(buf, NULL, 16); kputs("len> 0x"); debugger_read(buf, 64); uint64_t len = (~0xF) & strtoll(buf, NULL, 16); for (uint64_t a = start; a <= start + len; a += 0x10) { kprintf("%08lx: ", a); for (uint64_t b = 0; b < 0x10; b++) { kprintf("%02x ", *(uint8_t *)(a + b)); if (b % 0x08 == 7) { kputc(' '); } } kputs("|"); for (uint64_t b = 0; b < 0x10; b++) { uint8_t ch = *(uint8_t *)(a + b); if (ch < 0x20 || ch >= 0x7f) { ch = '.'; } kputc(ch); } kputs("|\n"); } return 1; } case 'b': // breakpoints { int res = debugger_handle_bkp_cmd(buf+1); debugger_load_bkps(); return res; } default: kputs("unknown command\n"); return 1; } } void debugger(struct isr_regs *volatile state, int cause) { struct dr6 dr6; __asm__ volatile ("mov %%dr6, %0" : "=r"(dr6)); if (!(dr6.b0 || dr6.b1 || dr6.b2 || dr6.b3)) { if (dr6.bs && dbg_steps > 0) { dbg_steps--; return; } if (dr6.bs && dbg_continue) { return; } } debugger_msg(cause, dr6); kputs("hi\n"); dbg_steps = 0; dbg_continue = 0; struct rflags *rflags = (struct rflags *) &state->rflags; rflags->tf = 0; if (dr6.b0) { state->rip += bkps[0].instr_len; } else if (dr6.b1) { state->rip += bkps[1].instr_len; } else if (dr6.b2) { state->rip += bkps[2].instr_len; } else if (dr6.b3) { state->rip += bkps[3].instr_len; } while (debugger_prompt(state)); }