#include #include #include #include #include #include /* #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; }