#include #include #include #include #include #include #include #include #include #include #include "debugger.h" #include "helpers.h" static const int PTRACE_OPTIONS = PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT | PTRACE_O_TRACESYSGOOD; static const int PTRACE_CHILD_OPTIONS = PTRACE_OPTIONS | PTRACE_O_EXITKILL; //static const int STOP_ALL_ON_EVENT = 1; static const useconds_t SCHEDULER_DELAY = 10000; static struct process *new_process(pid_t id, int child) { struct process *proc = xmalloc(sizeof(*proc)); proc->id = id; proc->child = child; list_init(&proc->breakpoints); list_init(&proc->threads); memset(proc->status, 0, sizeof(proc->status)); return proc; } 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->doing = 0; th->donext = 0; strcpy(th->status, "RUNNING"); return th; } static struct thread *thread_by_id(struct process *proc, pid_t id) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->id == id) { return th; } } return NULL; } static void free_breakpoints(struct process *proc) { while (proc->breakpoints.head != proc->breakpoints.end) { struct breakpoint *b = proc->breakpoints.head; list_remove(b); free(b); } } static void install_breakpoints(struct thread *th) { struct archinfo archinfo; struct iovec regs = { &th->state->regs, th->state->regsize }; architecture_info(&archinfo, ®s); struct list *breaks = &th->proc->breakpoints; for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { if (!b->installed) { unsigned long word; word = ptrace(PTRACE_PEEKTEXT, th->id, b->address, NULL); b->text = word; word = (word & ~archinfo.bp_mask) | archinfo.bp_insn; ptrace(PTRACE_POKETEXT, th->id, b->address, word); b->installed = 1; b->previously_installed = 1; } } } static void uninstall_breakpoints(struct thread *th) { struct list *breaks = &th->proc->breakpoints; for (struct breakpoint *b = breaks->tail; b != breaks->end; b = b->prev) { if (b->installed) { ptrace(PTRACE_POKETEXT, th->id, b->address, b->text); b->installed = 0; } if (b->previously_installed && b->enabled < 0) { struct thread *t = NULL; if (b->tid == 0 || ((t = thread_by_id(th->proc, b->tid)) && !t->doing)) { struct breakpoint *del = b; b = b->next; list_remove(del); free(del); } } } } static int detect_breakpoint(struct thread *th, int *restart) { int ret = 0; *restart = 0; /* Hack: Need to manually fetch registers here, since capture_state() has * not yet run for this stop. It is not guaranteed that we even want to * preserve the current state. */ user_regs_t regs; struct iovec ivregs = { ®s, sizeof(regs) }; ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, &ivregs); struct archinfo archinfo; architecture_info(&archinfo, &ivregs); unsigned long breakpt_address = archinfo.progmctr - archinfo.bp_adjust; struct breakpoint *b = get_breakpoint(th->proc, breakpt_address); if (b && b->installed && th->doing != PTRACE_SINGLESTEP) { /* restore actual program counter to breakpoint address */ if (ivregs.iov_len == sizeof(struct user_regs_32)) { regs.PROGMCTR_32 = breakpt_address; } else { regs.PROGMCTR_64 = breakpt_address; } ptrace(PTRACE_SETREGSET, th->id, NT_PRSTATUS, &ivregs); b->hits++; /* todo: consider whether this is firing too much */ ret = b->user; if (b->stack != 0 && b->stack != archinfo.stackptr) { *restart = 1; } else if (b->tid != 0 && b->tid != th->id) { *restart = 1; } else if (!b->enabled) { *restart = 1; } } return ret; } static void free_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 process *proc) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (!th->state) { if (th->clearstates) { free_states(th); } struct state *s = xmalloc(sizeof(*s)); struct iovec regs = { &s->regs, sizeof(s->regs) }; ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, ®s); s->regsize = regs.iov_len; 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 char thread_state(struct thread *th) { char statpath[32], stat[512]; snprintf(statpath, sizeof(statpath), "/proc/%li/stat", (long)th->id); FILE *f = fopen(statpath, "r"); if (f) { fread(stat, 1, sizeof(stat), f); fclose(f); char *c = strchr(stat, ' '); c = strchr(c+1, ' ') + 1; return *c; } return 0; } static int wait_thread(struct thread *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); char state; do { state = thread_state(th); } while (state != 't' && state != 'D' && state != 'Z'); wait_thread(th); //if (STOP_ALL_ON_EVENT) { // th->cont = 0; // th->shouldcont = 0; //} } } } static void continue_all_threads(struct process *proc) { struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->id > 0) { char state; do { wait_thread(th); ptrace(PTRACE_CONT, th->id, NULL, th->signal); th->stopped = 0; th->signal = 0; th->donext = 0; th->doing = PTRACE_CONT; strcpy(th->status, "RUNNING"); th->clearstates = 1; state = thread_state(th); } while (state == 't'); } } } static void resume_threads(struct process *proc) { struct list *threads = &proc->threads; int once = 0; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->stopped && th->doing == PTRACE_SINGLESTEP) { ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); th->stopped = 0; th->signal = 0; strcpy(th->status, "RUNNING"); } } for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->stopped && th->doing && th->doing != PTRACE_SINGLESTEP) { if (!once) { usleep(SCHEDULER_DELAY); once = 1; } install_breakpoints(th); ptrace(th->doing, th->id, NULL, th->signal); th->stopped = 0; th->signal = 0; strcpy(th->status, "RUNNING"); } } } static int wait_thread(struct thread *th) { if (th->id <= 0) { return -1; } int status; if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; } if (WIFEXITED(status)) { th->id = 0; th->stopped = 1; th->signal = WEXITSTATUS(status); th->doing = 0; th->donext = 0; strcpy(th->status, "EXITED"); return 1; } else if (WIFSIGNALED(status)) { th->id = 0; th->stopped = 1; th->signal = WTERMSIG(status); th->doing = 0; th->donext = 0; strcpy(th->status, "TERMINATED"); return 1; } else if (WIFSTOPPED(status)) { struct process *eventproc; struct thread *eventth; unsigned long eventmsg; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); /* todo: process status messages */ switch (status >> 8) { /* todo: other ptrace event stops */ case SIGTRAP | (PTRACE_EVENT_CLONE << 8): eventth = new_thread(th->proc, eventmsg); list_insert(th->proc->threads.end, eventth); while (!wait_thread(eventth)) {} th->stopped = 1; th->signal = 0; th->doing = 0; th->donext = 0; strcpy(th->status, "CLONE"); th->state = NULL; return 1; case SIGTRAP | (PTRACE_EVENT_FORK << 8): eventproc = new_process(eventmsg, th->proc->child); eventth = new_thread(eventproc, eventmsg); list_insert(eventproc->threads.end, eventth); list_insert(th->proc->next, eventproc); //while (!wait_thread(eventth)) {} dbg_sync(eventproc); th->stopped = 1; th->signal = 0; th->doing = 0; th->donext = 0; strcpy(th->status, "FORK"); th->state = NULL; return 1; case SIGTRAP | (PTRACE_EVENT_EXEC << 8): eventth = thread_by_id(th->proc, eventmsg); if (eventth->id != th->id) { eventth->id = 0; eventth->stopped = 1; eventth->signal = 0; eventth->doing = 0; eventth->donext = 0; strcpy(eventth->status, "EXITED"); } th->stopped = 1; th->signal = 0; th->doing = 0; th->donext = 0; strcpy(th->status, "EXECVE"); th->state = NULL; return 1; case SIGTRAP | (PTRACE_EVENT_EXIT << 8): th->stopped = 1; th->signal = 0; th->doing = PTRACE_CONT; th->donext = 0; strcpy(th->status, "EXITING"); th->state = NULL; return 1; case SIGTRAP | (PTRACE_EVENT_STOP << 8): th->stopped = 1; th->signal = 0; strcpy(th->status, "STOPPED"); if (!th->doing) { th->state = NULL; } return 1; case SIGTRAP | 0x80: th->stopped = 1; th->signal = 0; th->doing = 0; th->donext = 0; strcpy(th->status, "SYSCALL"); th->state = NULL; return 1; case SIGTRAP: th->stopped = 1; th->signal = 0; int restart; int bp = detect_breakpoint(th, &restart); strcpy(th->status, (bp ? "BREAKPOINT" : "STEP")); if (restart) { th->donext = th->doing; th->doing = (th->doing ? PTRACE_SINGLESTEP : 0); } else { th->doing = th->donext; th->donext = 0; } if (!th->doing) { th->state = NULL; } return 1; default: th->stopped = 1; th->signal = WSTOPSIG(status); th->doing = 0; th->donext = 0; strcpy(th->status, "SIGNAL DELIVERY"); th->state = NULL; return 1; } } return -1; } struct breakpoint *add_breakpoint(struct process *proc, unsigned long address) { struct breakpoint *b = xmalloc(sizeof(*b)); b->address = address; b->text = 0; b->installed = 0; b->previously_installed = 0; b->hits = 0; b->user = 1; b->stack = 0; b->tid = 0; b->enabled = 1; list_insert(proc->breakpoints.end, b); return b; } struct breakpoint *get_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 b; } } return NULL; } struct process *dbg_attach(pid_t pid, int child) { const int OPTIONS = (child ? PTRACE_CHILD_OPTIONS : PTRACE_OPTIONS); char taskpath[32]; snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); DIR *taskdir = opendir(taskpath); if (!taskdir) { return NULL; } struct dirent *task; struct process *proc = new_process(pid, child); while ((task = readdir(taskdir))) { pid_t id = strict_strtoul(task->d_name, 0); if (id != 0) { if (ptrace(PTRACE_SEIZE, id, NULL, OPTIONS) < 0) { closedir(taskdir); dbg_detach(proc); return NULL; } struct thread *th = new_thread(proc, id); list_insert(proc->threads.end, th); } } closedir(taskdir); interrupt_all_threads(proc); capture_state(proc); return proc; } void dbg_detach(struct process *proc) { /* ptrace requires that threads be stopped before calling PTRACE_DETACH. * In some cases, a blocked thread can possibly have a pending trap which * would crash the process if encountered after ptracing. We cycle an extra * continue/interrupt to consume this trap event ourselves before * detaching. */ interrupt_all_threads(proc); continue_all_threads(proc); interrupt_all_threads(proc); /* Supplement to PTRACE_O_EXITKILL */ if (proc->child) { kill(proc->id, SIGKILL); } while (proc->threads.head != proc->threads.end) { struct thread *th = proc->threads.head; if (th->id > 0) { uninstall_breakpoints(th); ptrace(PTRACE_DETACH, th->id, NULL, th->signal); th->id = 0; } dbg_free(th); } free_breakpoints(proc); list_remove(proc); free(proc); } int dbg_free(struct thread *th) { if (th->id <= 0) { free_states(th); list_remove(th); free(th); return 0; } return -1; } int dbg_sync(struct process *proc) { struct thread *acted = NULL; struct list *threads = &proc->threads; for (struct thread *th = threads->head; th != threads->end; th = th->next) { if (th->id > 0) { if (/*!th->stopped &&*/ wait_thread(th)) { acted = th; } else if (th->stopped && th->doing) { acted = th; } } } if (acted) { interrupt_all_threads(proc); uninstall_breakpoints(acted); capture_state(proc); resume_threads(proc); } return acted != NULL; } int dbg_intr(struct thread *th) { if (th->id <= 0 || th->stopped) { return -1; } /* todo: move this into dbg_sync? */ /* probably not */ ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); th->doing = 0; th->donext = 0; return 0; } int dbg_cont(struct thread *th) { if (th->id <= 0 || !th->stopped) { return -1; } th->doing = PTRACE_SINGLESTEP; th->donext = PTRACE_CONT; th->clearstates = 1; return 0; } int dbg_syscall(struct thread *th) { if (th->id <= 0 || !th->stopped) { return -1; } th->doing = PTRACE_SINGLESTEP; th->donext = PTRACE_SYSCALL; th->clearstates = 1; return 0; } int dbg_stepin(struct thread *th) { if (!th->stopped) { return -1; } if (th->state != th->states.tail) { th->state = th->state->next; return 0; } if (th->id <= 0) { return -1; } th->doing = PTRACE_SINGLESTEP; th->donext = 0; th->clearstates = 0; return 0; } int dbg_stepover(struct thread *th) { if (!th->stopped) { return -1; } if (th->state != th->states.tail) { th->state = th->state->next; return 0; } if (th->id <= 0) { return -1; } struct archinfo archinfo; struct iovec regs = { &th->state->regs, th->state->regsize }; architecture_info(&archinfo, ®s); csh handle; cs_open(archinfo.cs_arch, archinfo.cs_mode, &handle); cs_insn *insn = cs_malloc(handle); uint64_t address = archinfo.progmctr; size_t size = 128; const uint8_t *code = deref(th, address, size); cs_disasm_iter(handle, &code, &size, &address, insn); if (insn->id == archinfo.cs_call) { struct breakpoint *b = add_breakpoint(th->proc, address); b->user = 0; b->stack = archinfo.stackptr; b->tid = th->id; b->enabled = -1; th->doing = PTRACE_SINGLESTEP; th->donext = PTRACE_CONT; th->clearstates = 0; } else { th->doing = PTRACE_SINGLESTEP; th->donext = 0; th->clearstates = 0; } cs_free(insn, 1); cs_close(&handle); return 0; } int dbg_stepback(struct thread *th) { if (!th->stopped) { return -1; } if (th->state != th->states.head) { th->state = th->state->prev; return 0; } return -1; } void *deref(struct thread *th, unsigned long address, size_t size) { /* todo: size, and mapping breaks */ (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 <= address && address < m->end) { return (unsigned char *)m->data + (address - m->start); } } return NULL; } //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; //}