diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 10683a4..e324553 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,7 +15,7 @@ jobs: - name: Install Deps run: | sudo apt update - sudo apt install libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libportmidi-dev + sudo apt install libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libportmidi-dev libcjson-dev - name: Build run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d2aa2e7..62f5467 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,7 +33,7 @@ jobs: echo PATH: $PATH - name: Install Deps - run: pacman -S --noconfirm ${{ matrix.package }}-SDL2 ${{ matrix.package }}-SDL2_ttf ${{ matrix.package }}-SDL2_mixer ${{ matrix.package }}-libjpeg-turbo ${{ matrix.package }}-nsis ${{ matrix.package }}-ntldd-git + run: pacman -S --noconfirm ${{ matrix.package }}-SDL2 ${{ matrix.package }}-SDL2_ttf ${{ matrix.package }}-SDL2_mixer ${{ matrix.package }}-libjpeg-turbo ${{ matrix.package }}-cjson ${{ matrix.package }}-nsis ${{ matrix.package }}-ntldd-git - name: Checkout uses: actions/checkout@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index abb61f0..f86eb2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,16 @@ else() optional_pkg_check_modules(SDL2MIXER IMPORTED_TARGET SDL2_mixer) option(ENABLE_DEBUGGER "Enable built-in debugger" ON) + if (ENABLE_DEBUGGER) + pkg_check_modules(cJSON IMPORTED_TARGET libcjson) + if (NOT cJSON_FOUND) + # libcjson-dev of Debian buster / Ubuntu 20.04 does not install pkgconfig files. + find_library(cJSON cjson) + if (NOT cJSON) + message(FATAL_ERROR "libcjson is required but not found.") + endif() + endif() + endif() include(FindJPEG) if (JPEG_FOUND) diff --git a/README.md b/README.md index 94e50e6..3a1582c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Prebuilt binaries for Windows and Android can be downloaded from the [Releases]( ## Build ### Linux (Debian / Ubuntu) - $ sudo apt install build-essential cmake libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev + $ sudo apt install build-essential cmake libgtk-3-dev libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libcjson-dev $ mkdir -p out/debug $ cd out/debug $ cmake -DCMAKE_BUILD_TYPE=Debug ../../ @@ -18,7 +18,7 @@ Prebuilt binaries for Windows and Android can be downloaded from the [Releases]( [Homebrew](https://brew.sh/index_ja) is needed. - $ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf libjpeg + $ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf libjpeg cjson $ mkdir -p out/debug $ cd out/debug $ cmake -DCMAKE_BUILD_TYPE=Debug ../../ @@ -28,7 +28,7 @@ Prebuilt binaries for Windows and Android can be downloaded from the [Releases]( [MSYS2](https://www.msys2.org) is needed. - $ pacman -S cmake mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-libjpeg-turbo + $ pacman -S cmake mingw-w64-x86_64-cmake mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_ttf mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-cjson $ mkdir -p out/debug $ cd out/debug $ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51c1ec7..924f6f3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,7 +71,7 @@ target_sources(xsystem35 PRIVATE # Debugger if (ENABLE_DEBUGGER) target_sources(xsystem35 PRIVATE - debug_symbol.c debugger.c debugger_cui.c) + debug_symbol.c debugger.c debugger_cui.c debugger_dap.c) endif() if (WIN32) @@ -116,6 +116,13 @@ else() # non-emscripten, non-android if (SDL2MIXER_FOUND) target_link_libraries(xsystem35 PRIVATE PkgConfig::SDL2MIXER) endif() + if (ENABLE_DEBUGGER) + if (cJSON_FOUND) + target_link_libraries(xsystem35 PRIVATE PkgConfig::cJSON) + else() + target_link_libraries(xsystem35 PRIVATE ${cJSON}) + endif() + endif() install(TARGETS xsystem35 RUNTIME DESTINATION bin) diff --git a/src/debugger.c b/src/debugger.c index 4ce335f..eb8dc61 100644 --- a/src/debugger.c +++ b/src/debugger.c @@ -31,6 +31,7 @@ #define INTERNAL_BREAKPOINT_NO -1 DebuggerState dbg_state = DBG_RUNNING; +DebuggerImpl *dbg_impl; struct debug_symbols *symbols; static Breakpoint *breakpoints = NULL; @@ -43,12 +44,17 @@ static struct { int line; } step_exec_state; -void dbg_init(const char *symbols_path) { - dbg_cui_init(); +void dbg_init(const char *symbols_path, boolean use_dap) { + dbg_impl = use_dap ? &dbg_dap_impl : &dbg_cui_impl; + dbg_impl->init(); dbg_state = DBG_STOPPED_ENTRY; symbols = dsym_load(symbols_path); } +void dbg_quit() { + dbg_impl->quit(); +} + int dbg_lookup_var(const char *name) { if (symbols) return dsym_lookup_variable(symbols, name); @@ -113,6 +119,26 @@ boolean dbg_delete_breakpoint(int no) { return false; } +void dbg_delete_breakpoints_in_page(int page) { + Breakpoint *prev = NULL; + for (Breakpoint *bp = breakpoints; bp;) { + if (bp->page == page) { + assert(bp->dfile->data[bp->addr] == BREAKPOINT); + bp->dfile->data[bp->addr] = bp->restore_op; + ald_freedata(bp->dfile); + if (prev) + prev->next = bp->next; + else + breakpoints = bp->next; + bp = bp->next; + free(bp); + } else { + prev = bp; + bp = bp->next; + } + } +} + BYTE dbg_handle_breakpoint(int page, int addr) { Breakpoint *bp = dbg_find_breakpoint(page, addr); if (!bp) @@ -265,5 +291,10 @@ void dbg_main(void) { default: break; } - dbg_cui_repl(); + dbg_impl->repl(); +} + +void dbg_onsleep(void) { + if (dbg_impl) + dbg_impl->onsleep(); } diff --git a/src/debugger.h b/src/debugger.h index c5274e3..4045cad 100644 --- a/src/debugger.h +++ b/src/debugger.h @@ -39,17 +39,19 @@ extern DebuggerState dbg_state; #ifdef ENABLE_DEBUGGER #define dbg_trapped() (dbg_state != DBG_RUNNING) -#define dbg_interrupted() (dbg_state == DBG_STOPPED_INTERRUPT) -void dbg_init(const char *symbols_path); +void dbg_init(const char *symbols_path, boolean use_dap); +void dbg_quit(); void dbg_main(void); +void dbg_onsleep(void); BYTE dbg_handle_breakpoint(int page, int addr); #else // ENABLE_DEBUGGER #define dbg_trapped() false -#define dbg_interrupted() false -#define dbg_init(symbols_path) +#define dbg_init(symbols_path, use_dap) +#define dbg_quit() #define dbg_main() +#define dbg_onsleep() #define dbg_handle_breakpoint(page, addr) BREAKPOINT #endif // ENABLE_DEBUGGER diff --git a/src/debugger_cui.c b/src/debugger_cui.c index a51dcd8..0c5c5eb 100644 --- a/src/debugger_cui.c +++ b/src/debugger_cui.c @@ -315,7 +315,7 @@ static void sigint_handler(int sig_num) { dbg_state = DBG_STOPPED_INTERRUPT; } -void dbg_cui_init(void) { +static void dbg_cui_init(void) { #ifdef HAVE_SIGACTION sys_set_signalhandler(SIGINT, sigint_handler); #endif @@ -325,6 +325,9 @@ void dbg_cui_init(void) { #endif } +static void dbg_cui_quit(void) { +} + typedef struct { const char *name; const char *alias; @@ -379,7 +382,7 @@ static CommandResult cmd_help(void) { return CONTINUE_REPL; } -void dbg_cui_repl(void) { +static void dbg_cui_repl(void) { if (dbg_state == DBG_STOPPED_BREAKPOINT) { Breakpoint *bp = dbg_find_breakpoint(nact->current_page, nact->current_addr); if (bp) @@ -408,3 +411,15 @@ void dbg_cui_repl(void) { } } } + +static void dbg_cui_onsleep(void) { + if (dbg_state == DBG_STOPPED_INTERRUPT) + dbg_main(); +} + +DebuggerImpl dbg_cui_impl = { + .init = dbg_cui_init, + .quit = dbg_cui_quit, + .repl = dbg_cui_repl, + .onsleep = dbg_cui_onsleep, +}; diff --git a/src/debugger_dap.c b/src/debugger_dap.c new file mode 100644 index 0000000..5cd65c7 --- /dev/null +++ b/src/debugger_dap.c @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2021 kichikuou + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +#include "cjson/cJSON.h" +#include "debugger.h" +#include "debugger_private.h" +#include "debug_symbol.h" +#include "system.h" +#include "variable.h" + +#define THREAD_ID 1 + +static boolean initialized = false; +static char *src_dir; + +cJSON *create_source(const char *name) { + cJSON *source = cJSON_CreateObject(); + cJSON_AddStringToObject(source, "name", name); + if (src_dir) { + char *buf = alloca(strlen(src_dir) + strlen(name) + 2); + sprintf(buf, "%s/%s", src_dir, name); + cJSON_AddStringToObject(source, "path", buf); + } + cJSON_AddNumberToObject(source, "sourceReference", 0); + return source; +} + +static void send_json(cJSON *json) { + static int seq = 0; + + cJSON_AddNumberToObject(json, "seq", seq); + char *str = cJSON_PrintUnformatted(json); + printf("Content-Length: %zu\r\n\r\n%s", strlen(str), str); + fflush(stdout); + free(str); + cJSON_free(json); +} + +static void emit_initialized_event() { + cJSON *event = cJSON_CreateObject(); + cJSON_AddStringToObject(event, "type", "event"); + cJSON_AddStringToObject(event, "event", "initialized"); + send_json(event); +} + +static void emit_terminated_event() { + cJSON *event = cJSON_CreateObject(); + cJSON_AddStringToObject(event, "type", "event"); + cJSON_AddStringToObject(event, "event", "terminated"); + send_json(event); +} + +static void emit_stop_event() { + const char *reason; + switch (dbg_state) { + case DBG_STOPPED_ENTRY: reason = "entry"; break; + case DBG_STOPPED_STEP: reason = "step"; break; + case DBG_STOPPED_NEXT: reason = "step"; break; + case DBG_STOPPED_BREAKPOINT: reason = "breakpoint"; break; + case DBG_STOPPED_INTERRUPT: reason = "pause"; break; + default: reason = "unknown"; break; + } + + cJSON *event = cJSON_CreateObject(), *body; + cJSON_AddStringToObject(event, "type", "event"); + cJSON_AddStringToObject(event, "event", "stopped"); + cJSON_AddItemToObjectCS(event, "body", body = cJSON_CreateObject()); + cJSON_AddStringToObject(body, "reason", reason); + cJSON_AddNumberToObject(body, "threadId", THREAD_ID); // needed? + send_json(event); +} + +static void cmd_initialize(cJSON *args, cJSON *resp) { + cJSON *body; + cJSON_AddBoolToObject(resp, "success", true); + cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); + cJSON_AddBoolToObject(body, "supportsConfigurationDoneRequest", true); + cJSON_AddBoolToObject(body, "supportsEvaluateForHovers", true); +} + +static void cmd_launch(cJSON *args, cJSON *resp) { + cJSON *srcDir = cJSON_GetObjectItemCaseSensitive(args, "srcDir"); + if (cJSON_IsString(srcDir)) + src_dir = strdup(srcDir->valuestring); + cJSON_AddBoolToObject(resp, "success", true); + + emit_initialized_event(); + initialized = true; +} + +static void cmd_configurationDone(cJSON *args, cJSON *resp) { + cJSON_AddBoolToObject(resp, "success", true); +} + +static void cmd_stackTrace(cJSON *args, cJSON *resp) { + StackTrace *trace = dbg_stack_trace(); + + cJSON *body, *stackFrames; + cJSON_AddBoolToObject(resp, "success", true); + cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); + cJSON_AddItemToObjectCS(body, "stackFrames", stackFrames = cJSON_CreateArray()); + for (int i = 0; i < trace->nr_frame; i++) { + StackFrame *frame = &trace->frames[i]; + cJSON *item = cJSON_CreateObject(); + cJSON_AddItemToArray(stackFrames, item); + cJSON_AddNumberToObject(item, "id", i); + cJSON_AddStringToObject(item, "name", frame->name); + cJSON_AddItemToObjectCS(item, "source", create_source(frame->src)); + cJSON_AddNumberToObject(item, "line", frame->line); + cJSON_AddNumberToObject(item, "column", 0); + } + cJSON_AddNumberToObject(body, "totalFrames", trace->nr_frame); + + free(trace); +} + +static void cmd_setBreakpoints(cJSON *args, cJSON *resp) { + cJSON *source = cJSON_GetObjectItemCaseSensitive(args, "source"); + cJSON *source_name = cJSON_GetObjectItemCaseSensitive(source, "name"); + cJSON *lines = cJSON_GetObjectItemCaseSensitive(args, "lines"); + if (!cJSON_IsString(source_name) || !cJSON_IsArray(lines)) { + cJSON_AddBoolToObject(resp, "success", false); + // TODO: add message + return; + } + const char *filename = source_name->valuestring; + int page = dsym_src2page(symbols, filename); + + dbg_delete_breakpoints_in_page(page); + + cJSON *body, *breakpoints; + cJSON_AddBoolToObject(resp, "success", true); + cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); + cJSON_AddItemToObjectCS(body, "breakpoints", breakpoints = cJSON_CreateArray()); + + cJSON *line; + cJSON_ArrayForEach(line, lines) { + cJSON *item = cJSON_CreateObject(); + cJSON_AddItemToArray(breakpoints, item); + + int line_no = line->valueint; + int addr = dsym_line2addr(symbols, page, line_no); + if (page < 0) { + fprintf(stderr, "No source file named %s.\n", filename); + cJSON_AddBoolToObject(item, "verified", false); + continue; + } + if (addr < 0) { + fprintf(stderr, "No line %d in file %s.\n", line_no, filename); + cJSON_AddBoolToObject(item, "verified", false); + continue; + } + Breakpoint *bp = dbg_set_breakpoint(page, addr, false); + if (!bp) { + fprintf(stderr, "Failed to set breakpoint at %d:0x%x\n", page, addr); + cJSON_AddBoolToObject(item, "verified", false); + continue; + } + line_no = dsym_addr2line(symbols, page, addr); + cJSON_AddNumberToObject(item, "id", bp->no); + cJSON_AddBoolToObject(item, "verified", true); + cJSON_AddItemToObjectCS(item, "source", create_source(filename)); + cJSON_AddNumberToObject(item, "line", line_no); + } +} + +static void cmd_evaluate(cJSON *args, cJSON *resp) { + cJSON *expression = cJSON_GetObjectItemCaseSensitive(args, "expression"); + if (!cJSON_IsString(expression)) { + cJSON_AddBoolToObject(resp, "success", false); + // TODO: add message + return; + } + const char *varname = expression->valuestring; + int var = dbg_lookup_var(varname); + if (var < 0) { + fprintf(stderr, "Unrecognized variable name \"%s\".\n", varname); + // TODO: add message + cJSON_AddBoolToObject(resp, "success", false); + return; + } + + char result[20]; + sprintf(result, "%d", sysVar[var]); + + cJSON *body; + cJSON_AddBoolToObject(resp, "success", true); + cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); + cJSON_AddStringToObject(body, "result", result); + cJSON_AddNumberToObject(body, "variablesReference", 0); +} + +static void cmd_continue(cJSON *args, cJSON *resp) { + cJSON_AddBoolToObject(resp, "success", true); +} + +static void cmd_pause(cJSON *args, cJSON *resp) { + dbg_state = DBG_STOPPED_INTERRUPT; + cJSON_AddBoolToObject(resp, "success", true); +} + +static void cmd_stepIn(cJSON *args, cJSON *resp) { + dbg_step(); + cJSON_AddBoolToObject(resp, "success", true); +} + +static void cmd_next(cJSON *args, cJSON *resp) { + dbg_next(); + cJSON_AddBoolToObject(resp, "success", true); +} + +static void cmd_threads(cJSON *args, cJSON *resp) { + cJSON *body, *threads, *thread; + cJSON_AddBoolToObject(resp, "success", true); + cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); + cJSON_AddItemToObjectCS(body, "threads", threads = cJSON_CreateArray()); + cJSON_AddItemToArray(threads, thread = cJSON_CreateObject()); + cJSON_AddNumberToObject(thread, "id", THREAD_ID); + cJSON_AddStringToObject(thread, "name", "main thread"); +} + +static void cmd_scopes(cJSON *args, cJSON *resp) { + cJSON *body; + cJSON_AddBoolToObject(resp, "success", true); + cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); + cJSON_AddItemToObjectCS(body, "scopes", cJSON_CreateArray()); +} + +static void cmd_disconnect(cJSON *args, cJSON *resp) { + cJSON_AddBoolToObject(resp, "success", true); + send_json(resp); + sys_exit(0); +} + +static boolean handle_request(cJSON *request) { + boolean continue_repl = true; + + cJSON *resp = cJSON_CreateObject(); + cJSON_AddStringToObject(resp, "type", "response"); + cJSON *request_seq = cJSON_DetachItemFromObjectCaseSensitive(request, "seq"); + cJSON_AddItemToObjectCS(resp, "request_seq", request_seq); + cJSON *command = cJSON_DetachItemFromObjectCaseSensitive(request, "command"); + cJSON_AddItemToObjectCS(resp, "command", command); + cJSON *args = cJSON_GetObjectItemCaseSensitive(request, "arguments"); + + if (!cJSON_IsString(command)) { + fprintf(stderr, "protocol error: command is not a string\n"); + // FIXME: return an error response + cJSON_free(resp); + return continue_repl; + } + + if (!strcmp(command->valuestring, "initialize")) { + cmd_initialize(args, resp); + } else if (!strcmp(command->valuestring, "launch")) { + cmd_launch(args, resp); + } else if (!strcmp(command->valuestring, "configurationDone")) { + cmd_configurationDone(args, resp); + } else if (!strcmp(command->valuestring, "continue")) { + cmd_continue(args, resp); + continue_repl = false; + } else if (!strcmp(command->valuestring, "stackTrace")) { + cmd_stackTrace(args, resp); + } else if (!strcmp(command->valuestring, "stepIn")) { + cmd_stepIn(args, resp); + continue_repl = false; + } else if (!strcmp(command->valuestring, "next")) { + cmd_next(args, resp); + continue_repl = false; + } else if (!strcmp(command->valuestring, "pause")) { + cmd_pause(args, resp); + } else if (!strcmp(command->valuestring, "evaluate")) { + cmd_evaluate(args, resp); + } else if (!strcmp(command->valuestring, "setBreakpoints")) { + cmd_setBreakpoints(args, resp); + } else if (!strcmp(command->valuestring, "threads")) { + cmd_threads(args, resp); + } else if (!strcmp(command->valuestring, "scopes")) { + cmd_scopes(args, resp); + } else if (!strcmp(command->valuestring, "disconnect")) { + cmd_disconnect(args, resp); + } else { + fprintf(stderr, "unknown command \"%s\"\n", command->valuestring); + } + send_json(resp); + return continue_repl; +} + +static boolean handle_message(char *msg) { + cJSON *json = cJSON_Parse(msg); + cJSON *type = cJSON_GetObjectItemCaseSensitive(json, "type"); + boolean continue_repl = true; + if (cJSON_IsString(type) && !strcmp(type->valuestring, "request")) + continue_repl = handle_request(json); + cJSON_free(json); + free(msg); + return continue_repl; +} + +struct cmdq_elem { + char *cmd; + struct cmdq_elem *next; +}; + +struct { + SDL_mutex *mutex; + SDL_cond *cond_nonempty; + struct cmdq_elem *head; + struct cmdq_elem *last; +} cmd_queue; + +static boolean cmdq_isempty(void) { + return !cmd_queue.head; +} + +static void cmdq_enqueue(char *cmd) { + struct cmdq_elem *e = malloc(sizeof(struct cmdq_elem)); + e->cmd = cmd; + e->next = NULL; + + SDL_LockMutex(cmd_queue.mutex); + if (!cmd_queue.head) { + cmd_queue.head = cmd_queue.last = e; + } else { + cmd_queue.last->next = e; + cmd_queue.last = e; + } + SDL_UnlockMutex(cmd_queue.mutex); + SDL_CondSignal(cmd_queue.cond_nonempty); +} + +static char *cmdq_dequeue(void) { + SDL_LockMutex(cmd_queue.mutex); + while (!cmd_queue.head) + SDL_CondWait(cmd_queue.cond_nonempty, cmd_queue.mutex); + + struct cmdq_elem *e = cmd_queue.head; + cmd_queue.head = e->next; + if (!e->next) + cmd_queue.last = NULL; + + SDL_UnlockMutex(cmd_queue.mutex); + + char *cmd = e->cmd; + free(e); + return cmd; +} + +static int read_command_thread(void *data) { + int content_length = -1; + char header[512]; + while (fgets(header, sizeof(header), stdin)) { + if (sscanf(header, "Content-Length: %d", &content_length) == 1) { + continue; + } else if ((header[0] == '\r' && header[1] == '\n') || header[0] == '\n') { + if (content_length < 0) { + fprintf(stderr, "Debug Adapter Protocol error: no Content-Length header\n"); + continue; + } + char *buf = malloc(content_length); + fread(buf, content_length, 1, stdin); + cmdq_enqueue(buf); + content_length = -1; + } else { + fprintf(stderr, "Unknown Debug Adapter Protocol header: %s", header); + } + } + cmdq_enqueue(NULL); // EOF + return 0; +} + +static void dbg_dap_init(void) { + cmd_queue.mutex = SDL_CreateMutex(); + cmd_queue.cond_nonempty = SDL_CreateCond(); + +#ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + + SDL_CreateThread(read_command_thread, "Debugger", NULL); + + while (!initialized) { + char *msg = cmdq_dequeue(); + if (!msg) + break; + handle_message(msg); + } +} + +static void dbg_dap_quit(void) { + emit_terminated_event(); +} + +static void dbg_dap_repl(void) { + emit_stop_event(); + dbg_state = DBG_RUNNING; + + boolean continue_repl = true; + while (continue_repl) { + char *msg = cmdq_dequeue(); + if (!msg) + break; + continue_repl = handle_message(msg); + } +} + +static void dbg_dap_onsleep(void) { + while (!cmdq_isempty()) { + char *msg = cmdq_dequeue(); + if (!msg) + break; + handle_message(msg); + } + if (dbg_state == DBG_STOPPED_INTERRUPT) + dbg_main(); +} + +DebuggerImpl dbg_dap_impl = { + .init = dbg_dap_init, + .quit = dbg_dap_quit, + .repl = dbg_dap_repl, + .onsleep = dbg_dap_onsleep, +}; diff --git a/src/debugger_private.h b/src/debugger_private.h index 95e4065..20cd767 100644 --- a/src/debugger_private.h +++ b/src/debugger_private.h @@ -47,14 +47,24 @@ typedef struct { StackFrame frames[]; } StackTrace; +typedef struct { + void (*init)(void); + void (*quit)(void); + void (*repl)(void); + void (*onsleep)(void); +} DebuggerImpl; + +extern DebuggerImpl dbg_cui_impl; +extern DebuggerImpl dbg_dap_impl; +extern DebuggerImpl *dbg_impl; + Breakpoint *dbg_find_breakpoint(int page, int addr); Breakpoint *dbg_set_breakpoint(int page, int addr, boolean is_internal); boolean dbg_delete_breakpoint(int no); +void dbg_delete_breakpoints_in_page(int page); void dbg_step(void); void dbg_next(void); int dbg_lookup_var(const char *name); StackTrace *dbg_stack_trace(void); -void dbg_cui_init(void); -void dbg_cui_repl(void); #endif // __DEBUGGER_PRIVATE_H__ diff --git a/src/sdl_draw.c b/src/sdl_draw.c index d56836e..fe2f1e6 100644 --- a/src/sdl_draw.c +++ b/src/sdl_draw.c @@ -76,8 +76,7 @@ void sdl_updateScreen(void) { void sdl_sleep(int msec) { sdl_updateScreen(); - if (dbg_interrupted()) - dbg_main(); + dbg_onsleep(); #ifdef __EMSCRIPTEN__ emscripten_sleep(msec); #else @@ -97,8 +96,7 @@ EM_JS(void, wait_vsync, (void), { void sdl_wait_vsync() { sdl_updateScreen(); - if (dbg_interrupted()) - dbg_main(); + dbg_onsleep(); #ifdef __EMSCRIPTEN__ wait_vsync(); #else diff --git a/src/xsystem35.c b/src/xsystem35.c index e57b351..b2e6d7d 100644 --- a/src/xsystem35.c +++ b/src/xsystem35.c @@ -88,7 +88,11 @@ static void check_profile(); /* for debugging */ static FILE *fpdebuglog; static int debuglv = DEBUGLEVEL; -static boolean debugger_enabled; +enum { + DEBUGGER_DISABLED, + DEBUGGER_CUI, + DEBUGGER_DAP, +} debugger_mode = DEBUGGER_DISABLED; static int audio_buffer_size = 0; @@ -246,6 +250,7 @@ static void sys35_init() { } static void sys35_remove() { + dbg_quit(); mus_exit(); ags_remove(); #ifdef ENABLE_GTK @@ -293,7 +298,9 @@ static void sys35_ParseOption(int *argc, char **argv) { fclose(fp); gameResourceFile = argv[i + 1]; } else if (0 == strcmp(argv[i], "-debug")) { - debugger_enabled = true; + debugger_mode = DEBUGGER_CUI; + } else if (0 == strcmp(argv[i], "-debug_dap")) { + debugger_mode = DEBUGGER_DAP; } else if (0 == strcmp(argv[i], "-devcd")) { if (argv[i + 1] != NULL) { cd_set_devicename(argv[i + 1]); @@ -455,7 +462,7 @@ static void registerGameFiles(void) { SYSERROR("No Scenario data available\n"); for (int type = 0; type < DRIFILETYPEMAX; type++) { boolean use_mmap = true; - if (debugger_enabled && type == DRIFILE_SCO) { + if (debugger_mode != DEBUGGER_DISABLED && type == DRIFILE_SCO) { // Do not mmap scenario files so that BREAKPOINT instructions can be inserted. use_mmap = false; } @@ -525,10 +532,10 @@ int main(int argc, char **argv) { #endif menu_init(); - if (debugger_enabled) { + if (debugger_mode != DEBUGGER_DISABLED) { char symbols_path[500]; snprintf(symbols_path, sizeof(symbols_path), "%s.symbols", nact->files.game_fname[DRIFILE_SCO][0]); - dbg_init(symbols_path); + dbg_init(symbols_path, debugger_mode == DEBUGGER_DAP); } nact_main();