diff --git a/.gitignore b/.gitignore index c006f14..af914b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.o *.d debugus +test diff --git a/Makefile b/Makefile index 796fa35..e4cb626 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ CC=gcc -CFLAGS=-MD -MP -I./mujs +CFLAGS=-MD -MP -ggdb -I./mujs LDFLAGS=-lm -SRCS=debugus.c linenoise.c +SRCS=debugus.c linenoise.c hash.c OBJS=$(patsubst %.c,%.o,$(SRCS)) DEPS=$(patsubst %.c,%.d,$(SRCS)) -all: debugus +all: debugus test + +test: test.o + $(CC) -o $@ $^ debugus: $(OBJS) ./mujs/build/debug/libmujs.o $(CC) -o $@ $^ $(LDFLAGS) diff --git a/debugus.c b/debugus.c index 8a6dad8..3e1b453 100644 --- a/debugus.c +++ b/debugus.c @@ -1,12 +1,19 @@ #include #include +#include +#include +#include +#include #include +#include #include #include #include +#include #include "linenoise.h" #include "mujs.h" +#include "hash.h" #define LOG_ERR(fmt, ...) fprintf(stderr, "Error: " fmt, ##__VA_ARGS__) #define LOG_INF(fmt, ...) fprintf(stdout, "Info: " fmt, ##__VA_ARGS__) @@ -15,38 +22,177 @@ #define shift(argc, argv, msg, ...) (argc <= 0 ? (LOG_ERR(msg, ##__VA_ARGS__), exit(EXIT_FAILURE)) : (void)0, argc--, *argv++) +// 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; + 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_print_file(js_State *js) { - void *data = js_currentfunctiondata(js); - Dbg *dbg = (Dbg *)data; + Dbg *dbg = getdbg(); + LOG_INF("File: %s\n", dbg->file); + + js_pushundefined(js); +} - printf("File: %s\n", dbg->file); +void dbg_js_print_dbg_pid(js_State *js) +{ + Dbg *dbg = getdbg(); + LOG_INF("Debugger PID: %d\n", dbg->pid); + + js_pushundefined(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, "%"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, "%"SCNxPTR, &addr); + dbg->program_load_offset = addr; + + js_pushundefined(js); +} + +void dbg_js_print_program_load_offset(js_State *js) +{ + Dbg *dbg = getdbg(); + LOG_INF("Program load offset: 0x%"PRIxPTR"\n", dbg->program_load_offset); + + js_pushundefined(js); +} + +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_init_js(Dbg *dbg) { dbg->js = js_newstate(NULL, NULL, JS_STRICT); - js_newcfunctionx(dbg->js, &dbg_js_print_file, "print_file", 0, dbg, NULL); - js_setglobal(dbg->js, "print_file"); + #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(print_file, 0); + make_js_func(print_dbg_pid, 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); + make_js_func(print_program_load_offset, 0); + + #undef make_js_func } 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); + hashtable_init(&dbg->brks, MAX_BRKS); } void dbg_deinit(Dbg *dbg) { js_freestate(dbg->js); + hashtable_deinit(&dbg->brks); } void dbg_loop(Dbg *dbg) @@ -66,8 +212,9 @@ void start_debugging(const char *input_file_path) { pid_t pid = fork(); if (pid == 0) { - /* ptrace(PTRACE_TRACEME, 0, NULL, NULL); */ - /* execl(input_file_path, input_file_path, NULL); */ + 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); diff --git a/hash.c b/hash.c new file mode 100644 index 0000000..7ccb4fd --- /dev/null +++ b/hash.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#include "hash.h" + +static uint64_t hashtable_hash(const char *key) +{ + #define FNV_OFFSET 14695981039346656037UL + #define FNV_PRIME 1099511628211UL + + uint64_t hash = FNV_OFFSET; + for (const char *p = key; *p; p++) { + hash ^= (uint64_t)(unsigned char)(*p); + hash *= FNV_PRIME; + } + return hash; +} + +void hashtable_init (HashTable *t, size_t capacity) +{ + memset(t, 0, sizeof(*t)); + + t->capacity = capacity; + t->buckets = malloc(t->capacity * sizeof(*t->buckets)); + + for (int i = 0; i < t->capacity; i++) { + t->buckets[i] = NULL; + } +} + +void hashtable_deinit (HashTable *t) +{ + for (int i = 0; i < t->capacity; i++) { + if (t->buckets[i] != NULL) { + if (t->buckets[i]->key != NULL) { + free((void *)t->buckets[i]->key); + } + free(t->buckets[i]); + } + } + free(t->buckets); +} + +HashEntry * hashtable_new (const char *key, void *value, size_t sz) +{ + HashEntry *e = malloc(sizeof(*e)); + e->key = malloc(strlen(key)+1); + e->value = malloc(sz); + e->sz = sz; + strcpy((char*)e->key, key); + memcpy(e->value, value, e->sz); + return e; +} + +void * hashtable_get (HashTable *t, const char *key) +{ + uint64_t hash = hashtable_hash(key); + size_t index = (size_t)(hash % t->capacity); + + for (int i = index; i < t->capacity; i++) { + if (t->buckets[i] != NULL && t->buckets[i]->key != NULL && strcmp(t->buckets[i]->key, key) == 0) { + return t->buckets[i]->value; + } + } + + return NULL; +} + +bool hashtable_check (HashTable *t, const char *key) +{ + return hashtable_get(t, key) != NULL; +} + +void hashtable_set (HashTable *t, const char *key, void *value, size_t sz) +{ + uint64_t hash = hashtable_hash(key); + size_t index = (size_t)(hash % t->capacity); + + for (int i = index; i < t->capacity; i++) { + if (t->buckets[i] == NULL) { + HashEntry *e = hashtable_new(key, value, sz); + t->buckets[i] = e; + break; + } else if (t->buckets[i] != NULL && t->buckets[i]->key != NULL && strcmp(t->buckets[i]->key, key) == 0) { + t->buckets[i]->sz = sz; + t->buckets[i]->value = value; + break; + } + } +} + +bool hashtable_delete (HashTable *t, const char *key) +{ + uint64_t hash = hashtable_hash(key); + size_t index = (size_t)(hash % t->capacity); + + for (int i = index; i < t->capacity; i++) { + if (t->buckets[i] != NULL && t->buckets[i]->key != NULL && strcmp(t->buckets[i]->key, key) == 0) { + HashEntry *e = t->buckets[i]; + free((void*)e->key); + free((void*)e->value); + free((void*)e); + t->buckets[i] = NULL; + return true; + } + } + return false; +} + diff --git a/hash.h b/hash.h new file mode 100644 index 0000000..8899eab --- /dev/null +++ b/hash.h @@ -0,0 +1,27 @@ +#ifndef HASH_H_ +#define HASH_H_ + +#include +#include +#include + +typedef struct HashEntry { + const char *key; + void *value; + size_t sz; +} HashEntry; + +typedef struct { + HashEntry **buckets; + size_t capacity; +} HashTable; + +void hashtable_init (HashTable *t, size_t capacity); +void hashtable_deinit (HashTable *t); +HashEntry * hashtable_new (const char *key, void *value, size_t sz); +void * hashtable_get (HashTable *t, const char *key); +void hashtable_set (HashTable *t, const char *key, void *value, size_t sz); +bool hashtable_delete (HashTable *t, const char *key); +bool hashtable_check (HashTable *t, const char *key); + +#endif // HASH_H_ diff --git a/test.c b/test.c new file mode 100644 index 0000000..d637e43 --- /dev/null +++ b/test.c @@ -0,0 +1,10 @@ +#include + +int main(void) +{ + for (int i = 0; i < 20; i++) { + printf("i = %d\n", i); + } + return 0; +} +