#include #include #include #include #include #include #include #include #include #include #include #include #include #include "linenoise.h" #include "mujs.h" #include "hash.h" #include "pmparser.h" #define LOG_ERR(fmt, ...) fprintf(stderr, "Error: " fmt, ##__VA_ARGS__) #define LOG_INF(fmt, ...) fprintf(stdout, "Info: " fmt, ##__VA_ARGS__) #define unused(x) ((void)(x)) #define shift(argc, argv, msg, ...) (argc <= 0 ? (LOG_ERR(msg, ##__VA_ARGS__), exit(EXIT_FAILURE)) : (void)0, argc--, *argv++) #define INIT_SCRIPT ".debugusrc.js" // Registers typedef enum { rax, rbx, rcx, rdx, rdi, rsi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15, rip, rflags, cs, orig_rax, fs_base, gs_base, fs, gs, ss, ds, es, MAX_REGISTERS, } Register; typedef struct { Register r; int dwarf_r; const char *name; } RegisterDescriptor; static RegisterDescriptor reg_descriptors[MAX_REGISTERS] = { #define make_rd(r, dr) { r, dr, #r } make_rd(orig_rax, -1), make_rd(rip, -1), make_rd(rax, 0), make_rd(rdx, 1), make_rd(rcx, 2), make_rd(rbx, 3), make_rd(rsi, 4), make_rd(rdi, 5), make_rd(rbp, 6), make_rd(rsp, 7), make_rd(r8, 8), make_rd(r9, 9), make_rd(r10, 10), make_rd(r11, 11), make_rd(r12, 12), make_rd(r13, 13), make_rd(r14, 14), make_rd(r15, 15), make_rd(rflags, 49), make_rd(es, 50), make_rd(cs, 51), make_rd(ss, 52), make_rd(ds, 53), make_rd(fs, 54), make_rd(gs, 55), make_rd(fs_base, 58), make_rd(gs_base, 59), #undef make_rd }; uint64_t get_reg_value(pid_t pid, Register r) { struct user_regs_struct rs; ptrace(PTRACE_GETREGS, pid, NULL, &rs); for (int i = 0; i < sizeof(reg_descriptors)/sizeof(reg_descriptors[0]); i++) { if (reg_descriptors[i].r == r) { return ((uint64_t*)&rs)[i]; } } } void set_reg_value(pid_t pid, Register r, uint64_t v) { struct user_regs_struct rs; ptrace(PTRACE_GETREGS, pid, NULL, &rs); for (int i = 0; i < sizeof(reg_descriptors)/sizeof(reg_descriptors[0]); i++) { if (reg_descriptors[i].r == r) { ((uint64_t*)&rs)[i] = v; ptrace(PTRACE_SETREGS, pid, NULL, &rs); return; } } } const char *get_reg_name(Register r) { for (int i = 0; i < sizeof(reg_descriptors)/sizeof(reg_descriptors[0]); i++) { if (reg_descriptors[i].r == r) { return reg_descriptors[i].name; } } } Register get_reg_from_name(const char *name) { for (int i = 0; i < MAX_REGISTERS; i++) { if (strcmp(name, reg_descriptors[i].name) == 0) { return reg_descriptors[i].r; } } } // Memory uint64_t mem_read(pid_t pid, uint64_t addr) { return ptrace(PTRACE_PEEKDATA, pid, addr, NULL); } void mem_write(pid_t pid, uint64_t addr, uint64_t v) { ptrace(PTRACE_POKEDATA, pid, addr, v); } // How breakpoints work? // We can enable/disable breakpoints by putting/removing an int 3 instruction // into/from the executed program. int 3 will trigger a SIGTRAP, which we can // then handle. Pretty cool, eh? // // To handle a SIGTRAP, we can use waitpid(2) to listen for child process' events. #define MAX_BRKS 0xff typedef struct { pid_t pid; pid_t proc_pid; uintptr_t addr; bool enabled; uint8_t data; } Brk; void brk_enable(Brk *brk) { long data = ptrace(PTRACE_PEEKDATA, brk->pid, brk->addr, NULL); brk->data = (uint8_t)(data&0xff); uint64_t int3 = 0xCC; // int 3 instruction, SIGTRAP uint64_t data_with_int3 = ((data& ~0xff) | int3); ptrace(PTRACE_POKEDATA, brk->pid, brk->addr, data_with_int3); brk->enabled = true; } void brk_disable(Brk *brk) { long data = ptrace(PTRACE_PEEKDATA, brk->pid, brk->addr, NULL); long restored_data = ((data& ~0xff) | brk->data); ptrace(PTRACE_POKEDATA, brk->pid, brk->addr, restored_data); brk->enabled = false; } typedef struct { const char *file; pid_t pid; js_State *js; HashTable brks; uintptr_t program_load_offset; } Dbg; #define getdbg() ((Dbg*)js_currentfunctiondata(js)) void dbg_js_cont(js_State *js) { Dbg *dbg = getdbg(); ptrace(PTRACE_CONT, dbg->pid, NULL, NULL); int status, options = 0; waitpid(dbg->pid, &status, options); js_pushundefined(js); } void dbg_js_mk_brk_addr(js_State *js) { Dbg *dbg = getdbg(); const char *addr_str = js_tostring(js, 1); uintptr_t addr; sscanf(addr_str, "0x%"SCNxPTR, &addr); Brk brk = { .pid = dbg->pid, .addr = dbg->program_load_offset + addr }; brk_enable(&brk); hashtable_set(&dbg->brks, addr_str, &brk, sizeof(brk)); done: js_pushundefined(js); } void dbg_js_rm_brk_addr(js_State *js) { Dbg *dbg = getdbg(); const char *addr_str = js_tostring(js, 1); Brk *brk = (Brk *)hashtable_get(&dbg->brks, addr_str); if (brk == NULL) { LOG_ERR("No breakpoint at address: %s\n", addr_str); goto done; } brk_disable(brk); hashtable_delete(&dbg->brks, addr_str); done: js_pushundefined(js); } void dbg_js_set_program_load_offset(js_State *js) { Dbg *dbg = getdbg(); const char *addr_str = js_tostring(js, 1); uintptr_t addr; sscanf(addr_str, "0x%"SCNxPTR, &addr); dbg->program_load_offset = addr; js_pushundefined(js); } void dbg_js_get_program_load_offset(js_State *js) { Dbg *dbg = getdbg(); js_pushnumber(js, dbg->program_load_offset); } void dbg_js_list_brks(js_State *js) { Dbg *dbg = getdbg(); int c = 0; for (int i = 0; i < dbg->brks.capacity; i++) { if (dbg->brks.buckets[i] != NULL) { Brk *brk = (Brk*)dbg->brks.buckets[i]->value; LOG_INF("Breakpoint %d (%s) at 0x%"PRIxPTR"\n", c+1, dbg->brks.buckets[i]->key, brk->addr); c++; } } js_pushundefined(js); } void dbg_js_log_inf(js_State *js) { const char *str = js_tostring(js, 1); LOG_INF("%s\n", str); js_pushundefined(js); } void dbg_js_log_err(js_State *js) { const char *str = js_tostring(js, 1); LOG_ERR("%s\n", str); js_pushundefined(js); } void dbg_js_load_script(js_State *js) { void dbg_load_script(Dbg *dbg, const char *script_path); Dbg *dbg = getdbg(); const char *path = js_tostring(js, 1); dbg_load_script(dbg, path); js_pushundefined(js); } void dbg_js_get_file(js_State *js) { Dbg *dbg = getdbg(); js_pushstring(js, dbg->file); } void dbg_js_get_pid(js_State *js) { Dbg *dbg = getdbg(); js_pushnumber(js, dbg->pid); } void dbg_js_get_reg(js_State *js) { Dbg *dbg = getdbg(); const char *name = js_tostring(js, 1); Register r = get_reg_from_name(name); uint64_t v = get_reg_value(dbg->pid, r); char buf[20]; snprintf(buf, sizeof(buf), "0x%016"PRIx64, v); js_pushstring(js, buf); } void dbg_js_set_reg(js_State *js) { Dbg *dbg = getdbg(); const char *name = js_tostring(js, 1); Register r = get_reg_from_name(name); const char *value_str = js_tostring(js, 2); uint64_t value; sscanf(value_str, "0x%"SCNx64, &value); set_reg_value(dbg->pid, r, value); js_pushundefined(js); } void dbg_js_mem_read(js_State *js) { Dbg *dbg = getdbg(); const char *addr_str = js_tostring(js, 1); uintptr_t addr; sscanf(addr_str, "0x%"SCNxPTR, &addr); uint64_t v = mem_read(dbg->pid, addr); char buf[20]; snprintf(buf, sizeof(buf), "0x%"PRIx64, v); js_pushstring(js, buf); } void dbg_js_mem_write(js_State *js) { Dbg *dbg = getdbg(); const char *addr_str = js_tostring(js, 1); uintptr_t addr; sscanf(addr_str, "0x%"SCNxPTR, &addr); const char *value_str = js_tostring(js, 2); uint64_t value; sscanf(value_str, "0x%"SCNx64, &value); mem_write(dbg->pid, addr, value); js_pushundefined(js); } void dbg_init_js(Dbg *dbg) { dbg->js = js_newstate(NULL, NULL, JS_STRICT); #define make_js_func(name, n) \ do { \ js_newcfunctionx(dbg->js, &dbg_js_##name, #name, n, dbg, NULL); \ js_setglobal(dbg->js, #name); \ } while(0) make_js_func(cont, 0); make_js_func(mk_brk_addr, 1 /*addr*/); make_js_func(rm_brk_addr, 1 /*addr*/); make_js_func(list_brks, 0); make_js_func(set_program_load_offset, 1 /*offset*/); make_js_func(log_inf, 1 /*string*/); make_js_func(log_err, 1 /*string*/); make_js_func(load_script, 1 /*path*/); make_js_func(get_file, 0); make_js_func(get_pid, 0); make_js_func(get_program_load_offset, 0); make_js_func(get_reg, 1 /*reg name*/); make_js_func(set_reg, 2 /* reg name, value*/); make_js_func(mem_read, 1 /*addr*/); make_js_func(mem_write, 2 /*addr, value*/); #undef make_js_func } void dbg_init_load_offset(Dbg *dbg) { procmaps_iterator maps_iter = {0}; procmaps_error_t parser_err = PROCMAPS_SUCCESS; parser_err = pmparser_parse(dbg->pid, &maps_iter); if (parser_err) { LOG_ERR("Failed to parse /proc/%d/maps (%d)\n", dbg->pid, (int)parser_err); return; } // We only need the first one procmaps_struct *mem_region = pmparser_next(&maps_iter); dbg->program_load_offset = (uintptr_t)mem_region->addr_start; pmparser_free(&maps_iter); } void dbg_load_script(Dbg *dbg, const char *script_path) { FILE *script = fopen(script_path, "r"); if (script == NULL) { return; } fseek(script, 0L, SEEK_END); long size = ftell(script); rewind(script); char *script_buf = malloc(size+1); fread(script_buf, size, 1, script); script_buf[size] = '\0'; js_dostring(dbg->js, script_buf); fclose(script); } void dbg_init(Dbg *dbg, const char *file, pid_t pid) { memset(dbg, 0, sizeof(*dbg)); dbg->file = file; dbg->pid = pid; dbg_init_js(dbg); dbg_init_load_offset(dbg); hashtable_init(&dbg->brks, MAX_BRKS); dbg_load_script(dbg, INIT_SCRIPT); } void dbg_deinit(Dbg *dbg) { js_freestate(dbg->js); hashtable_deinit(&dbg->brks); } void dbg_loop(Dbg *dbg) { int status, options = 0; waitpid(dbg->pid, &status, options); char *line = NULL; while ((line = linenoise("debugus # ")) != NULL) { js_dostring(dbg->js, line); linenoiseHistoryAdd(line); linenoiseFree(line); } } void start_debugging(const char *input_file_path) { pid_t pid = fork(); if (pid == 0) { personality(ADDR_NO_RANDOMIZE); ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl(input_file_path, input_file_path, NULL); } else if (pid >= 1) { Dbg dbg; dbg_init(&dbg, input_file_path, pid); dbg_loop(&dbg); dbg_deinit(&dbg); } } int main(int argc, char ** argv) { const char *program_name = shift(argc, argv, ""); unused(program_name); const char *input_file_path = shift(argc, argv, "No input file provided\n"); start_debugging(input_file_path); return 0; }