Implement Breakpoints
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*.o
|
*.o
|
||||||
*.d
|
*.d
|
||||||
debugus
|
debugus
|
||||||
|
test
|
||||||
|
|||||||
9
Makefile
9
Makefile
@@ -1,11 +1,14 @@
|
|||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-MD -MP -I./mujs
|
CFLAGS=-MD -MP -ggdb -I./mujs
|
||||||
LDFLAGS=-lm
|
LDFLAGS=-lm
|
||||||
SRCS=debugus.c linenoise.c
|
SRCS=debugus.c linenoise.c hash.c
|
||||||
OBJS=$(patsubst %.c,%.o,$(SRCS))
|
OBJS=$(patsubst %.c,%.o,$(SRCS))
|
||||||
DEPS=$(patsubst %.c,%.d,$(SRCS))
|
DEPS=$(patsubst %.c,%.d,$(SRCS))
|
||||||
|
|
||||||
all: debugus
|
all: debugus test
|
||||||
|
|
||||||
|
test: test.o
|
||||||
|
$(CC) -o $@ $^
|
||||||
|
|
||||||
debugus: $(OBJS) ./mujs/build/debug/libmujs.o
|
debugus: $(OBJS) ./mujs/build/debug/libmujs.o
|
||||||
$(CC) -o $@ $^ $(LDFLAGS)
|
$(CC) -o $@ $^ $(LDFLAGS)
|
||||||
|
|||||||
161
debugus.c
161
debugus.c
@@ -1,12 +1,19 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <sys/ptrace.h>
|
#include <sys/ptrace.h>
|
||||||
|
#include <sys/personality.h>
|
||||||
|
|
||||||
#include "linenoise.h"
|
#include "linenoise.h"
|
||||||
#include "mujs.h"
|
#include "mujs.h"
|
||||||
|
#include "hash.h"
|
||||||
|
|
||||||
#define LOG_ERR(fmt, ...) fprintf(stderr, "Error: " fmt, ##__VA_ARGS__)
|
#define LOG_ERR(fmt, ...) fprintf(stderr, "Error: " fmt, ##__VA_ARGS__)
|
||||||
#define LOG_INF(fmt, ...) fprintf(stdout, "Info: " 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++)
|
#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 {
|
typedef struct {
|
||||||
const char *file;
|
const char *file;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
js_State *js;
|
js_State *js;
|
||||||
|
HashTable brks;
|
||||||
|
uintptr_t program_load_offset;
|
||||||
} Dbg;
|
} Dbg;
|
||||||
|
|
||||||
|
#define getdbg() ((Dbg*)js_currentfunctiondata(js))
|
||||||
|
|
||||||
void dbg_js_print_file(js_State *js)
|
void dbg_js_print_file(js_State *js)
|
||||||
{
|
{
|
||||||
void *data = js_currentfunctiondata(js);
|
Dbg *dbg = getdbg();
|
||||||
Dbg *dbg = (Dbg *)data;
|
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)
|
void dbg_init_js(Dbg *dbg)
|
||||||
{
|
{
|
||||||
dbg->js = js_newstate(NULL, NULL, JS_STRICT);
|
dbg->js = js_newstate(NULL, NULL, JS_STRICT);
|
||||||
|
|
||||||
js_newcfunctionx(dbg->js, &dbg_js_print_file, "print_file", 0, dbg, NULL);
|
#define make_js_func(name, n) \
|
||||||
js_setglobal(dbg->js, "print_file");
|
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)
|
void dbg_init(Dbg *dbg, const char *file, pid_t pid)
|
||||||
{
|
{
|
||||||
|
memset(dbg, 0, sizeof(*dbg));
|
||||||
dbg->file = file;
|
dbg->file = file;
|
||||||
dbg->pid = pid;
|
dbg->pid = pid;
|
||||||
dbg_init_js(dbg);
|
dbg_init_js(dbg);
|
||||||
|
hashtable_init(&dbg->brks, MAX_BRKS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dbg_deinit(Dbg *dbg)
|
void dbg_deinit(Dbg *dbg)
|
||||||
{
|
{
|
||||||
js_freestate(dbg->js);
|
js_freestate(dbg->js);
|
||||||
|
hashtable_deinit(&dbg->brks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dbg_loop(Dbg *dbg)
|
void dbg_loop(Dbg *dbg)
|
||||||
@@ -66,8 +212,9 @@ void start_debugging(const char *input_file_path)
|
|||||||
{
|
{
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
/* ptrace(PTRACE_TRACEME, 0, NULL, NULL); */
|
personality(ADDR_NO_RANDOMIZE);
|
||||||
/* execl(input_file_path, input_file_path, NULL); */
|
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
|
||||||
|
execl(input_file_path, input_file_path, NULL);
|
||||||
} else if (pid >= 1) {
|
} else if (pid >= 1) {
|
||||||
Dbg dbg;
|
Dbg dbg;
|
||||||
dbg_init(&dbg, input_file_path, pid);
|
dbg_init(&dbg, input_file_path, pid);
|
||||||
|
|||||||
112
hash.c
Normal file
112
hash.c
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
27
hash.h
Normal file
27
hash.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef HASH_H_
|
||||||
|
#define HASH_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
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_
|
||||||
Reference in New Issue
Block a user