diff options
author | Malfurious <m@lfurio.us> | 2023-07-08 11:27:56 -0400 |
---|---|---|
committer | Malfurious <m@lfurio.us> | 2023-07-08 11:27:56 -0400 |
commit | 1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be (patch) | |
tree | 62ee155841728954887285e97f04d069ecdf39a4 /debugger.c | |
parent | c29bf2efbdc4f4186f3fe571601b4d1acac4b321 (diff) | |
download | misplays-1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be.tar.gz misplays-1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be.zip |
Initial debugger core and test UI
This is vaguely competent at tracing single-threaded programs. Vi-like
keybinds defined in misplays.c.
Signed-off-by: Malfurious <m@lfurio.us>
Diffstat (limited to 'debugger.c')
-rw-r--r-- | debugger.c | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/debugger.c b/debugger.c new file mode 100644 index 0000000..19d9edc --- /dev/null +++ b/debugger.c @@ -0,0 +1,282 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/ptrace.h> +#include <sys/uio.h> +#include <sys/wait.h> + +#include <capstone/capstone.h> + +/* #include "config.h" */ +#include "debugger.h" +#include "helpers.h" + +static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD; + +static void capture_state(struct tracee *dbg) { + struct state *s = xmalloc(sizeof(*s)); + ptrace(PTRACE_GETREGS, dbg->id, NULL, &s->regs); + ptrace(PTRACE_GETFPREGS, dbg->id, NULL, &s->fpregs); + list_init(&s->maps); + list_insert(dbg->states.end, s); + dbg->state = s; + + char mapspath[128], entry[512]; + snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)dbg->id); + + FILE *maps = fopen(mapspath, "r"); + if (maps) { + while (fgets(entry, sizeof(entry), maps)) { + struct map *m = xmalloc(sizeof(*m)); + sscanf(entry, "%lx-%lx ", &m->start, &m->end); + size_t size = m->end - m->start; + m->data = xmalloc(size); + + struct iovec loc = { m->data, size }; + struct iovec rem = { (void *)m->start, size }; + if (process_vm_readv(dbg->id, &loc, 1, &rem, 1, 0) < 0) { + free(m->data); + free(m); + continue; + } + + list_insert(s->maps.end, m); + } + + fclose(maps); + } +} + +static void clear_states(struct tracee *dbg) { + while (dbg->states.head != dbg->states.end) { + struct state *s = dbg->states.head; + while (s->maps.head != s->maps.end) { + struct map *m = s->maps.head; + list_remove(m); + free(m->data); + free(m); + } + list_remove(s); + free(s); + } + dbg->state = NULL; +} + +static void install_breakpoints(struct tracee *dbg) { + struct list *breaks = &dbg->breaks; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + if (b->enabled) { + unsigned long data; + data = ptrace(PTRACE_PEEKTEXT, dbg->id, b->address, NULL); + b->orig = data; + + data = (data & ~0xff) | BREAKPOINT_INSN; + ptrace(PTRACE_POKETEXT, dbg->id, b->address, data); + b->active = 1; + } + } +} + +static int uninstall_breakpoints(struct tracee *dbg, int stop) { + struct user_regs_struct regs; + int ch = 0; + int reenter = 0; + if (stop) { + ptrace(PTRACE_GETREGS, dbg->id, NULL, ®s); + } + + struct list *breaks = &dbg->breaks; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + if (b->active) { + ptrace(PTRACE_POKETEXT, dbg->id, b->address, b->orig); + b->active = 0; + + /* detect stop at breakpoint */ + if (stop && !ch) { + if (regs.rip - 1 == b->address) { + regs.rip--; + ptrace(PTRACE_SETREGS, dbg->id, NULL, ®s); + ch = 1; + + if (b->stack != 0 && b->stack != regs.rsp) { + reenter = 1; + } + } + } + + if (b->enabled < 0 && !reenter) { + struct breakpoint *del = b; + b = b->prev; + list_remove(del); + free(del); + } + } + } + + return reenter; +} + +int dbg_process(struct tracee *dbg, pid_t pid) { + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { + return -1; + } + + list_init(&dbg->breaks); + list_init(&dbg->states); + dbg->state = NULL; + dbg->id = pid; + dbg->child = 0; + dbg->stopped = 0; + dbg->status = 0; + dbg->signal = 0; + dbg->cont = 0; + dbg->buff = NULL; + dbg->buffsize = 0; + + while (!dbg_wait(dbg)) {} + ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_OPTIONS); + return 0; +} + +int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons) { + pid_t pid = fork(); + if (pid < 0) { + return -1; + } + + if (pid == 0) { + console_configslave(cons); + setpgid(0, 0); + ptrace(PTRACE_TRACEME, 0, NULL, NULL); + close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); + execvp(argv[0], argv); + exit(-1); + } + + list_init(&dbg->breaks); + list_init(&dbg->states); + dbg->state = NULL; + dbg->id = pid; + dbg->child = 1; + dbg->stopped = 0; + dbg->status = 0; + dbg->signal = 0; + dbg->cont = 0; + dbg->buff = NULL; + dbg->buffsize = 0; + + while (!dbg_wait(dbg)) {} + ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_EXITKILL | PTRACE_OPTIONS); + return 0; +} + +int dbg_wait(struct tracee *dbg) { + if (dbg->stopped) { + return 1; + } + + if (waitpid(dbg->id, &dbg->status, WNOHANG | __WALL) <= 0) { + return 0; + } + + if (dbg->cont != 0) { + install_breakpoints(dbg); + ptrace(dbg->cont, dbg->id, NULL, NULL); + dbg->cont = 0; + return 0; + } + + if (uninstall_breakpoints(dbg, 1)) { + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + dbg->cont = PTRACE_CONT; + return 0; + } + + capture_state(dbg); + dbg->stopped = 1; + return 1; +} + +/* note: move head < state < tail checks from main into these function + * add a dbg_stepback function */ + +int dbg_stepin(struct tracee *dbg) { + /* consider making this a no-op if we are not stopped on a call */ + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + dbg->stopped = 0; + dbg->cont = 0; + return 0; +} + +int dbg_stepover(struct tracee *dbg) { + csh handle; + cs_open(CS_ARCH_X86, CS_MODE_64, &handle); + + uint64_t address = dbg->state->regs.rip; + size_t size = 128; + const uint8_t *code = deref(dbg, address, size); + cs_insn *insn = cs_malloc(handle); + + cs_disasm_iter(handle, &code, &size, &address, insn); + + if (insn->id == X86_INS_CALL) { + struct breakpoint *b = xmalloc(sizeof(*b)); + b->address = address; + b->stack = dbg->state->regs.rsp; + b->enabled = -1; + b->active = 0; + list_insert(dbg->breaks.end, b); + dbg->cont = PTRACE_CONT; + //dbg_cont(dbg, PTRACE_CONT); + } else { + dbg->cont = 0; + //dbg_stepin(dbg); + } + + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + dbg->stopped = 0; + + cs_free(insn, 1); + cs_close(&handle); + return 0; +} + +/* +int dbg_stepout(struct tracee *dbg) { + unsigned long bp = dbg->state->regs.rbp; + unsigned long ra = *(unsigned long *)deref(dbg, bp+8, sizeof(unsigned long)); + + struct breakpoint *b = xmalloc(sizeof(*b)); + b->address = ra; + b->stack = 0; + b->enabled = -1; + b->active = 0; + list_insert(dbg->breaks.end, b); + + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + //clear_states(dbg); // keep this? + dbg->stopped = 0; + dbg->cont = PTRACE_CONT; + return 0; +} +*/ + +int dbg_cont(struct tracee *dbg, int cont) { + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + clear_states(dbg); + dbg->stopped = 0; + dbg->cont = cont; + return 0; +} + +void *deref(struct tracee *dbg, unsigned long addr, size_t size) { + (void)size; // todo + + struct list *maps = &dbg->state->maps; + for (struct map *m = maps->head; m != maps->end; m = m->next) { + if (m->start <= addr && addr < m->end) { + return (unsigned char *)m->data + (addr - m->start); + } + } + + return NULL; +} |