Merge pull request #231 from kichikuou/debugger-dap

debugger_dap: Source-level debugging
This commit is contained in:
Nunuhara Cabbage
2025-05-12 17:50:08 -07:00
committed by GitHub
7 changed files with 192 additions and 11 deletions
+1
View File
@@ -81,6 +81,7 @@ void dbg_cmd_repl(void);
void dbg_cmd_add_module(const char *name, unsigned nr_commands, struct dbg_cmd *commands);
void dbg_handle_breakpoint(void);
bool dbg_clear_breakpoint(uint32_t addr, void(*free_data)(void*));
void dbg_foreach_breakpoint(void (*fun)(int addr, struct breakpoint*, void *data), void *data);
bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data);
bool dbg_set_address_breakpoint(uint32_t address, void(*cb)(struct breakpoint*), void *data);
bool dbg_set_step_over_breakpoint(void);
+1
View File
@@ -71,6 +71,7 @@ void log_message(const char *log, const char *fmt, ...);
#define UNIMPLEMENTED(fmt, ...) \
sys_warning("unimplemented: %s" fmt "\n", __func__, ##__VA_ARGS__)
bool is_absolute_path(const char *path);
char *unix_path(const char *path);
char *gamedir_path(const char *path);
char *gamedir_path_icase(const char *path);
+21
View File
@@ -175,6 +175,27 @@ bool dbg_clear_breakpoint(uint32_t addr, void(*free_data)(void*))
return true;
}
struct dbg_foreach_data {
void (*fun)(int addr, struct breakpoint*, void *data);
void *data;
};
void dbg_foreach_breakpoint_cb(struct ht_slot *slot, void *d_)
{
if (!slot->value)
return;
struct dbg_foreach_data *d = d_;
d->fun(slot->ikey, slot->value, d->data);
}
void dbg_foreach_breakpoint(void (*fun)(int addr, struct breakpoint*, void *data), void *data)
{
if (!bp_table)
return;
struct dbg_foreach_data d = { fun, data };
ht_foreach(bp_table, dbg_foreach_breakpoint_cb, &d);
}
bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data)
{
char *name = utf2sjis(_name, 0);
+22 -3
View File
@@ -150,9 +150,28 @@ const char *dbg_info_source_line(const struct dbg_info *info, int file, int line
int dbg_info_find_file(const struct dbg_info *info, const char *filename)
{
for (int i = 0; i < info->nr_sources; i++) {
if (!strcmp(info->sources[i], filename) || !strcmp(path_basename(info->sources[i]), filename))
return i;
if (is_absolute_path(filename)) {
char *target = realpath_utf8(filename); // Convert '\\' to '/' on Windows
char *prefix = realpath_utf8(info->src_root);
for (int i = 0; i < info->nr_sources; i++) {
char *path = path_join(prefix, info->sources[i]);
if (!strcasecmp(path, target)) {
free(path);
free(prefix);
free(target);
return i;
}
free(path);
}
free(prefix);
free(target);
} else {
for (int i = 0; i < info->nr_sources; i++) {
if (!strcasecmp(info->sources[i], filename) ||
!strcasecmp(path_basename(info->sources[i]), filename)) {
return i;
}
}
}
return -1;
}
+145 -6
View File
@@ -56,6 +56,15 @@ static enum {
DAP_STOPPED
} dap_state = DAP_UNINITIALIZED;
static enum {
NOT_STEPPING = 0,
STEPPING_INTO,
STEPPING_OVER,
STEPPING_FINISH
} stepping = NOT_STEPPING;
static int stepping_file = 0;
static int stepping_line = 0;
static struct msgq *queue;
static void json_add_sjis_to_object(cJSON *obj, const char *name, const char *sjis)
@@ -153,6 +162,7 @@ static void cmd_initialize(cJSON *args, cJSON *resp)
//cJSON_AddBoolToObject(body, "supportsSetExpression", true);
//cJSON_AddBoolToObject(body, "supportsTerminateRequest", true);
cJSON_AddBoolToObject(body, "supportsInstructionBreakpoints", true);
cJSON_AddBoolToObject(body, "supportsSteppingGranularity", true);
send_response(resp, true);
emit_initialized_event();
@@ -194,13 +204,24 @@ static void cmd_stackTrace(cJSON *args, cJSON *resp)
json_add_sjis_to_object(frame, "name", ain->functions[fno].name);
}
char ip[9];
if (i == call_stack_ptr - 1) {
snprintf(ip, 9, "%x", (unsigned)instr_ptr);
} else {
snprintf(ip, 9, "%x", call_stack[i+1].call_address);
}
int addr = (i == call_stack_ptr - 1) ? instr_ptr : call_stack[i+1].call_address;
snprintf(ip, 9, "%x", (unsigned)addr);
cJSON_AddStringToObject(frame, "instructionPointerReference", ip);
cJSON_AddNumberToObject(frame, "line", 0);
int file, line;
if (dbg_info && dbg_info_addr2line(dbg_info, addr, &file, &line)) {
cJSON_AddNumberToObject(frame, "line", line);
const char *fname = dbg_info_source_name(dbg_info, file);
cJSON *source = cJSON_CreateObject();
cJSON_AddStringToObject(source, "name", fname);
char *path = dbg_info_source_path(dbg_info, file);
cJSON_AddStringToObject(source, "path", path);
free(path);
cJSON_AddNumberToObject(source, "sourceReference", 0);
cJSON_AddItemToObjectCS(frame, "source", source);
} else {
cJSON_AddNumberToObject(frame, "line", 0);
}
cJSON_AddNumberToObject(frame, "column", 0);
}
send_response(resp, true);
@@ -452,6 +473,76 @@ static void cmd_variables(cJSON *args, cJSON *resp)
send_response(resp, true);
}
void delete_breakpoints_in_file(int addr, struct breakpoint *bp, void *file_)
{
int file = *(int *)file_;
int f;
if (dbg_info_addr2line(dbg_info, addr, &f, NULL) && f == file) {
dbg_clear_breakpoint(addr, NULL);
}
}
static void cmd_setBreakpoints(cJSON *args, cJSON *resp)
{
if (!dbg_info) {
cJSON_AddStringToObject(resp, "message", "no debug info available");
send_response(resp, false);
return;
}
cJSON *source = cJSON_GetObjectItemCaseSensitive(args, "source");
cJSON *source_path = cJSON_GetObjectItemCaseSensitive(source, "path");
cJSON *req_bps = cJSON_GetObjectItemCaseSensitive(args, "breakpoints");
if (!cJSON_IsString(source_path) || !cJSON_IsArray(req_bps)) {
cJSON_AddStringToObject(resp, "message", "invalid arguments");
send_response(resp, false);
return;
}
const char *path = source_path->valuestring;
int file = dbg_info_find_file(dbg_info, path);
if (file < 0) {
cJSON_AddStringToObject(resp, "message", "no such file");
send_response(resp, false);
return;
}
dbg_foreach_breakpoint(delete_breakpoints_in_file, &file);
cJSON *body, *resp_bps;
cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject());
cJSON_AddItemToObjectCS(body, "breakpoints", resp_bps = cJSON_CreateArray());
char message[256];
cJSON *req_bp;
cJSON_ArrayForEach(req_bp, req_bps) {
cJSON *resp_bp = cJSON_CreateObject();
cJSON_AddItemToArray(resp_bps, resp_bp);
cJSON *line = cJSON_GetObjectItemCaseSensitive(req_bp, "line");
int line_no = line->valueint;
int addr = dbg_info_line2addr(dbg_info, file, line_no);
if (addr < 0) {
snprintf(message, sizeof(message), "no line %d in %s", line_no, path);
cJSON_AddBoolToObject(resp_bp, "verified", false);
cJSON_AddStringToObject(resp_bp, "message", message);
continue;
}
bool verified = dbg_set_address_breakpoint(addr, NULL, NULL);
if (!verified) {
snprintf(message, sizeof(message), "failed to set breakpoint at 0x%x", addr);
cJSON_AddBoolToObject(resp_bp, "verified", false);
cJSON_AddStringToObject(resp_bp, "message", message);
continue;
}
dbg_info_addr2line(dbg_info, addr, NULL, &line_no);
cJSON_AddNumberToObject(resp_bp, "id", addr);
cJSON_AddBoolToObject(resp_bp, "verified", true);
cJSON_AddNumberToObject(resp_bp, "line", line_no);
}
send_response(resp, true);
}
static void cmd_setInstructionBreakpoints(cJSON *args, cJSON *resp)
{
static uint32_t *old_breakpoints = NULL;
@@ -554,6 +645,8 @@ static void cmd_continue(cJSON *args, cJSON *resp)
{
send_response(resp, true);
stepping = NOT_STEPPING;
stepping_file = stepping_line = 0;
dap_state = DAP_RUNNING;
dbg_continue();
}
@@ -573,12 +666,35 @@ static void cmd_pause(cJSON *args, cJSON *resp)
}
}
enum stepping_granularity {
STEPPING_GRANULARITY_STATEMENT,
STEPPING_GRANULARITY_LINE,
STEPPING_GRANULARITY_INSTRUCTION,
};
static enum stepping_granularity get_granularity(cJSON *args)
{
cJSON *g = cJSON_GetObjectItemCaseSensitive(args, "granularity");
if (cJSON_IsString(g)) {
if (!strcmp(g->valuestring, "line"))
return STEPPING_GRANULARITY_LINE;
if (!strcmp(g->valuestring, "instruction"))
return STEPPING_GRANULARITY_INSTRUCTION;
}
return STEPPING_GRANULARITY_STATEMENT;
}
static void cmd_stepIn(cJSON *args, cJSON *resp)
{
stepping_file = stepping_line = 0;
if (dbg_info && get_granularity(args) != STEPPING_GRANULARITY_INSTRUCTION)
dbg_info_addr2line(dbg_info, instr_ptr, &stepping_file, &stepping_line);
dbg_set_step_into_breakpoint();
send_response(resp, true);
dap_state = DAP_RUNNING;
stepping = STEPPING_INTO;
dbg_continue();
}
@@ -588,15 +704,21 @@ static void cmd_stepOut(cJSON *args, cJSON *resp)
send_response(resp, true);
dap_state = DAP_RUNNING;
stepping = STEPPING_FINISH;
dbg_continue();
}
static void cmd_next(cJSON *args, cJSON *resp)
{
stepping_file = stepping_line = 0;
if (dbg_info && get_granularity(args) != STEPPING_GRANULARITY_INSTRUCTION)
dbg_info_addr2line(dbg_info, instr_ptr, &stepping_file, &stepping_line);
dbg_set_step_over_breakpoint();
send_response(resp, true);
dap_state = DAP_RUNNING;
stepping = STEPPING_OVER;
dbg_continue();
}
@@ -798,6 +920,7 @@ static struct {
{ "next", cmd_next, false },
{ "pause", cmd_pause, true },
{ "evaluate", cmd_evaluate, true },
{ "setBreakpoints", cmd_setBreakpoints, true },
{ "setInstructionBreakpoints", cmd_setInstructionBreakpoints, true },
{ "threads", cmd_threads, true },
{ "scopes", cmd_scopes, true },
@@ -906,6 +1029,22 @@ void dbg_dap_quit(void)
void dbg_dap_repl(struct dbg_stop *stop)
{
if (stepping) {
if (stepping_line) {
int file, line;
dbg_info_addr2line(dbg_info, instr_ptr, &file, &line);
if (file == stepping_file && line == stepping_line) {
if (stepping == STEPPING_INTO)
dbg_set_step_into_breakpoint();
else if (stepping == STEPPING_OVER)
dbg_set_step_over_breakpoint();
dbg_continue();
}
}
stepping = NOT_STEPPING;
stepping_file = stepping_line = 0;
}
emit_stopped_event(stop);
dap_state = DAP_STOPPED;
+1 -1
View File
@@ -117,7 +117,7 @@ char *unix_path(const char *path)
return utf;
}
static bool is_absolute_path(const char *path)
bool is_absolute_path(const char *path)
{
#if (defined(_WIN32) || defined(__WIN32__))
int i = (isalpha(path[0]) && path[1] == ':') ? 2 : 0;