diff options
-rw-r--r-- | debugger.c | 795 | ||||
-rw-r--r-- | debugger.h | 64 | ||||
-rw-r--r-- | helpers.c | 22 | ||||
-rw-r--r-- | helpers.h | 5 | ||||
-rw-r--r-- | misplays.c | 392 |
5 files changed, 905 insertions, 373 deletions
@@ -1,282 +1,705 @@ +#include <dirent.h> +#include <elf.h> +#include <signal.h> #include <stdio.h> #include <stdlib.h> +#include <string.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; - } +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 const unsigned int BREAKPOINT_INSN = 0xcc; + +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 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 & ~0xff) | BREAKPOINT_INSN; + ptrace(PTRACE_POKETEXT, th->id, b->address, word); + b->installed = 1; + } + } +} - list_insert(s->maps.end, m); +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->enabled < 0) { + struct thread *t; + 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; - fclose(maps); + struct user_regs_struct regs; + struct iovec ivregs = { ®s, sizeof(regs) }; + ptrace(PTRACE_GETREGSET, th->id, NT_PRSTATUS, &ivregs); + + struct breakpoint *b = get_breakpoint(th->proc, regs.rip - 1); + if (b && b->installed && th->doing != PTRACE_SINGLESTEP) { + regs.rip--; + 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 != regs.rsp) { + *restart = 1; + } else if (b->tid != 0 && b->tid != th->id) { + *restart = 1; + } else if (!b->enabled) { + *restart = 1; + } } + + return ret; } -static void clear_states(struct tracee *dbg) { - while (dbg->states.head != dbg->states.end) { - struct state *s = dbg->states.head; +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); } - dbg->state = NULL; + + th->state = NULL; + th->clearstates = 0; } -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 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); + 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 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); +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; } - 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; - } + 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->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; +} + +void dbg_sync(struct process *proc) { + struct thread *acted = NULL; - if (b->enabled < 0 && !reenter) { - struct breakpoint *del = b; - b = b->prev; - list_remove(del); - free(del); + 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; } } } - return reenter; + if (acted) { + interrupt_all_threads(proc); + uninstall_breakpoints(acted); + capture_state(proc); + resume_threads(proc); + } } -int dbg_process(struct tracee *dbg, pid_t pid) { - if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { +int dbg_intr(struct thread *th) { + if (th->id <= 0 || th->stopped) { 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); + /* 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_new_process(struct tracee *dbg, char **argv, struct console *cons) { - pid_t pid = fork(); - if (pid < 0) { +int dbg_cont(struct thread *th) { + if (th->id <= 0 || !th->stopped) { 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); + th->doing = PTRACE_SINGLESTEP; + th->donext = PTRACE_CONT; + th->clearstates = 1; return 0; } -int dbg_wait(struct tracee *dbg) { - if (dbg->stopped) { - return 1; +int dbg_syscall(struct thread *th) { + if (th->id <= 0 || !th->stopped) { + return -1; } - if (waitpid(dbg->id, &dbg->status, WNOHANG | __WALL) <= 0) { - return 0; + 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 (dbg->cont != 0) { - install_breakpoints(dbg); - ptrace(dbg->cont, dbg->id, NULL, NULL); - dbg->cont = 0; + if (th->state != th->states.tail) { + th->state = th->state->next; return 0; } - if (uninstall_breakpoints(dbg, 1)) { - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - dbg->cont = PTRACE_CONT; - return 0; + if (th->id <= 0) { + return -1; } - capture_state(dbg); - dbg->stopped = 1; - return 1; + th->doing = PTRACE_SINGLESTEP; + th->donext = 0; + th->clearstates = 0; + return 0; } -/* note: move head < state < tail checks from main into these function - * add a dbg_stepback function */ +int dbg_stepover(struct thread *th) { + if (!th->stopped) { + return -1; + } + + if (th->state != th->states.tail) { + th->state = th->state->next; + return 0; + } -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; -} + if (th->id <= 0) { + return -1; + } -int dbg_stepover(struct tracee *dbg) { csh handle; cs_open(CS_ARCH_X86, CS_MODE_64, &handle); + cs_insn *insn = cs_malloc(handle); - uint64_t address = dbg->state->regs.rip; + uint64_t address = th->state->regs.rip; size_t size = 128; - const uint8_t *code = deref(dbg, address, size); - cs_insn *insn = cs_malloc(handle); + const uint8_t *code = deref(th, address, size); 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; + struct breakpoint *b = add_breakpoint(th->proc, address); + b->user = 0; + b->stack = th->state->regs.rsp; + b->tid = th->id; b->enabled = -1; - b->active = 0; - list_insert(dbg->breaks.end, b); - dbg->cont = PTRACE_CONT; - //dbg_cont(dbg, PTRACE_CONT); + + th->doing = PTRACE_SINGLESTEP; + th->donext = PTRACE_CONT; + th->clearstates = 0; } else { - dbg->cont = 0; - //dbg_stepin(dbg); + th->doing = PTRACE_SINGLESTEP; + th->donext = 0; + th->clearstates = 0; } - 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)); +int dbg_stepback(struct thread *th) { + if (!th->stopped) { + return -1; + } - 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; -} -*/ + if (th->state != th->states.head) { + th->state = th->state->prev; + 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; + return -1; } -void *deref(struct tracee *dbg, unsigned long addr, size_t size) { - (void)size; // todo +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 = &dbg->state->maps; + 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); + 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; +//} @@ -3,18 +3,20 @@ #include <sys/user.h> #include <unistd.h> -#include "console.h" #include "list.h" -#define BREAKPOINT_INSN 0xcc - struct breakpoint { LINKEDLIST; + unsigned long address; + unsigned long text; + int installed; + int hits; + int user; + unsigned long stack; - unsigned long orig; + pid_t tid; int enabled; - int active; }; struct map { @@ -27,29 +29,49 @@ struct map { struct state { LINKEDLIST; struct user_regs_struct regs; - struct user_fpregs_struct fpregs; struct list maps; }; -struct tracee { - struct list breaks; +struct process { + LINKEDLIST; + pid_t id; + int child; + struct list breakpoints; + struct list threads; + char status[128]; +}; + +struct thread { + LINKEDLIST; + struct process *proc; + struct list states; struct state *state; + int clearstates; + pid_t id; - int child; int stopped; - int status; int signal; - int cont; - void *buff; - size_t buffsize; + int doing; + int donext; + + char status[128]; }; -extern int dbg_process(struct tracee *dbg, pid_t pid); -extern int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons); -extern int dbg_wait(struct tracee *dbg); -extern int dbg_stepin(struct tracee *dbg); -extern int dbg_stepover(struct tracee *dbg); -//extern int dbg_stepout(struct tracee *dbg); -extern int dbg_cont(struct tracee *dbg, int mode); -extern void *deref(struct tracee *dbg, unsigned long addr, size_t size); +extern struct breakpoint*add_breakpoint(struct process*proc,unsigned long address); +extern struct breakpoint*get_breakpoint(struct process*proc,unsigned long address); + +extern struct process *dbg_attach(pid_t pid, int child); +extern void dbg_detach(struct process *proc); +extern int dbg_free(struct thread *th); + +extern void dbg_sync(struct process *proc); + +extern int dbg_intr(struct thread *th); +extern int dbg_cont(struct thread *th); +extern int dbg_syscall(struct thread *th); +extern int dbg_stepin(struct thread *th); +extern int dbg_stepover(struct thread *th); +extern int dbg_stepback(struct thread *th); + +extern void *deref(struct thread *th, unsigned long address, size_t size); @@ -11,6 +11,12 @@ void *xmalloc(size_t size) { return ptr; } +unsigned long strict_strtoul(const char *nptr, int base) { + char *endptr; + unsigned long ret = strtoul(nptr, &endptr, base); + return (*endptr ? 0 : ret); +} + void cursinit(void) { setlocale(LC_ALL, ""); initscr(); @@ -20,6 +26,14 @@ void cursinit(void) { curs_set(FALSE); timeout(25); refresh(); + + start_color(); + use_default_colors(); + + init_pair(1, COLOR_GREEN, -1); + init_pair(2, COLOR_CYAN, -1); + init_pair(3, COLOR_RED, -1); + init_pair(4, COLOR_YELLOW, -1); } void cursupdate(void) { @@ -57,3 +71,11 @@ int pprintw(PANEL *pan, const char *fmt, ...) { int pclear(PANEL *pan) { return wclear(panel_window(pan)); } + +int pattron(PANEL *pan, int attrs) { + return wattron(panel_window(pan), attrs); +} + +int pattroff(PANEL *pan, int attrs) { + return wattroff(panel_window(pan), attrs); +} @@ -4,7 +4,10 @@ #include <panel.h> #include <stddef.h> +#define KEY_ESCAPE 0x1b + extern void *xmalloc(size_t size); +extern unsigned long strict_strtoul(const char *nptr, int base); extern void cursinit(void); extern void cursupdate(void); @@ -13,3 +16,5 @@ extern void delpan(PANEL *pan); extern void reset_panel(PANEL *pan, int h, int w, int y, int x); extern int pprintw(PANEL *pan, const char *fmt, ...); extern int pclear(PANEL *pan); +extern int pattron(PANEL *pan, int attrs); +extern int pattroff(PANEL *pan, int attrs); @@ -1,8 +1,8 @@ #include <signal.h> -#include <stdlib.h> #include <string.h> +#include <stdio.h> +#include <stdlib.h> #include <sys/ptrace.h> -#include <linux/ptrace.h> #include <unistd.h> #include <capstone/capstone.h> @@ -10,95 +10,50 @@ #include "console.h" #include "debugger.h" #include "helpers.h" +#include "list.h" static PANEL *left, *right; -static struct console cons; -static int mode = 0; - -static void describe_status(struct tracee *_dbg, PANEL *pan) { - struct tracee dbg = *_dbg; - int status = dbg.status; - struct ptrace_syscall_info info; - - if (WIFEXITED(status)) { - pprintw(pan, "exited with code: %u\n", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - pprintw(pan, "terminated by signal: %s\n", strsignal(WTERMSIG(status))); - } else { - unsigned long msg; - if (ptrace(PTRACE_GETEVENTMSG, dbg.id, NULL, &msg) < 0) { - perror("PTRACE_GETEVENTMSG"); - } - - if (WIFSTOPPED(status)) { - switch (status >> 8) { - case ((PTRACE_EVENT_VFORK << 8) | SIGTRAP): - pprintw(pan, "child vfork()'d to: %lu\n", msg); - break; - case ((PTRACE_EVENT_FORK << 8) | SIGTRAP): - pprintw(pan, "child fork()'d to: %lu\n", msg); - break; - case ((PTRACE_EVENT_CLONE << 8) | SIGTRAP): - pprintw(pan, "child clone()'d to: %lu\n", msg); - break; - case ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP): - pprintw(pan, "finished vfork\n"); - break; - case ((PTRACE_EVENT_EXEC << 8) | SIGTRAP): - pprintw(pan, "child exec()'d from: %lu\n", msg); - break; - case ((PTRACE_EVENT_EXIT << 8) | SIGTRAP): - pprintw(pan, "exiting with code: %lu\n", msg); - break; - case ((PTRACE_EVENT_STOP << 8) | SIGTRAP): - pprintw(pan, "child stopped\n"); - break; - case ((PTRACE_EVENT_SECCOMP << 8) | SIGTRAP): - pprintw(pan, "child seccomp event\n"); - break; - case (SIGTRAP | 0x80): - ptrace(PTRACE_GET_SYSCALL_INFO, dbg.id, sizeof(info), &info); - pprintw(pan, "child entering syscall: %llu (%llx, %llx, %llx, %llx, %llx, %llx)\n", - info.entry.nr, - info.entry.args[0], - info.entry.args[1], - info.entry.args[2], - info.entry.args[3], - info.entry.args[4], - info.entry.args[5]); - break; +static pid_t parse_pid(const char *nptr) { + char *endptr; + pid_t pid = strtoul(nptr, &endptr, 0); + return (*endptr ? 0 : pid); +} - case SIGSTOP: - case SIGTSTP: - case SIGTTIN: - case SIGTTOU: - pprintw(pan, "child group-stopped\n"); - break; +static pid_t dofork(char **argv, struct console *cons) { + pid_t pid = fork(); + if (pid < 0) { + return -1; + } - default: - pprintw(pan, "received signal: %s\n", strsignal(WSTOPSIG(status))); - break; - } - } else { - pprintw(pan, "child stop event unrecognized\n"); - } + if (pid == 0) { + usleep(10000); + console_configslave(cons); + close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); + //raise(SIGSTOP); // ptrace(PTRACE_TRACEME, 0, NULL, NULL); + execvp(argv[0], argv); + exit(EXIT_FAILURE); } + + return pid; } -static void list_breakpoints(struct tracee *dbg, PANEL *pan) { - struct list *breaks = &dbg->breaks; +static void list_breakpoints(struct thread *dbg, PANEL *pan) { + struct list *breaks = &dbg->proc->breakpoints; if (breaks->head != breaks->end) { pprintw(pan, "---\n"); for (struct breakpoint *bp=breaks->head; bp!=breaks->end; bp=bp->next) { - pprintw(pan, "0x%lx ", bp->address); + pprintw(pan, "0x%lx (%c) (%i) ", + bp->address, + (bp->installed ? '*' : ' '), + bp->hits); } pprintw(pan, "\n"); } } -static void describe_states(struct tracee *dbg, PANEL *pan) { +static void describe_states(struct thread *dbg, PANEL *pan) { struct list *states = &dbg->states; for (struct state *s = states->head; s != states->end; s = s->next) { pprintw(pan, "%c", (s == dbg->state ? '#' : '-')); @@ -106,8 +61,9 @@ static void describe_states(struct tracee *dbg, PANEL *pan) { pprintw(pan, "\n"); } -static void dump_registers(struct tracee *dbg, PANEL *pan) { +static void dump_registers(struct thread *dbg, PANEL *pan) { struct user_regs_struct *regs = &dbg->state->regs; + pprintw(pan, "orig_rax = 0x%016llx\n", regs->orig_rax); pprintw(pan, "rax = 0x%016llx\n", regs->rax); pprintw(pan, "rbx = 0x%016llx\n", regs->rbx); pprintw(pan, "rcx = 0x%016llx\n", regs->rcx); @@ -119,7 +75,7 @@ static void dump_registers(struct tracee *dbg, PANEL *pan) { pprintw(pan, "rip = 0x%016llx\n", regs->rip); } -static void dump_stack(struct tracee *dbg, PANEL *pan) { +static void dump_stack(struct thread *dbg, PANEL *pan) { unsigned long sp = dbg->state->regs.rsp; for (size_t i = 0; i < 16; i++, sp += 8) { unsigned long word = *(unsigned long *)deref(dbg, sp,sizeof(unsigned long)); @@ -127,23 +83,51 @@ static void dump_stack(struct tracee *dbg, PANEL *pan) { } } -static void disasm(struct tracee *dbg, PANEL *pan) { +static int rip_visited(struct thread *th, unsigned long rip) { + struct list *states = &th->states; + for (struct state *s = states->head; s != states->end; s = s->next) { + if (rip == s->regs.rip) { + return 1; + } + } + return 0; +} + +static void disasm(struct thread *dbg, PANEL *pan) { csh handle; cs_insn *insn; if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) { - perror("capstone open"); + //perror("capstone open"); } else { uint64_t address = dbg->state->regs.rip; size_t codez = 128; const uint8_t *code = deref(dbg, address, codez); insn = cs_malloc(handle); - for (size_t i = 0; i < 16; i++) { + for (size_t i = 0; i < 32; i++) { if (!cs_disasm_iter(handle, &code, &codez, &address, insn)) { break; } + if (dbg->stopped) { + if (insn->address == dbg->state->regs.rip) { + pattron(pan, COLOR_PAIR(1)); + } else if (get_breakpoint(dbg->proc, insn->address)) { + pattron(pan, COLOR_PAIR(3)); + } else if (rip_visited(dbg, insn->address)) { + pattron(pan, COLOR_PAIR(2)); + } + } pprintw(pan, "0x%"PRIx64":\t%s %s\n", insn->address, insn->mnemonic, insn->op_str); + if (dbg->stopped) { + if (insn->address == dbg->state->regs.rip) { + pattroff(pan, COLOR_PAIR(1)); + } else if (get_breakpoint(dbg->proc, insn->address)) { + pattroff(pan, COLOR_PAIR(3)); + } else if (rip_visited(dbg, insn->address)) { + pattroff(pan, COLOR_PAIR(2)); + } + } } cs_free(insn, 1); @@ -151,66 +135,117 @@ static void disasm(struct tracee *dbg, PANEL *pan) { } } -static void info_update(struct tracee *dbg, PANEL *pan) { +static void info_update(struct thread *th, PANEL *pan) { pclear(pan); - pprintw(pan, "PID: %li\n", (long)dbg->id); - if (!dbg->stopped) { - pprintw(pan, "Process is running...\n"); - } else { - describe_status(dbg, pan); - list_breakpoints(dbg, pan); - describe_states(dbg, pan); - dump_registers(dbg, pan); - pprintw(pan, "---\n"); - dump_stack(dbg, pan); - pprintw(pan, "---\n"); - disasm(dbg, pan); + pprintw(pan, "TID: %li\n", (long)th->id); + pprintw(pan, "%s (%i: %s)\n", th->status, th->signal, strsignal(th->signal)); + list_breakpoints(th, pan); + describe_states(th, pan); + dump_registers(th, pan); + pprintw(pan, "---\n"); + dump_stack(th, pan); + pprintw(pan, "---\n"); + disasm(th, pan); +} + +static void wait_all_threads(struct list *processes) { + for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) { + dbg_sync(proc); } } -static void layout(void) { +static void layout(struct list *processes, struct thread *th) { int w = COLS/2; - reset_panel(left, LINES-1, w, 0, 0); - reset_panel(right, LINES-1, COLS-w, 0, w); + reset_panel(left, LINES-2, w, 1, 0); + reset_panel(right, LINES-2, COLS-w, 1, w); + + clear(); + + for (struct process *proc = processes->head; proc != processes->end; proc = proc->next) { + if (th->proc == proc) { + attron(COLOR_PAIR(1)); + printw("{ "); + } else { + attron(COLOR_PAIR(4)); + printw("{ "); + attroff(COLOR_PAIR(4)); + } + + struct list *threads = &proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + if (t == th) { + printw("**"); + } + printw("%li (%s)", (long)t->id, t->status); + if (t == th) { + printw("**"); + } + + if (t->next != threads->end) { + printw(" "); + } + } + + + if (th->proc == proc) { + printw(" } "); + attroff(COLOR_PAIR(1)); + } else { + attron(COLOR_PAIR(4)); + printw(" } "); + attroff(COLOR_PAIR(4)); + } + } } int main(int argc, char **argv) { - if (argc < 3) { - fprintf(stderr, "Usage: %s <stop> <command>\n", argv[0]); - return 1; + if (argc < 2) { + fprintf(stderr, "Usage: %s <pid | cmdline>\n", argv[0]); + return EXIT_FAILURE; } - cursinit(); - left = newpan(0, 0, 0, 0); - right = newpan(0, 0, 0, 0); - layout(); + getchar(); + struct console cons = {0}; console_init(&cons); + struct list processes = {0}; + struct thread *th = NULL; + list_init(&processes); + + int child = 0; + pid_t pid = parse_pid(argv[1]); argv[argc] = NULL; - struct tracee dbg; - if (dbg_new_process(&dbg, argv+2, &cons)) { - pprintw(right, "Failed to start child\n"); + + if (pid == 0) { + pid = dofork(argv+1, &cons); + child = 1; + } + + struct process *proc = dbg_attach(pid, child); + if (!proc) { + fprintf(stderr, "Failed to attach to process %li\n", (long)pid); + return EXIT_FAILURE; } + list_insert(processes.end, proc); + th = proc->threads.head; - unsigned long stop = strtoul(argv[1], NULL, 0); - if (stop != 0) { - struct breakpoint *b = xmalloc(sizeof(*b)); - b->address = stop; - b->stack = 0; - b->enabled = -1; - b->active = 0; - list_insert(dbg.breaks.end, b); - dbg_cont(&dbg, PTRACE_CONT); + if (child) { + dbg_cont(th); } + cursinit(); + left = newpan(0, 0, 0, 0); + right = newpan(0, 0, 0, 0); + layout(&processes, th); + int quit = 0; + int mode = 0; while (!quit) { - //if (dbg.stopped == NULL) { - dbg_wait(&dbg); - //} + wait_all_threads(&processes); + layout(&processes, th); console_update(&cons, right); - info_update(&dbg, left); + info_update(th, left); cursupdate(); int ch = getch(); @@ -218,7 +253,7 @@ int main(int argc, char **argv) { if (mode == 0) { switch (ch) { case KEY_RESIZE: - layout(); + layout(&processes, th); break; case 'q': quit = 1; @@ -228,59 +263,72 @@ int main(int argc, char **argv) { console_enter(&cons, right); break; case 'j': - if (dbg.stopped) { - if (dbg.state != dbg.states.tail) { - dbg.state = dbg.state->next; - } else { - dbg_stepover(&dbg); - } - } + dbg_stepover(th); break; case 'k': - if (dbg.stopped) { - if (dbg.state != dbg.states.head) { - dbg.state = dbg.state->prev; - } - } + dbg_stepback(th); break; case 'l': - if (dbg.stopped) { - if (dbg.state != dbg.states.tail) { - //dbg.state = dbg.state->next; - } else { - dbg_stepin(&dbg); - } - } + dbg_stepin(th); + break; + case 'h': + /* todo: step out */ break; - //case 'h': - // if (dbg.stopped) { - // dbg_stepout(&dbg); - // } - // break; case 'g': - if (dbg.stopped) { - dbg.state = dbg.states.head; + if (th->stopped) { + th->state = th->states.head; } break; case 'G': - if (dbg.stopped) { - dbg.state = dbg.states.tail; + if (th->stopped) { + th->state = th->states.tail; } break; case 's': - if (dbg.stopped) { - dbg_cont(&dbg, PTRACE_SYSCALL); - } + dbg_syscall(th); break; case 'c': - if (dbg.stopped) { - dbg_cont(&dbg, PTRACE_CONT); - } + dbg_cont(th); break; case 'p': - if (!dbg.stopped) { - tgkill(dbg.id, dbg.id, SIGSTOP); + dbg_intr(th); + break; + case 't': + proc = th->proc; + th = th->next; + if (th == proc->threads.end) { + do { + proc = proc->next; + } while (proc == processes.end); + th = proc->threads.head; } + layout(&processes, th); + break; + case 'T': + proc = th->proc; + th = th->prev; + if (th == proc->threads.end) { + do { + proc = proc->prev; + } while (proc == processes.end); + th = proc->threads.tail; + } + layout(&processes, th); + break; + case 'd': + proc = th->proc; + if (proc->child) { + break; + } + if (proc->prev == proc->next) { + break; + } + struct process *del = proc; + do { + proc = proc->next; + } while (proc == processes.end); + th = proc->threads.head; + dbg_detach(del); break; case ':': mvprintw(LINES-1, 0, ":"); @@ -293,27 +341,33 @@ int main(int argc, char **argv) { noecho(); timeout(25); clear(); - refresh(); + layout(&processes, th); char *t = cmd; - struct breakpoint *b = xmalloc(sizeof(*b)); - b->enabled = 1; - b->active = 0; + int en = 1; + pid_t tid = 0; if (t[0] == '!') { - b->enabled = -1; + en = -1; + t++; + } else if (t[0] == '#') { + en = 0; + t++; + } else if (t[0] == '@') { + tid = th->id; t++; } - b->address = strtoul(t, NULL, 0); - b->stack = 0; - list_insert(dbg.breaks.end, b); + unsigned long address = strtoul(t, NULL, 0); + struct breakpoint *b = add_breakpoint(th->proc, address); + b->enabled = en; + b->tid = tid; break; } } else { switch (ch) { case KEY_RESIZE: - layout(); + layout(&processes, th); break; - case 0x1b: + case KEY_ESCAPE: mode = 0; console_leave(&cons, right); break; @@ -326,6 +380,12 @@ int main(int argc, char **argv) { } } + for (struct process *p = processes.head; p != processes.end; p = p->next) { + struct process *del = p; + p = p->prev; + dbg_detach(del); + } + endwin(); - return 0; + return EXIT_SUCCESS; } |