/* Copyright (C) 2023 Nunuhara Cabbage * * Based largely on DAP implementation from xsystem35-sdl2 * 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, see . */ #include #include #include #include #include #ifdef _WIN32 #include #include #endif #define VM_PRIVATE #include "system4.h" #include "system4/string.h" #include "system4/utfsjis.h" #include "xsystem4.h" #include "base64.h" #include "cJSON.h" #include "debugger.h" #include "gfx/gfx.h" #include "json.h" #include "msgqueue.h" #include "parts/parts_internal.h" #include "sact.h" #include "scene.h" #include "vm.h" #include "vm/heap.h" #include "vm/page.h" #define THREAD_ID 1 static enum { DAP_UNINITIALIZED, DAP_INITIALIZED, DAP_RUNNING, DAP_STOPPED } dap_state = DAP_UNINITIALIZED; static struct msgq *queue; static void json_add_sjis_to_object(cJSON *obj, const char *name, const char *sjis) { char *utf = sjis2utf(sjis, 0); cJSON_AddStringToObject(obj, name, utf); free(utf); } static void send_json(cJSON *json) { static int seq = 1; 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_Delete(json); } static void send_response(cJSON *response, bool success) { cJSON_AddBoolToObject(response, "success", success); send_json(response); } static void emit_event(const char *name, cJSON *body) { cJSON *event = cJSON_CreateObject(); cJSON_AddStringToObject(event, "type", "event"); cJSON_AddStringToObject(event, "event", name); if (body) { cJSON_AddItemToObjectCS(event, "body", body); } send_json(event); } static void emit_initialized_event(void) { emit_event("initialized", NULL); } static void emit_terminated_event(void) { emit_event("terminated", NULL); } static void emit_stopped_event(void) { cJSON *body = cJSON_CreateObject(); // TODO: specify correct reason cJSON_AddStringToObject(body, "reason", "pause"); emit_event("stopped", body); } static void emit_output_event(const char *category, const char *output) { cJSON *body = cJSON_CreateObject(); cJSON_AddStringToObject(body, "category", category); cJSON_AddStringToObject(body, "output", output); emit_event("output", body); } static void cmd_initialize(cJSON *args, cJSON *resp) { cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddBoolToObject(body, "supportsConfigurationDoneRequest", true); //cJSON_AddBoolToObject(body, "supportsFunctionBreakpoints", true); //cJSON_AddBoolToObject(body, "supportsConditionalBreakpoints", true); //cJSON_AddBoolToObject(body, "supportsHitConditionalBreakpoints", true); cJSON_AddBoolToObject(body, "supportsEvaluateForHovers", true); //cJSON_AddBoolToObject(body, "supportsSetVariable", true); //cJSON_AddBoolToObject(body, "supportsSetExpression", true); //cJSON_AddBoolToObject(body, "supportsTerminateRequest", true); cJSON_AddBoolToObject(body, "supportsInstructionBreakpoints", true); send_response(resp, true); emit_initialized_event(); dap_state = DAP_INITIALIZED; } static void cmd_launch(cJSON *args, cJSON *resp) { if (dap_state != DAP_INITIALIZED) { send_response(resp, false); return; } // TODO: handle noDebug option send_response(resp, true); dap_state = DAP_RUNNING; } static void cmd_configurationDone(cJSON *args, cJSON *resp) { send_response(resp, true); } static void cmd_stackTrace(cJSON *args, cJSON *resp) { cJSON *body, *stack_frames, *frame; // FIXME: we ignore startFrame/levels arguments cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddNumberToObject(body, "totalFrames", call_stack_ptr); cJSON_AddItemToObjectCS(body, "stackFrames", stack_frames = cJSON_CreateArray()); for (int i = 0; i < call_stack_ptr; i++) { cJSON_AddItemToArray(stack_frames, frame = cJSON_CreateObject()); cJSON_AddNumberToObject(frame, "id", i); // this *shouldn't* happen, but since we're in the debugger... int fno = call_stack[i].fno; if (fno < 0 || fno >= ain->nr_functions) { cJSON_AddStringToObject(frame, "name", ""); } else { 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].call_address); } cJSON_AddStringToObject(frame, "instructionPointerReference", ip); cJSON_AddNumberToObject(frame, "line", 0); cJSON_AddNumberToObject(frame, "column", 0); } send_response(resp, true); } enum var_ref_type { VAR_REF_ARGUMENTS = 0, VAR_REF_LOCALS = 1, VAR_REF_MEMBERS = 2, VAR_REF_GENERIC = 3, }; static inline unsigned var_ref(unsigned slot, enum var_ref_type type) { return (slot << 4) | type; } static inline unsigned var_ref_slot(unsigned var_ref) { return var_ref >> 4; } static inline enum var_ref_type var_ref_type(unsigned var_ref) { return var_ref & 0xF; } static void cmd_scopes(cJSON *args, cJSON *resp) { if (!args) { send_response(resp, false); return; } cJSON *j_id = cJSON_GetObjectItemCaseSensitive(args, "frameId"); if (!j_id || !cJSON_IsNumber(j_id)) { send_response(resp, false); return; } int id = j_id->valueint; if (id < 0 || id >= call_stack_ptr) { send_response(resp, false); return; } if (call_stack[id].fno < 0 || call_stack[id].fno >= ain->nr_functions) { send_response(resp, false); return; } cJSON *body, *scopes, *arg_scope, *loc_scope, *mem_scope; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddItemToObjectCS(body, "scopes", scopes = cJSON_CreateArray()); int l_page = call_stack[id].page_slot; if (page_index_valid(l_page) && heap_get_page(l_page)->type == LOCAL_PAGE) { int arg_ref = var_ref(l_page, VAR_REF_ARGUMENTS); int loc_ref = var_ref(l_page, VAR_REF_LOCALS); cJSON_AddItemToArray(scopes, arg_scope = cJSON_CreateObject()); cJSON_AddStringToObject(arg_scope, "name", "Arguments"); cJSON_AddStringToObject(arg_scope, "presentationHint", "arguments"); cJSON_AddNumberToObject(arg_scope, "variablesReference", arg_ref); cJSON_AddItemToArray(scopes, loc_scope = cJSON_CreateObject()); cJSON_AddStringToObject(loc_scope, "name", "Locals"); cJSON_AddStringToObject(loc_scope, "presentationHint", "locals"); cJSON_AddNumberToObject(loc_scope, "variablesReference", loc_ref); } int s_page = call_stack[id].struct_page; if (page_index_valid(s_page) && heap_get_page(s_page)->type == STRUCT_PAGE) { int mem_ref = var_ref(s_page, VAR_REF_MEMBERS); cJSON_AddItemToArray(scopes, mem_scope = cJSON_CreateObject()); cJSON_AddStringToObject(mem_scope, "name", "Members"); cJSON_AddStringToObject(mem_scope, "presentationHint", "locals"); cJSON_AddNumberToObject(mem_scope, "variablesReference", mem_ref); } // TODO: stack values? send_response(resp, true); } static bool type_is_structured(struct ain_type *type) { switch (type->data) { case AIN_STRUCT: case AIN_REF_STRUCT: case AIN_ARRAY_TYPE: case AIN_REF_ARRAY_TYPE: return true; default: return false; } } static int make_var_ref(struct ain_type *type, union vm_value value) { if (type_is_structured(type) && page_index_valid(value.i)) return var_ref(value.i, VAR_REF_GENERIC); return 0; } static cJSON *value_to_json(const char *name, struct ain_type *type, union vm_value value) { char *s_type = ain_strtype_d(ain, type); struct string *s_val = dbg_value_to_string(type, value, 0); cJSON *json = cJSON_CreateObject(); json_add_sjis_to_object(json, "name", name); json_add_sjis_to_object(json, "value", s_val->text); json_add_sjis_to_object(json, "type", s_type); cJSON_AddNumberToObject(json, "variablesReference", make_var_ref(type, value)); free_string(s_val); free(s_type); return json; } static cJSON *var_to_json(struct ain_variable *var, union vm_value value) { return value_to_json(var->name, &var->type, value); } static void add_arguments_to_array(cJSON *array, struct page *page) { struct ain_function *f = &ain->functions[page->index]; for (int i = 0; i < f->nr_args; i++) { cJSON_AddItemToArray(array, var_to_json(&f->vars[i], page->values[i])); } } static void add_locals_to_array(cJSON *array, struct page *page) { struct ain_function *f = &ain->functions[page->index]; for (int i = f->nr_args; i < f->nr_vars; i++) { cJSON_AddItemToArray(array, var_to_json(&f->vars[i], page->values[i])); } } static void add_variables_to_array(cJSON *array, struct page *page) { struct ain_function *f = &ain->functions[page->index]; for (int i = 0; i < f->nr_vars; i++) { cJSON_AddItemToArray(array, var_to_json(&f->vars[i], page->values[i])); } } static void add_members_to_array(cJSON *array, struct page *page) { struct ain_struct *s = &ain->structures[page->index]; for (int i = 0; i < s->nr_members; i++) { cJSON_AddItemToArray(array, var_to_json(&s->members[i], page->values[i])); } } static void add_globals_to_array(cJSON *array, struct page *page) { for (int i = 0; i < ain->nr_globals; i++) { cJSON_AddItemToArray(array, var_to_json(&ain->globals[i], page->values[i])); } } static void add_array_elements_to_array(cJSON *array, struct page *page) { if (!page) return; for (int i = 0; i < page->nr_vars; i++) { char name[512]; snprintf(name, 512, "[%d]", i); struct ain_type type; type.data = variable_type(page, i, &type.struc, &type.rank); cJSON_AddItemToArray(array, value_to_json(name, &type, page->values[i])); } } static void add_page_to_array(cJSON *array, struct page *page) { switch (page->type) { case GLOBAL_PAGE: add_globals_to_array(array, page); break; case LOCAL_PAGE: add_variables_to_array(array, page); break; case STRUCT_PAGE: add_members_to_array(array, page); break; case ARRAY_PAGE: add_array_elements_to_array(array, page); break; default: break; } } static cJSON *var_ref_to_json(struct page *page, enum var_ref_type type) { cJSON *vars = cJSON_CreateArray(); if (type == VAR_REF_ARGUMENTS) { add_arguments_to_array(vars, page); } else if (type == VAR_REF_LOCALS) { add_locals_to_array(vars, page); } else if (type == VAR_REF_MEMBERS) { add_members_to_array(vars, page); } else if (type == VAR_REF_GENERIC) { add_page_to_array(vars, page); } else { cJSON_Delete(vars); return NULL; } return vars; } static void cmd_variables(cJSON *args, cJSON *resp) { if (!args) { send_response(resp, false); return; } cJSON *j_ref = cJSON_GetObjectItemCaseSensitive(args, "variablesReference"); if (!j_ref || !cJSON_IsNumber(j_ref)) { send_response(resp, false); return; } int slot = var_ref_slot(j_ref->valueint); enum var_ref_type type = var_ref_type(j_ref->valueint); if (!page_index_valid(slot)) { send_response(resp, false); return; } struct page *page = heap_get_page(slot); // FIXME: filter/start/count are ignored cJSON *vars = var_ref_to_json(page, type); if (!vars) { send_response(resp, false); return; } cJSON *body = cJSON_CreateObject(); cJSON_AddItemToObjectCS(body, "variables", vars); cJSON_AddItemToObjectCS(resp, "body", body); send_response(resp, true); } static void cmd_setInstructionBreakpoints(cJSON *args, cJSON *resp) { static uint32_t *old_breakpoints = NULL; static int nr_old_breakpoints = 0; uint32_t *addresses = NULL; if (!args) { goto fail; } cJSON *j_bp = cJSON_GetObjectItemCaseSensitive(args, "breakpoints"); if (!j_bp || !cJSON_IsArray(j_bp)) { goto fail; } int nr_breakpoints = cJSON_GetArraySize(j_bp); addresses = xcalloc(nr_breakpoints, sizeof(uint32_t)); int i; cJSON *e; cJSON_ArrayForEachIndex(i, e, j_bp) { if (!cJSON_IsObject(e)) { goto fail; } cJSON *ref = cJSON_GetObjectItemCaseSensitive(e, "instructionReference"); if (!ref || !cJSON_IsString(ref)) { goto fail; } char *endptr; addresses[i] = strtol(ref->valuestring, &endptr, 16); if (ref->valuestring[0] == '\0' || *endptr != '\0') { goto fail; } // FIXME: offset is ignored // TODO: condition // TODO: hitCondition } // clear old breakpoints for (int i = 0; i < nr_old_breakpoints; i++) { dbg_clear_breakpoint(old_breakpoints[i], NULL); } cJSON *body, *breakpoints, *bp; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddItemToObjectCS(body, "breakpoints", breakpoints = cJSON_CreateArray()); for (int i = 0; i < nr_breakpoints; i++) { char s_addr[9]; snprintf(s_addr, 9, "%x", addresses[i]); cJSON_AddItemToArray(breakpoints, bp = cJSON_CreateObject()); cJSON_AddStringToObject(bp, "instructionReference", s_addr); bool verified = dbg_set_address_breakpoint(addresses[i], NULL, NULL); cJSON_AddBoolToObject(bp, "verified", verified); // TODO: id, message, offset } // save new breakpoints list free(old_breakpoints); old_breakpoints = addresses; nr_old_breakpoints = nr_breakpoints; send_response(resp, true); return; fail: free(addresses); send_response(resp, false); } static void cmd_evaluate(cJSON *args, cJSON *resp) { if (!args) { send_response(resp, false); return; } cJSON *j_expr = cJSON_GetObjectItemCaseSensitive(args, "expression"); if (!j_expr || !cJSON_IsString(j_expr)) { send_response(resp, false); return; } // TODO: frameId struct ain_type type; union vm_value val = dbg_eval_string(j_expr->valuestring, &type); struct string *s_val = dbg_value_to_string(&type, val, 0); char *s_type = ain_strtype_d(ain, &type); cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); json_add_sjis_to_object(body, "result", s_val->text); json_add_sjis_to_object(body, "type", s_type); cJSON_AddNumberToObject(body, "variablesReference", make_var_ref(&type, val)); // TODO: presentationHint send_response(resp, true); } static void cmd_continue(cJSON *args, cJSON *resp) { send_response(resp, true); dap_state = DAP_RUNNING; dbg_continue(); } static void cmd_pause(cJSON *args, cJSON *resp) { if (dap_state < DAP_RUNNING) { cJSON_AddBoolToObject(resp, "success", false); return; } cJSON_AddBoolToObject(resp, "success", true); send_json(resp); if (dap_state != DAP_STOPPED) { dbg_repl(); } } static void cmd_stepIn(cJSON *args, cJSON *resp) { dbg_set_step_into_breakpoint(); send_response(resp, true); dap_state = DAP_RUNNING; dbg_continue(); } static void cmd_stepOut(cJSON *args, cJSON *resp) { dbg_set_finish_breakpoint(); send_response(resp, true); dap_state = DAP_RUNNING; dbg_continue(); } static void cmd_next(cJSON *args, cJSON *resp) { dbg_set_step_over_breakpoint(); send_response(resp, true); dap_state = DAP_RUNNING; dbg_continue(); } static void cmd_threads(cJSON *args, cJSON *resp) { cJSON *body, *threads, *thread; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddItemToObjectCS(body, "threads", threads = cJSON_CreateArray()); cJSON_AddItemToArray(threads, thread = cJSON_CreateObject()); cJSON_AddNumberToObject(thread, "id", 0); cJSON_AddStringToObject(thread, "name", "main_thread"); send_response(resp, true); } static void cmd_setVariable(cJSON *args, cJSON *resp) { // TODO send_response(resp, false); } static void cmd_disconnect(cJSON *args, cJSON *resp) { send_response(resp, true); vm_exit(0); } // get scene metadata static void cmd_xsystem4_scene(cJSON *args, cJSON *resp) { cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddItemToObjectCS(body, "entities", scene_to_json(true)); send_response(resp, true); } static bool get_id(cJSON *args, const char *id_name, int *id_out) { if (!args) return false; cJSON *j_id = cJSON_GetObjectItemCaseSensitive(args, id_name); if (!j_id || !cJSON_IsNumber(j_id)) return false; *id_out = j_id->valueint; return true; } // render a scene entity (using sprite->render()) static void cmd_xsystem4_renderEntity(cJSON *args, cJSON *resp) { int id; struct sprite *sp; if (!get_id(args, "entityId", &id) || !(sp = scene_get(id))) { send_response(resp, false); return; } // create output texture Texture t; gfx_init_texture_rgba(&t, config.view_width, config.view_height, (SDL_Color){0,0,0,0}); // render entity to texture GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, &t, 0, 0, t.w, t.h); sp->render(sp); gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo); // send response cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddNumberToObject(body, "entityId", id); cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(&t)); send_response(resp, true); gfx_delete_texture(&t); } // get the stored texture data of a sact_sprite static void cmd_xsystem4_spriteTexture(cJSON *args, cJSON *resp) { int id; struct sact_sprite *sp; if (!get_id(args, "spriteId", &id) || !(sp = sact_try_get_sprite(id)) || !sp->texture.handle) { send_response(resp, false); return; } cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddNumberToObject(body, "spriteId", id); cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(&sp->texture)); if (sp->text.texture.handle) { cJSON_AddItemToObjectCS(body, "textTexture", texture_to_json_with_pixels(&sp->text.texture)); } send_response(resp, true); } static struct parts *get_parts(cJSON *args, int *id_out) { struct parts *parts; if (!get_id(args, "partsId", id_out) || !(parts = parts_try_get(*id_out)) || !parts->states[parts->state].common.texture.handle) return NULL; return parts; } // render a parts object static void cmd_xsystem4_renderParts(cJSON *args, cJSON *resp) { int parts_id; struct parts *parts = get_parts(args, &parts_id); if (!parts) { send_response(resp, false); return; } // create output texture Texture t; gfx_init_texture_rgba(&t, config.view_width, config.view_height, (SDL_Color){0,0,0,0}); // render parts to textre GLuint fbo = gfx_set_framebuffer(GL_DRAW_FRAMEBUFFER, &t, 0, 0, t.w, t.h); parts_render(parts); gfx_reset_framebuffer(GL_DRAW_FRAMEBUFFER, fbo); // send response cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddNumberToObject(body, "partsId", parts_id); cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(&t)); send_response(resp, true); gfx_delete_texture(&t); } // get the stored texture data of a parts object static void cmd_xsystem4_partsTexture(cJSON *args, cJSON *resp) { int parts_id; struct parts *parts = get_parts(args, &parts_id);; if (!parts) { send_response(resp, false); return; } Texture *t = &parts->states[parts->state].common.texture; cJSON *body; cJSON_AddItemToObjectCS(resp, "body", body = cJSON_CreateObject()); cJSON_AddNumberToObject(body, "partsId", parts_id); cJSON_AddItemToObjectCS(body, "texture", texture_to_json_with_pixels(t)); send_response(resp, true); } static struct { const char *name; void (*fun)(cJSON *args, cJSON *resp); bool continue_repl; } commands[] = { { "initialize", cmd_initialize, true }, { "launch", cmd_launch, true }, { "configurationDone", cmd_configurationDone, true }, { "continue", cmd_continue, false }, { "stackTrace", cmd_stackTrace, true }, { "stepIn", cmd_stepIn, false }, { "stepOut", cmd_stepOut, false }, { "next", cmd_next, false }, { "pause", cmd_pause, true }, { "evaluate", cmd_evaluate, true }, { "setInstructionBreakpoints", cmd_setInstructionBreakpoints, true }, { "threads", cmd_threads, true }, { "scopes", cmd_scopes, true }, { "variables", cmd_variables, true }, { "setVariable", cmd_setVariable, true }, { "disconnect", cmd_disconnect, true }, // DAP extensions { "xsystem4.scene", cmd_xsystem4_scene, true }, { "xsystem4.renderEntity", cmd_xsystem4_renderEntity, true }, { "xsystem4.spriteTexture", cmd_xsystem4_spriteTexture, true }, { "xsystem4.renderParts", cmd_xsystem4_renderParts, true }, { "xsystem4.partsTexture", cmd_xsystem4_partsTexture, true }, }; static bool handle_request(cJSON *request) { 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)) { WARNING("protocol error: command is not a string"); cJSON_Delete(resp); return true; } for (unsigned i = 0; i < sizeof(commands) / sizeof(*commands); i++) { if (!strcmp(command->valuestring, commands[i].name)) { commands[i].fun(args, resp); return commands[i].continue_repl; } } WARNING("unknown command \"%s\"", command->valuestring); return true; } static bool handle_message(char *msg) { cJSON *json = cJSON_Parse(msg); cJSON *type = cJSON_GetObjectItemCaseSensitive(json, "type"); bool continue_repl = true; if (cJSON_IsString(type) && !strcmp(type->valuestring, "request")) continue_repl = handle_request(json); cJSON_Delete(json); free(msg); return continue_repl; } 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) { WARNING("Debug Adapter Protocol error: no Content-Length header"); continue; } char *buf = malloc(content_length); if (fread(buf, content_length, 1, stdin) != 1) { WARNING("fread(stdin): %s", strerror(errno)); free(buf); continue; } msgq_enqueue(queue, buf); content_length = -1; } else { WARNING("Unknown Debug Adapter Protocol header: %s", header); } } msgq_enqueue(queue, NULL); // EOF return 0; } void dbg_dap_init(void) { atexit(dbg_dap_quit); queue = msgq_new(); #ifdef _WIN32 _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); #endif SDL_CreateThread(read_command_thread, "Debugger", NULL); while (dap_state < DAP_RUNNING) { char *msg = msgq_dequeue(queue); if (!msg) break; handle_message(msg); } } void dbg_dap_quit(void) { emit_terminated_event(); } void dbg_dap_repl(void) { emit_stopped_event(); dap_state = DAP_STOPPED; bool continue_repl = true; while (continue_repl) { char *msg = msgq_dequeue(queue); if (!msg) break; continue_repl = handle_message(msg); } } void dbg_dap_handle_messages(void) { while (!msgq_isempty(queue)) { char *msg = msgq_dequeue(queue); if (!msg) break; handle_message(msg); } } void dbg_dap_log(const char *log, const char *fmt, va_list ap) { size_t len = max(1024, strlen(fmt) * 2); char *buf = xmalloc(len); vsnprintf(buf, len, fmt, ap); emit_output_event(log, buf); free(buf); }