mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-30 10:48:21 +03:00
Add support for Debug Adapter Protocol
If -debug_dap flag is given, xsystem35 speaks the Debug Adapter Protocol (https://microsoft.github.io/debug-adapter-protocol/) via stdin/stdout. This enables xsystem35 to work as a backend of VS Code's Debugger (and possibly other debuggers that support DAP).
This commit is contained in:
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ../../
|
||||
|
||||
+8
-1
@@ -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)
|
||||
|
||||
|
||||
+34
-3
@@ -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();
|
||||
}
|
||||
|
||||
+6
-4
@@ -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
|
||||
|
||||
+17
-2
@@ -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,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* Copyright (C) 2021 kichikuou <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL_mutex.h>
|
||||
#include <SDL_thread.h>
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#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,
|
||||
};
|
||||
+12
-2
@@ -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__
|
||||
|
||||
+2
-4
@@ -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
|
||||
|
||||
+12
-5
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user