diff options
author | Malfurious <m@lfurio.us> | 2023-07-20 14:55:13 -0400 |
---|---|---|
committer | Malfurious <m@lfurio.us> | 2024-04-24 13:31:08 -0400 |
commit | d070fde6478431c71fb4a55e783a577439c7cb99 (patch) | |
tree | 1ff5f980eca499518943e4c36e5103d8be1847a8 /debugger.c | |
parent | 1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be (diff) | |
download | misplays-d070fde6478431c71fb4a55e783a577439c7cb99.tar.gz misplays-d070fde6478431c71fb4a55e783a577439c7cb99.zip |
Multithread version 1
Signed-off-by: Malfurious <m@lfurio.us>
Diffstat (limited to 'debugger.c')
-rw-r--r-- | debugger.c | 744 |
1 files changed, 565 insertions, 179 deletions
@@ -1,5 +1,8 @@ +#include <dirent.h> +#include <errno.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sys/ptrace.h> #include <sys/uio.h> #include <sys/wait.h> @@ -10,18 +13,117 @@ #include "debugger.h" #include "helpers.h" -static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD; +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); + } -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); + ptrace(PTRACE_GETREGS, th->id, NULL, &s->regs); + ptrace(PTRACE_GETFPREGS, th->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); + 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) { @@ -33,7 +135,7 @@ static void capture_state(struct tracee *dbg) { 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) { + if (process_vm_readv(th->id, &loc, 1, &rem, 1, 0) < 0) { free(m->data); free(m); continue; @@ -46,232 +148,304 @@ static void capture_state(struct tracee *dbg) { } } -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); +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)) {} } - 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; +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); +} - data = (data & ~0xff) | BREAKPOINT_INSN; - ptrace(PTRACE_POKETEXT, dbg->id, b->address, data); - b->active = 1; +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; } -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); - } +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); - 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; + char taskpath[32]; + snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); - /* detect stop at breakpoint */ - if (stop && !ch) { - if (regs.rip - 1 == b->address) { - regs.rip--; - ptrace(PTRACE_SETREGS, dbg->id, NULL, ®s); - ch = 1; + DIR *taskdir = opendir(taskpath); + if (!taskdir) { + return -1; + } - if (b->stack != 0 && b->stack != regs.rsp) { - reenter = 1; - } - } - } + struct dirent *task; + while ((task = readdir(taskdir))) { + pid_t id = strtoul(task->d_name, NULL, 0); - if (b->enabled < 0 && !reenter) { - struct breakpoint *del = b; - b = b->prev; - list_remove(del); - free(del); + 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); } } - return reenter; -} - -int dbg_process(struct tracee *dbg, pid_t pid) { - if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { - return -1; + if (INTERRUPT_ON_SEIZE) { + interrupt_all_threads(proc); } - 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); + closedir(taskdir); return 0; } -int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons) { - pid_t pid = fork(); - if (pid < 0) { - return -1; - } +int dbg_detach(struct process *proc) { + interrupt_all_threads(proc); + uninstall_breakpoints(proc); + clear_breakpoints(proc); - 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); + 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); } - 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; +int dbg_wait(struct thread *th, int dostops) { + if (th->id < 0) { + return -1; } - if (waitpid(dbg->id, &dbg->status, WNOHANG | __WALL) <= 0) { + int status; + if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; } - if (dbg->cont != 0) { - install_breakpoints(dbg); - ptrace(dbg->cont, dbg->id, NULL, NULL); - dbg->cont = 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 (uninstall_breakpoints(dbg, 1)) { - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - dbg->cont = PTRACE_CONT; - return 0; + if (WIFSIGNALED(status)) { + th->id = -2; + th->stopped = 1; + th->signal = WTERMSIG(status); + th->cont = 0; + th->status = "TERMINATED"; + return 1; } - capture_state(dbg); - dbg->stopped = 1; - 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); + } -/* note: move head < state < tail checks from main into these function - * add a dbg_stepback function */ + capture_state(th); + return 1; -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; + 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_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); +int dbg_cont(struct thread *th, int cont) { + if (th->id < 0 || !th->stopped) { + return -1; } - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - dbg->stopped = 0; + 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; + } - cs_free(insn, 1); - cs_close(&handle); + /* 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_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_stepin(struct thread *th) { + if (th->id < 0 || !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); + if (ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal) < 0) { + return -1; + } - ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); - //clear_states(dbg); // keep this? - dbg->stopped = 0; - dbg->cont = PTRACE_CONT; + th->stopped = 0; + th->signal = 0; + th->cont = 0; + th->status = "RUNNING"; + th->clearstates = 0; 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; +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 tracee *dbg, unsigned long addr, size_t size) { - (void)size; // todo +void *deref(struct thread *th, unsigned long addr, size_t size) { + (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); @@ -280,3 +454,215 @@ void *deref(struct tracee *dbg, unsigned long addr, size_t size) { 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); +//} |