#include #include #include #include #include #include #include #include #include /* #include "config.h" */ #include "debugger.h" #include "helpers.h" static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; static const int INTERRUPT_ON_SEIZE = 1; static int detect_breakpoint(struct thread *th) { struct user_regs_struct regs; int check = 0; int restart = 0; ptrace(PTRACE_GETREGS, th->id, NULL, ®s); struct list *breaks = &th->proc->breakpoints; for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { /* should be if (b->active) ? * but there is a potential other bug that can happen * with the order of events in dbg_wait() */ if (b->enabled) { if (!check) { if (regs.rip - 1 == b->address) { regs.rip--; ptrace(PTRACE_SETREGS, th->id, NULL, ®s); check = 1; if (b->stack != 0 && b->stack != regs.rsp) { restart = 1; } if (b->tid != 0 && b->tid != th->id) { restart = 1; } } } if (b->enabled < 0 && !restart) { struct breakpoint *del = b; b = b->prev; list_remove(del); free(del); } } } return restart; } static void install_breakpoints(struct thread *th) { struct list *breaks = &th->proc->breakpoints; for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { if (b->enabled && !b->active) { unsigned long data; data = ptrace(PTRACE_PEEKTEXT, th->id, b->address, NULL); b->orig = data; data = (data & ~0xff) | BREAKPOINT_INSN; ptrace(PTRACE_POKETEXT, th->id, b->address, data); b->active = 1; } } } static void uninstall_breakpoints(struct process *proc) { struct list *breaks = &proc->breakpoints; for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) { if (b->active) { ptrace(PTRACE_POKETEXT, proc->id, b->address, b->orig); b->active = 0; } } } static void clear_breakpoints(struct process *proc) { while (proc->breakpoints.head != proc->breakpoints.end) { struct breakpoint *b = proc->breakpoints.head; list_remove(b); free(b); } } static void clear_states(struct thread *th) { while (th->states.head != th->states.end) { struct state *s = th->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); } th->state = NULL; th->clearstates = 0; } static void capture_state(struct thread *th) { if (th->clearstates) { clear_states(th); } struct state *s = xmalloc(sizeof(*s)); ptrace(PTRACE_GETREGS, th->id, NULL, &s->regs); ptrace(PTRACE_GETFPREGS, th->id, NULL, &s->fpregs); list_init(&s->maps); list_insert(th->states.end, s); th->state = s; char mapspath[32], entry[512]; snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)th->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(th->id, &loc, 1, &rem, 1, 0) < 0) { free(m->data); free(m); continue; } list_insert(s->maps.end, m); } fclose(maps); } } static struct thread *new_thread(struct process *proc, pid_t id) { struct thread *th = xmalloc(sizeof(*th)); th->proc = proc; list_init(&th->states); th->state = NULL; th->clearstates = 0; th->id = id; th->stopped = 0; th->signal = 0; th->cont = 0; th->status = "RUNNING"; return th; } static void interrupt_all_threads(struct process *proc) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (!th->stopped) { ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); while (!dbg_wait(th, 0)) {} } } } void add_breakpoint(struct process *proc, unsigned long address, unsigned long stack, pid_t tid, int enabled) { struct breakpoint *b = xmalloc(sizeof(*b)); b->orig = 0; b->address = address; b->stack = stack; b->tid = tid; b->enabled = enabled; b->active = 0; list_insert(proc->breakpoints.end, b); } int is_breakpoint(struct process *proc, unsigned long address) { struct list *breaks = &proc->breakpoints; for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { if (b->address == address) { return 1; } } return 0; } int dbg_process(struct process *proc, pid_t pid, int child) { proc->id = pid; proc->child = child; list_init(&proc->breakpoints); list_init(&proc->threads); char taskpath[32]; snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); DIR *taskdir = opendir(taskpath); if (!taskdir) { return -1; } struct dirent *task; while ((task = readdir(taskdir))) { pid_t id = strtoul(task->d_name, NULL, 0); if (id != 0) { if (ptrace(PTRACE_SEIZE, id, NULL, PTRACE_OPTIONS) < 0) { closedir(taskdir); return -1; } struct thread *th = new_thread(proc, id); list_insert(proc->threads.end, th); } } if (INTERRUPT_ON_SEIZE) { interrupt_all_threads(proc); } closedir(taskdir); return 0; } int dbg_detach(struct process *proc) { interrupt_all_threads(proc); uninstall_breakpoints(proc); clear_breakpoints(proc); while (proc->threads.head != proc->threads.end) { struct thread *th = proc->threads.head; if (th->id >= 0) { ptrace(PTRACE_DETACH, th->id, NULL, th->signal); } clear_states(th); list_remove(th); free(th); } return 0; } int dbg_wait(struct thread *th, int dostops) { if (th->id < 0) { return -1; } int status; if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; } if (WIFEXITED(status)) { th->id = -1; th->stopped = 1; th->signal = WEXITSTATUS(status); th->cont = 0; th->status = "EXITED"; return 1; } if (WIFSIGNALED(status)) { th->id = -2; th->stopped = 1; th->signal = WTERMSIG(status); th->cont = 0; th->status = "TERMINATED"; return 1; } unsigned long eventmsg; struct thread *newth; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); if (WIFSTOPPED(status)) { switch (status >> 8) { case SIGTRAP | (PTRACE_EVENT_CLONE << 8): newth = new_thread(th->proc, eventmsg); list_insert(th->proc->threads.end, newth); while (!dbg_wait(newth, 0)) {} th->stopped = 1; th->signal = 0; th->cont = 0; th->status = "CLONE EVENT"; if (dostops) { interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); } capture_state(th); return 1; case SIGTRAP | (PTRACE_EVENT_EXIT << 8): th->stopped = 1; th->signal = 0; //eventmsg; th->cont = 0; th->status = "EXIT EVENT"; if (dostops) { interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); } capture_state(th); return 1; case SIGTRAP | (PTRACE_EVENT_STOP << 8): th->stopped = 1; th->signal = 0; th->cont = 0; th->status = "STOP EVENT"; if (dostops) { interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); } capture_state(th); return 1; case SIGTRAP | 0x80: th->stopped = 1; th->signal = 0; th->cont = 0; th->status = "SYSCALL EVENT"; if (dostops) { interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); } capture_state(th); return 1; case SIGTRAP: th->stopped = 1; th->signal = 0; if (th->cont != 0) { install_breakpoints(th); ptrace(th->cont, th->id, NULL, NULL); th->cont = 0; th->stopped = 0; th->status = "RUNNING"; return 0; } if (dostops) { interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); } if (detect_breakpoint(th)) { dbg_cont(th, PTRACE_CONT); return 0; } th->status = "STEP/BREAKPOINT"; capture_state(th); return 1; default: th->stopped = 1; th->signal = WSTOPSIG(status); th->cont = 0; th->status = "SIGNAL DELIVERY"; if (dostops) { interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); } capture_state(th); return 1; } } return -1; } int dbg_cont(struct thread *th, int cont) { if (th->id < 0 || !th->stopped) { return -1; } struct list *threads = &th->proc->threads; for (struct thread *t = threads->head; t != threads->end; t = t->next) { ptrace(PTRACE_SINGLESTEP, t->id, NULL, t->signal); t->stopped = 0; t->signal = 0; t->cont = cont; t->status = "RUNNING"; t->clearstates = 1; } /* are there timing concerns here? Critically, we * are doing this before the event loop has a chance to * wait on any thread and actually start it in the cont */ /* cant do it here, since no thread is stopped (ptrace * returns ESRCH). */ //install_breakpoints(th->proc); return 0; } int dbg_stepin(struct thread *th) { if (th->id < 0 || !th->stopped) { return -1; } if (ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal) < 0) { return -1; } th->stopped = 0; th->signal = 0; th->cont = 0; th->status = "RUNNING"; th->clearstates = 0; return 0; } int dbg_intr(struct thread *th) { if (th->id < 0 || th->stopped) { return -1; } ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); return 0; } void *deref(struct thread *th, unsigned long addr, size_t size) { (void)size; if (!th->state) { return NULL; } struct list *maps = &th->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; } ///* //static const char *strstatus(int status) { // static char buff[128]; // // if (WIFEXITED(status)) { // snprintf(buff, sizeof(buff), "exited: %i", WEXITSTATUS(status)); // } else if (WIFSIGNALED(status)) { // snprintf(buff, sizeof(buff), "terminated: %s", strsignal(WTERMSIG(status))); // } else if (WIFSTOPPED(status)) { // snprintf(buff, sizeof(buff), "stopped: %s", strsignal(WSTOPSIG(status))); // } else { // snprintf(buff, sizeof(buff), "UNKNOWN STATUS VALUE"); // } // // return buff; //} //*/ // //static int all_cont(struct process *proc) { // /* install breakpoints */ // struct list *breaks = &proc->breakpoints; // for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { // if (b->enabled) { // unsigned long data; // data = ptrace(PTRACE_PEEKTEXT, proc->id, b->address, NULL); // b->orig = data; // // data = (data & ~0xff) | BREAKPOINT_INSN; // ptrace(PTRACE_POKETEXT, proc->id, b->address, data); // b->active = 1; // } // } // // /* continue all threads */ // struct list *threads = &proc->threads; // for (struct thread *th = threads->head; th != threads->end; th = th->next) { // ptrace(PTRACE_); // } // return -1; //} // ///* current problem to solve: // * if a SINGLESTEP is interrupted (it was over a blocking syscall for example), // * a CONT out of that interruption will stop AS IF the SINGLESTEP had completed. // */ ///* // * new: // * next step is to trigger all threads starting on continue. // */ /* PTRACE_GETSIGINFO ! */ /* handle ptrace() -> ESRCH as unexpectedly dead tracee */ // //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->gid = 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, NULL)) {} // ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_EXITKILL | PTRACE_OPTIONS); // return 0; //} // ///* 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; //} // //void dbg_free(struct tracee *dbg) { // while (dbg->breaks.head != dbg->breaks.end) { // struct breakpoint *b = dbg->breaks.head; // list_remove(b); // free(b); // } // clear_states(dbg); // free(dbg->buff); //}