diff options
author | Malfurious <m@lfurio.us> | 2023-09-19 11:02:03 -0400 |
---|---|---|
committer | Malfurious <m@lfurio.us> | 2024-04-24 13:31:08 -0400 |
commit | b4abda51217101ceffd19c3d403e40781e15dcec (patch) | |
tree | 1418becd7e2d8437fee1f5f3eaa27f7c6e1bb690 /debugger.c | |
parent | d070fde6478431c71fb4a55e783a577439c7cb99 (diff) | |
download | misplays-b4abda51217101ceffd19c3d403e40781e15dcec.tar.gz misplays-b4abda51217101ceffd19c3d403e40781e15dcec.zip |
Multithread version 2
Signed-off-by: Malfurious <m@lfurio.us>
Diffstat (limited to 'debugger.c')
-rw-r--r-- | debugger.c | 468 |
1 files changed, 191 insertions, 277 deletions
@@ -1,39 +1,41 @@ #include <dirent.h> -#include <errno.h> -#include <stdio.h> +#include <signal.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 | PTRACE_O_TRACECLONE; -static const int INTERRUPT_ON_SEIZE = 1; +#define BREAKPOINT_INSN 0xcc + +//struct list global_processes = {0}; +//struct thread *global_thread = NULL; + +static const int PTRACE_OPTIONS = + PTRACE_O_TRACECLONE | + PTRACE_O_TRACEEXIT | + PTRACE_O_TRACESYSGOOD; static int detect_breakpoint(struct thread *th) { - struct user_regs_struct regs; int check = 0; int restart = 0; + struct user_regs_struct regs; 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 (b->installed) { if (!check) { if (regs.rip - 1 == b->address) { regs.rip--; ptrace(PTRACE_SETREGS, th->id, NULL, ®s); check = 1; + b->hits++; if (b->stack != 0 && b->stack != regs.rsp) { restart = 1; @@ -42,9 +44,15 @@ static int detect_breakpoint(struct thread *th) { if (b->tid != 0 && b->tid != th->id) { restart = 1; } + + if (!b->enabled) { + restart = 1; + } } } + /* this needs moved, we have to finish the for-loop + * to actually know if restart is true or false... */ if (b->enabled < 0 && !restart) { struct breakpoint *del = b; b = b->prev; @@ -57,17 +65,17 @@ static int detect_breakpoint(struct thread *th) { return restart; } -static void install_breakpoints(struct thread *th) { - struct list *breaks = &th->proc->breakpoints; +static void install_breakpoints(struct process *proc) { + struct list *breaks = &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; + if (!b->installed) { + unsigned long word; + word = ptrace(PTRACE_PEEKTEXT, proc->id, b->address, NULL); + b->text = word; + + word = (word & ~0xff) | BREAKPOINT_INSN; + ptrace(PTRACE_POKETEXT, proc->id, b->address, word); + b->installed = 1; } } } @@ -75,14 +83,14 @@ static void install_breakpoints(struct thread *th) { 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; + if (b->installed) { + ptrace(PTRACE_POKETEXT, proc->id, b->address, b->text); + b->installed = 0; } } } -static void clear_breakpoints(struct process *proc) { +static void free_breakpoints(struct process *proc) { while (proc->breakpoints.head != proc->breakpoints.end) { struct breakpoint *b = proc->breakpoints.head; list_remove(b); @@ -90,7 +98,7 @@ static void clear_breakpoints(struct process *proc) { } } -static void clear_states(struct thread *th) { +static void free_states(struct thread *th) { while (th->states.head != th->states.end) { struct state *s = th->states.head; @@ -109,9 +117,9 @@ static void clear_states(struct thread *th) { th->clearstates = 0; } -static void capture_state(struct thread *th) { +static void capture_state_thread(struct thread *th) { if (th->clearstates) { - clear_states(th); + free_states(th); } struct state *s = xmalloc(sizeof(*s)); @@ -148,13 +156,33 @@ static void capture_state(struct thread *th) { } } -static struct thread *new_thread(struct process *proc, pid_t id) { +static void capture_state(struct thread *th, int all) { + if (all) { + struct list *threads = &th->proc->threads; + for (struct thread *t = threads->head; t != threads->end; t = t->next) { + capture_state_thread(t); + } + } else { + capture_state_thread(th); + } +} + +static struct process *new_process(pid_t pid, int child) { + struct process *proc = xmalloc(sizeof(*proc)); + proc->id = pid; + proc->child = child; + list_init(&proc->threads); + list_init(&proc->breakpoints); + return proc; +} + +static struct thread *new_thread(struct process *proc, pid_t tid) { struct thread *th = xmalloc(sizeof(*th)); th->proc = proc; list_init(&th->states); th->state = NULL; th->clearstates = 0; - th->id = id; + th->id = tid; th->stopped = 0; th->signal = 0; th->cont = 0; @@ -162,24 +190,28 @@ static struct thread *new_thread(struct process *proc, pid_t id) { return th; } -static void interrupt_all_threads(struct process *proc) { +static int interrupt_all_threads(struct process *proc) { + int stopped = 0; 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)) {} + while (!dbg_wait(th, 1)) {} + stopped = 1; } } + return stopped; } 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->text = 0; + b->installed = 0; + b->hits = 0; b->stack = stack; b->tid = tid; b->enabled = enabled; - b->active = 0; list_insert(proc->breakpoints.end, b); } @@ -193,18 +225,27 @@ int is_breakpoint(struct process *proc, unsigned long address) { 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); +//int dbg_attach(pid_t pid, int child) { +struct process *dbg_attach(pid_t pid, int child) { + //if (global_processes.end == NULL) { + // list_init(&global_processes); + //} + + struct process *proc = new_process(pid, child); + //list_insert(global_processes.end, proc); + + int options = PTRACE_OPTIONS; + if (child) { + options |= PTRACE_O_EXITKILL; + } char taskpath[32]; snprintf(taskpath, sizeof(taskpath), "/proc/%li/task", (long)pid); DIR *taskdir = opendir(taskpath); if (!taskdir) { - return -1; + dbg_detach(proc); + return NULL; } struct dirent *task; @@ -212,9 +253,10 @@ int dbg_process(struct process *proc, pid_t pid, int child) { pid_t id = strtoul(task->d_name, NULL, 0); if (id != 0) { - if (ptrace(PTRACE_SEIZE, id, NULL, PTRACE_OPTIONS) < 0) { + if (ptrace(PTRACE_SEIZE, id, NULL, options) < 0) { closedir(taskdir); - return -1; + dbg_detach(proc); + return NULL; } struct thread *th = new_thread(proc, id); @@ -222,37 +264,44 @@ int dbg_process(struct process *proc, pid_t pid, int child) { } } - if (INTERRUPT_ON_SEIZE) { - interrupt_all_threads(proc); - } - closedir(taskdir); - return 0; + + //global_thread = proc->threads.head; + interrupt_all_threads(proc); + return proc; } int dbg_detach(struct process *proc) { interrupt_all_threads(proc); uninstall_breakpoints(proc); - clear_breakpoints(proc); + free_breakpoints(proc); + + if (proc->child) { + kill(proc->id, SIGKILL); + } 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); + free_states(th); list_remove(th); free(th); } + list_remove(proc); + free(proc); return 0; } -int dbg_wait(struct thread *th, int dostops) { +int dbg_wait(struct thread *th, int recursion) { if (th->id < 0) { return -1; } + /* todo: check what happens if we call on an already known + * stopped thread? */ int status; if (waitpid(th->id, &status, __WALL | WNOHANG) <= 0) { return 0; @@ -276,42 +325,45 @@ int dbg_wait(struct thread *th, int dostops) { return 1; } - unsigned long eventmsg; + int stopped; struct thread *newth; + + unsigned long eventmsg; ptrace(PTRACE_GETEVENTMSG, th->id, NULL, &eventmsg); if (WIFSTOPPED(status)) { switch (status >> 8) { + /* todo: other ptrace event stops */ case SIGTRAP | (PTRACE_EVENT_CLONE << 8): newth = new_thread(th->proc, eventmsg); list_insert(th->proc->threads.end, newth); - while (!dbg_wait(newth, 0)) {} + while (!dbg_wait(newth, 1)) {} th->stopped = 1; th->signal = 0; th->cont = 0; th->status = "CLONE EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP | (PTRACE_EVENT_EXIT << 8): th->stopped = 1; - th->signal = 0; //eventmsg; + th->signal = 0; /* eventmsg has exit code, but would inject sig */ th->cont = 0; th->status = "EXIT EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP | (PTRACE_EVENT_STOP << 8): @@ -320,12 +372,12 @@ int dbg_wait(struct thread *th, int dostops) { th->cont = 0; th->status = "STOP EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP | 0x80: @@ -334,20 +386,23 @@ int dbg_wait(struct thread *th, int dostops) { th->cont = 0; th->status = "SYSCALL EVENT"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; case SIGTRAP: th->stopped = 1; th->signal = 0; + th->status = "STEP/BREAKPOINT"; if (th->cont != 0) { - install_breakpoints(th); + /* gdb this portion. are there race conditions + * that matter?? */ + install_breakpoints(th->proc); ptrace(th->cont, th->id, NULL, NULL); th->cont = 0; th->stopped = 0; @@ -355,18 +410,23 @@ int dbg_wait(struct thread *th, int dostops) { return 0; } - if (dostops) { - interrupt_all_threads(th->proc); + /* todo: Test two threads hitting a breakpoint at + * the same time. */ + int restart = detect_breakpoint(th); + + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + if (!restart) { + capture_state(th, stopped); + } } - if (detect_breakpoint(th)) { + if (restart) { dbg_cont(th, PTRACE_CONT); return 0; } - th->status = "STEP/BREAKPOINT"; - capture_state(th); return 1; default: @@ -375,12 +435,12 @@ int dbg_wait(struct thread *th, int dostops) { th->cont = 0; th->status = "SIGNAL DELIVERY"; - if (dostops) { - interrupt_all_threads(th->proc); + if (!recursion) { + stopped = interrupt_all_threads(th->proc); uninstall_breakpoints(th->proc); + capture_state(th, stopped); } - capture_state(th); return 1; } } @@ -388,6 +448,15 @@ int dbg_wait(struct thread *th, int dostops) { return -1; } +int dbg_intr(struct thread *th) { + if (th->id < 0 || th->stopped) { + return -1; + } + + ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); + return 0; +} + int dbg_cont(struct thread *th, int cont) { if (th->id < 0 || !th->stopped) { return -1; @@ -404,51 +473,81 @@ int dbg_cont(struct thread *th, int cont) { 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) { +int dbg_step(struct thread *th, int stepover) { + // todo: support step-out + //if (th->id < 0 || !th->stopped) { + // return -1; + //} + + if (!th->stopped) { return -1; } - if (ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal) < 0) { + if (th->state != th->states.tail) { + th->state = th->state->next; + return 0; + } + + if (th->id < 0) { return -1; } + csh cshandle; + cs_open(CS_ARCH_X86, CS_MODE_64, &cshandle); + cs_insn *insn = cs_malloc(cshandle); + + uint64_t address = th->state->regs.rip; + size_t size = 128; + const uint8_t *code = deref(th, address, size); + + cs_disasm_iter(cshandle, &code, &size, &address, insn); + + if (insn->id == X86_INS_CALL && stepover) { + add_breakpoint(th->proc, address, th->state->regs.rsp, th->id, -1); + th->cont = PTRACE_CONT; + } else { + th->cont = 0; + } + + ptrace(PTRACE_SINGLESTEP, th->id, NULL, th->signal); + th->stopped = 0; th->signal = 0; - th->cont = 0; th->status = "RUNNING"; - th->clearstates = 0; + + cs_free(insn, 1); + cs_close(&cshandle); return 0; } -int dbg_intr(struct thread *th) { - if (th->id < 0 || th->stopped) { +int dbg_pets(struct thread *th) { + if (!th->stopped) { return -1; } - ptrace(PTRACE_INTERRUPT, th->id, NULL, NULL); - return 0; + if (th->state != th->states.head) { + th->state = th->state->prev; + return 0; + } + + return -1; } -void *deref(struct thread *th, unsigned long addr, size_t size) { +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 <= 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); } } @@ -459,164 +558,11 @@ void *deref(struct thread *th, unsigned long addr, size_t size) { - - - - - - - - - - -///* -//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)); @@ -634,35 +580,3 @@ void *deref(struct thread *th, unsigned long addr, size_t size) { // 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); -//} |