From cb551e8f06034aa80f2d24b6ba8607a92ea766de Mon Sep 17 00:00:00 2001 From: kichikuou Date: Thu, 7 Nov 2024 15:49:55 +0900 Subject: [PATCH 1/2] debugger_dap: Source-level debugging - Add support for setBreakpoints request - Add source locations to stackTrace response - Source-level stepIn/stepOut/next executions All of these only work if debug information is available. Otherwise they work at the instruction level as before. --- include/debugger.h | 1 + include/xsystem4.h | 1 + src/debug.c | 21 +++++++ src/debug_info.c | 25 ++++++++- src/debugger_dap.c | 132 ++++++++++++++++++++++++++++++++++++++++++-- src/util.c | 2 +- subprojects/libsys4 | 2 +- 7 files changed, 173 insertions(+), 11 deletions(-) diff --git a/include/debugger.h b/include/debugger.h index 0018367..6299932 100644 --- a/include/debugger.h +++ b/include/debugger.h @@ -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); diff --git a/include/xsystem4.h b/include/xsystem4.h index a16358b..3bdf14f 100644 --- a/include/xsystem4.h +++ b/include/xsystem4.h @@ -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); diff --git a/src/debug.c b/src/debug.c index f1236ae..46899f6 100644 --- a/src/debug.c +++ b/src/debug.c @@ -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); diff --git a/src/debug_info.c b/src/debug_info.c index 73e54bf..ae80fd1 100644 --- a/src/debug_info.c +++ b/src/debug_info.c @@ -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; } diff --git a/src/debugger_dap.c b/src/debugger_dap.c index 200c3a4..57393e3 100644 --- a/src/debugger_dap.c +++ b/src/debugger_dap.c @@ -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) @@ -194,13 +203,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 +472,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 +644,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(); } @@ -575,10 +667,15 @@ static void cmd_pause(cJSON *args, cJSON *resp) static void cmd_stepIn(cJSON *args, cJSON *resp) { + stepping_file = stepping_line = 0; + if (dbg_info) + 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 +685,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) + 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 +901,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 +1010,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; diff --git a/src/util.c b/src/util.c index 573c303..1cf7ffa 100644 --- a/src/util.c +++ b/src/util.c @@ -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; diff --git a/subprojects/libsys4 b/subprojects/libsys4 index df3793d..3f44e18 160000 --- a/subprojects/libsys4 +++ b/subprojects/libsys4 @@ -1 +1 @@ -Subproject commit df3793dd077690f39c09a5f77b4c588270535899 +Subproject commit 3f44e18aaaa86b58003cb42f89554758506d3620 From e8ecad598a28c58e7bb9300f3d5e19c4d4e656ac Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sun, 10 Nov 2024 08:40:50 +0900 Subject: [PATCH 2/2] debugger_dap: Support SteppingGranularity Debuggers that operate at the instruction level can execute instruction- level step even when xsystem4 has loaded debug information, by adding `granularity: "instruction"` to the step request. --- src/debugger_dap.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/debugger_dap.c b/src/debugger_dap.c index 57393e3..d7f242a 100644 --- a/src/debugger_dap.c +++ b/src/debugger_dap.c @@ -162,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(); @@ -665,10 +666,28 @@ 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) + 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(); @@ -692,7 +711,7 @@ static void cmd_stepOut(cJSON *args, cJSON *resp) static void cmd_next(cJSON *args, cJSON *resp) { stepping_file = stepping_line = 0; - if (dbg_info) + 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();