mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-19 19:10:17 +00:00
Gsoc 2025 debugger (#390)
* implement `evaluate` request * Update main.c * Update main.c * change port to 6110 * Update main.c * simplify the workdir process and minor optimizations * implement exit event * fix bp not hit because of path format * implement output event * add `ctype.h` to `core.c` * implement exception breakpoint * fix assert triggered when called isspace --------- Co-authored-by: blueloveTH <blueloveth@foxmail.com>
This commit is contained in:
parent
054fcba7e4
commit
e40c4af55c
@ -6,7 +6,7 @@
|
||||
#include "pocketpy/pocketpy.h"
|
||||
|
||||
typedef enum { C11_STEP_IN, C11_STEP_OVER, C11_STEP_OUT, C11_STEP_CONTINUE } C11_STEP_MODE;
|
||||
|
||||
typedef enum { C11_DEBUGGER_NOSTOP, C11_DEBUGGER_STEP, C11_DEBUGGER_EXCEPTION, C11_DEBUGGER_BP} C11_STOP_REASON;
|
||||
typedef enum {
|
||||
C11_DEBUGGER_SUCCESS = 0,
|
||||
C11_DEBUGGER_EXIT = 1,
|
||||
@ -16,11 +16,13 @@ typedef enum {
|
||||
|
||||
void c11_debugger_init(void);
|
||||
void c11_debugger_set_step_mode(C11_STEP_MODE mode);
|
||||
void c11_debugger_exception_on_trace(py_Ref exc);
|
||||
C11_DEBUGGER_STATUS c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event);
|
||||
const char* c11_debugger_excinfo(const char ** message);
|
||||
void c11_debugger_frames(c11_sbuf* buffer);
|
||||
void c11_debugger_scopes(int frameid, c11_sbuf* buffer);
|
||||
bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer);
|
||||
int c11_debugger_setbreakpoint(const char* filename, int lineno);
|
||||
int c11_debugger_reset_breakpoints_by_source(const char* sourcesname);
|
||||
int c11_debugger_should_pause(void);
|
||||
C11_STOP_REASON c11_debugger_should_pause(void);
|
||||
int c11_debugger_should_keep_pause(void);
|
||||
|
@ -26,7 +26,7 @@ static void SourceData__ctor(struct SourceData* self,
|
||||
self->source = c11_sbuf__submit(&ss);
|
||||
// remove trailing newline
|
||||
int last_index = self->source->size - 1;
|
||||
while(last_index >= 0 && isspace(self->source->data[last_index])) {
|
||||
while(last_index >= 0 && isspace((unsigned char)self->source->data[last_index])) {
|
||||
last_index--;
|
||||
}
|
||||
if(last_index >= 0) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "pocketpy/interpreter/frame.h"
|
||||
#include "pocketpy/objects/exception.h"
|
||||
#include "pocketpy/pocketpy.h"
|
||||
#include <ctype.h>
|
||||
|
||||
@ -26,6 +27,8 @@ typedef struct c11_debugger_scope_index {
|
||||
static struct c11_debugger {
|
||||
py_Frame* current_frame;
|
||||
const char* current_filename;
|
||||
const char* current_excname;
|
||||
const char* current_excmessage;
|
||||
enum py_TraceEvent current_event;
|
||||
|
||||
int curr_stack_depth;
|
||||
@ -34,7 +37,9 @@ static struct c11_debugger {
|
||||
int step_line;
|
||||
C11_STEP_MODE step_mode;
|
||||
bool keep_suspend;
|
||||
bool isexceptionmode;
|
||||
|
||||
c11_vector* exception_stacktrace;
|
||||
c11_vector breakpoints;
|
||||
c11_vector py_frames;
|
||||
c11_smallmap_d2index scopes_query_cache;
|
||||
@ -76,6 +81,7 @@ void c11_debugger_init() {
|
||||
debugger.pause_allowed_depth = -1;
|
||||
debugger.step_line = -1;
|
||||
debugger.keep_suspend = false;
|
||||
debugger.isexceptionmode = false;
|
||||
debugger.step_mode = C11_STEP_CONTINUE;
|
||||
init_structures();
|
||||
}
|
||||
@ -96,6 +102,30 @@ C11_DEBUGGER_STATUS c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent ev
|
||||
return C11_DEBUGGER_SUCCESS;
|
||||
}
|
||||
|
||||
void c11_debugger_exception_on_trace(py_Ref exc) {
|
||||
BaseException* ud = py_touserdata(exc);
|
||||
c11_vector* stacktrace = &ud->stacktrace;
|
||||
const char* name = py_tpname(exc->type);
|
||||
const char* message;
|
||||
bool ok = py_str(exc);
|
||||
if(!ok || !py_isstr(py_retval())) {
|
||||
message = "<exception str() failed>";
|
||||
} else {
|
||||
message = c11_strdup(py_tostr(py_retval()));
|
||||
}
|
||||
debugger.exception_stacktrace = stacktrace;
|
||||
debugger.isexceptionmode = true;
|
||||
debugger.current_excname = name;
|
||||
debugger.current_excmessage = message;
|
||||
clear_structures();
|
||||
|
||||
}
|
||||
|
||||
const char* c11_debugger_excinfo(const char ** message){
|
||||
*message = debugger.current_excmessage;
|
||||
return debugger.current_excname;
|
||||
}
|
||||
|
||||
void c11_debugger_set_step_mode(C11_STEP_MODE mode) {
|
||||
switch(mode) {
|
||||
case C11_STEP_IN: debugger.pause_allowed_depth = INT32_MAX; break;
|
||||
@ -105,12 +135,12 @@ void c11_debugger_set_step_mode(C11_STEP_MODE mode) {
|
||||
break;
|
||||
case C11_STEP_OUT: debugger.pause_allowed_depth = debugger.curr_stack_depth - 1; break;
|
||||
case C11_STEP_CONTINUE: debugger.pause_allowed_depth = -1; break;
|
||||
default: break;
|
||||
}
|
||||
debugger.step_mode = mode;
|
||||
debugger.keep_suspend = false;
|
||||
}
|
||||
|
||||
|
||||
int c11_debugger_setbreakpoint(const char* filename, int lineno) {
|
||||
c11_debugger_breakpoint breakpoint = {.sourcename = c11_strdup(filename), .lineno = lineno};
|
||||
c11_vector__push(c11_debugger_breakpoint, &debugger.breakpoints, breakpoint);
|
||||
@ -137,33 +167,31 @@ int c11_debugger_reset_breakpoints_by_source(const char* sourcesname) {
|
||||
}
|
||||
|
||||
bool c11_debugger_path_equal(const char* path1, const char* path2) {
|
||||
if (path1 == NULL || path2 == NULL) return false;
|
||||
while (*path1 && *path2) {
|
||||
if(path1 == NULL || path2 == NULL) return false;
|
||||
while(*path1 && *path2) {
|
||||
char c1 = (*path1 == '\\') ? '/' : *path1;
|
||||
char c2 = (*path2 == '\\') ? '/' : *path2;
|
||||
c1 = (char)tolower((unsigned char)c1);
|
||||
c2 = (char)tolower((unsigned char)c2);
|
||||
if (c1 != c2) return false;
|
||||
if(c1 != c2) return false;
|
||||
path1++;
|
||||
path2++;
|
||||
}
|
||||
return *path1 == *path2;
|
||||
}
|
||||
|
||||
|
||||
int c11_debugger_should_pause() {
|
||||
if(debugger.current_event == TRACE_EVENT_POP) return false;
|
||||
bool should_pause = false;
|
||||
C11_STOP_REASON c11_debugger_should_pause() {
|
||||
if(debugger.current_event == TRACE_EVENT_POP && !debugger.isexceptionmode) return C11_DEBUGGER_NOSTOP;
|
||||
C11_STOP_REASON pause_resaon = C11_DEBUGGER_NOSTOP;
|
||||
int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth;
|
||||
int is_new_line = debugger.current_line != debugger.step_line;
|
||||
switch(debugger.step_mode) {
|
||||
case C11_STEP_IN: should_pause = true; break;
|
||||
|
||||
case C11_STEP_IN: pause_resaon = C11_DEBUGGER_STEP; break;
|
||||
case C11_STEP_OVER:
|
||||
if(is_new_line && is_out) should_pause = true;
|
||||
if(is_new_line && is_out) pause_resaon = C11_DEBUGGER_STEP;
|
||||
break;
|
||||
case C11_STEP_OUT:
|
||||
if(is_out) should_pause = true;
|
||||
if(is_out) pause_resaon = C11_DEBUGGER_STEP;
|
||||
break;
|
||||
case C11_STEP_CONTINUE:
|
||||
default: break;
|
||||
@ -172,18 +200,18 @@ int c11_debugger_should_pause() {
|
||||
c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, bp) {
|
||||
if(c11_debugger_path_equal(debugger.current_filename, bp->sourcename) &&
|
||||
debugger.current_line == bp->lineno) {
|
||||
should_pause = true;
|
||||
pause_resaon = C11_DEBUGGER_BP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(should_pause) { debugger.keep_suspend = true; }
|
||||
return should_pause;
|
||||
if(debugger.isexceptionmode) pause_resaon = C11_DEBUGGER_EXCEPTION;
|
||||
if(pause_resaon != C11_DEBUGGER_NOSTOP) { debugger.keep_suspend = true; }
|
||||
return pause_resaon;
|
||||
}
|
||||
|
||||
int c11_debugger_should_keep_pause(void) { return debugger.keep_suspend; }
|
||||
|
||||
|
||||
inline static c11_sv sv_from_cstr(const char* str) {
|
||||
c11_sv sv = {.data = str, .size = strlen(str)};
|
||||
return sv;
|
||||
@ -200,7 +228,7 @@ const inline static char* get_basename(const char* path) {
|
||||
return last_slash ? last_slash + 1 : path;
|
||||
}
|
||||
|
||||
void c11_debugger_frames(c11_sbuf* buffer) {
|
||||
void c11_debugger_normal_frames(c11_sbuf* buffer) {
|
||||
c11_sbuf__write_cstr(buffer, "{\"stackFrames\": [");
|
||||
int idx = 0;
|
||||
py_Frame* now_frame = debugger.current_frame;
|
||||
@ -226,6 +254,33 @@ void c11_debugger_frames(c11_sbuf* buffer) {
|
||||
pk_sprintf(buffer, "], \"totalFrames\": %d}", idx);
|
||||
}
|
||||
|
||||
void c11_debugger_exception_frames(c11_sbuf* buffer) {
|
||||
c11_sbuf__write_cstr(buffer, "{\"stackFrames\": [");
|
||||
int idx = 0;
|
||||
c11__foreach(BaseExceptionFrame, debugger.exception_stacktrace, it) {
|
||||
if(idx > 0) c11_sbuf__write_char(buffer, ',');
|
||||
int line = it->lineno;
|
||||
const char* filename = it->src->filename->data;
|
||||
const char* basename = get_basename(filename);
|
||||
const char* modname = it->name == NULL ? basename : it->name->data;
|
||||
pk_sprintf(
|
||||
buffer,
|
||||
"{\"id\": %d, \"name\": %Q, \"line\": %d, \"column\": 1, \"source\": {\"name\": %Q, \"path\": %Q}}",
|
||||
idx,
|
||||
sv_from_cstr(modname),
|
||||
line,
|
||||
sv_from_cstr(basename),
|
||||
sv_from_cstr(filename));
|
||||
idx++;
|
||||
}
|
||||
pk_sprintf(buffer, "], \"totalFrames\": %d}", idx);
|
||||
}
|
||||
|
||||
void c11_debugger_frames(c11_sbuf* buffer) {
|
||||
debugger.isexceptionmode ? c11_debugger_exception_frames(buffer)
|
||||
: c11_debugger_normal_frames(buffer);
|
||||
}
|
||||
|
||||
inline static c11_debugger_scope_index append_new_scope(int frameid) {
|
||||
assert(frameid < debugger.py_frames.length);
|
||||
py_Frame* requested_frame = c11__getitem(py_Frame*, &debugger.py_frames, frameid);
|
||||
@ -238,6 +293,17 @@ inline static c11_debugger_scope_index append_new_scope(int frameid) {
|
||||
return result;
|
||||
}
|
||||
|
||||
inline static c11_debugger_scope_index append_new_exception_scope(int frameid) {
|
||||
assert(frameid < debugger.exception_stacktrace->length);
|
||||
BaseExceptionFrame* requested_frame =
|
||||
c11__at(BaseExceptionFrame, debugger.exception_stacktrace, frameid);
|
||||
int base_index = py_list_len(python_vars);
|
||||
py_list_append(python_vars, &requested_frame->locals);
|
||||
py_list_append(python_vars, &requested_frame->globals);
|
||||
c11_debugger_scope_index result = {.locals_ref = base_index, .globals_ref = base_index + 1};
|
||||
return result;
|
||||
}
|
||||
|
||||
void c11_debugger_scopes(int frameid, c11_sbuf* buffer) {
|
||||
// query cache
|
||||
c11_debugger_scope_index* result =
|
||||
@ -250,7 +316,9 @@ void c11_debugger_scopes(int frameid, c11_sbuf* buffer) {
|
||||
if(result != NULL) {
|
||||
pk_sprintf(buffer, scopes_fmt, result->locals_ref, result->globals_ref);
|
||||
} else {
|
||||
c11_debugger_scope_index new_record = append_new_scope(frameid);
|
||||
c11_debugger_scope_index new_record = debugger.isexceptionmode
|
||||
? append_new_exception_scope(frameid)
|
||||
: append_new_scope(frameid);
|
||||
c11_smallmap_d2index__set(&debugger.scopes_query_cache, frameid, new_record);
|
||||
pk_sprintf(buffer, scopes_fmt, new_record.locals_ref, new_record.globals_ref);
|
||||
}
|
||||
|
@ -21,7 +21,8 @@
|
||||
X(threads) \
|
||||
X(configurationDone) \
|
||||
X(ready) \
|
||||
X(evaluate)
|
||||
X(evaluate) \
|
||||
X(exceptionInfo)
|
||||
|
||||
#define DECLARE_HANDLE_FN(name) void c11_dap_handle_##name(py_Ref arguments, c11_sbuf*);
|
||||
DAP_COMMAND_LIST(DECLARE_HANDLE_FN)
|
||||
@ -49,18 +50,23 @@ static struct c11_dap_server {
|
||||
c11_socket_handler server;
|
||||
c11_socket_handler toclient;
|
||||
bool isconfiguredone;
|
||||
bool isatttach;
|
||||
bool isfirstatttach;
|
||||
bool isattach;
|
||||
bool isclientready;
|
||||
} server;
|
||||
|
||||
void c11_dap_handle_initialize(py_Ref arguments, c11_sbuf* buffer) {
|
||||
c11_sbuf__write_cstr(buffer, "\"body\":{\"supportsConfigurationDoneRequest\":true}");
|
||||
c11_sbuf__write_cstr(buffer,
|
||||
"\"body\":{"
|
||||
"\"supportsConfigurationDoneRequest\":true,"
|
||||
"\"supportsExceptionInfoRequest\":true"
|
||||
"}");
|
||||
c11_sbuf__write_char(buffer, ',');
|
||||
}
|
||||
|
||||
void c11_dap_handle_attach(py_Ref arguments, c11_sbuf* buffer) { server.isatttach = true; }
|
||||
void c11_dap_handle_attach(py_Ref arguments, c11_sbuf* buffer) { server.isfirstatttach = true; }
|
||||
|
||||
void c11_dap_handle_launch(py_Ref arguments, c11_sbuf* buffer) { server.isatttach = true; }
|
||||
void c11_dap_handle_launch(py_Ref arguments, c11_sbuf* buffer) { server.isfirstatttach = true; }
|
||||
|
||||
void c11_dap_handle_ready(py_Ref arguments, c11_sbuf* buffer) { server.isclientready = true; }
|
||||
|
||||
@ -120,6 +126,38 @@ void c11_dap_handle_setBreakpoints(py_Ref arguments, c11_sbuf* buffer) {
|
||||
PK_FREE((void*)sourcename);
|
||||
}
|
||||
|
||||
inline static void c11_dap_build_ExceptionInfo(const char* exc_type, const char* exc_message, c11_sbuf* buffer) {
|
||||
const char* safe_type = exc_type ? exc_type : "UnknownException";
|
||||
const char* safe_message = exc_message ? exc_message : "No additional details available";
|
||||
|
||||
c11_sv type_sv = {.data = safe_type, .size = strlen(safe_type)};
|
||||
c11_sv message_sv = {.data = safe_message, .size = strlen(safe_message)};
|
||||
|
||||
c11_sbuf combined_buffer;
|
||||
c11_sbuf__ctor(&combined_buffer);
|
||||
pk_sprintf(&combined_buffer, "%s: %s", safe_type, safe_message);
|
||||
c11_string* combined_details = c11_sbuf__submit(&combined_buffer);
|
||||
|
||||
pk_sprintf(buffer,
|
||||
"{\"exceptionId\":%Q,"
|
||||
"\"description\":%Q,"
|
||||
"\"breakMode\":\"unhandled\"}",
|
||||
type_sv,
|
||||
(c11_sv){.data = combined_details->data, .size = combined_details->size}
|
||||
);
|
||||
|
||||
c11_string__delete(combined_details);
|
||||
}
|
||||
|
||||
void c11_dap_handle_exceptionInfo(py_Ref arguments, c11_sbuf* buffer) {
|
||||
const char* message;
|
||||
const char* name = c11_debugger_excinfo(&message);
|
||||
c11_sbuf__write_cstr(buffer, "\"body\":");
|
||||
c11_dap_build_ExceptionInfo(name, message, buffer);
|
||||
c11_sbuf__write_char(buffer, ',');
|
||||
}
|
||||
|
||||
|
||||
void c11_dap_handle_stackTrace(py_Ref arguments, c11_sbuf* buffer) {
|
||||
c11_sbuf__write_cstr(buffer, "\"body\":");
|
||||
c11_debugger_frames(buffer);
|
||||
@ -132,7 +170,6 @@ void c11_dap_handle_evaluate(py_Ref arguments, c11_sbuf* buffer) {
|
||||
py_printexc();
|
||||
c11__abort("[DEBUGGER ERROR] no expression found in evaluate request");
|
||||
}
|
||||
|
||||
// [eval, nil, expression, globals, locals]
|
||||
// vectorcall would pop the above 5 items
|
||||
// so we don't need to pop them manually
|
||||
@ -153,7 +190,7 @@ void c11_dap_handle_evaluate(py_Ref arguments, c11_sbuf* buffer) {
|
||||
py_str(py_retval());
|
||||
result = c11_strdup(py_tostr(py_retval()));
|
||||
}
|
||||
|
||||
|
||||
c11_sv result_sv = {.data = result, .size = strlen(result)};
|
||||
pk_sprintf(buffer, "{\"result\":%Q,\"variablesReference\":0}", result_sv);
|
||||
PK_FREE((void*)result);
|
||||
@ -289,12 +326,24 @@ void c11_dap_send_output_event(const char* category, const char* fmt, ...) {
|
||||
c11_string__delete(output_json);
|
||||
}
|
||||
|
||||
void c11_dap_send_stop_event() {
|
||||
c11_dap_send_event("stopped", "{\"threadId\":1,\"allThreadsStopped\":true}");
|
||||
void c11_dap_send_stop_event(C11_STOP_REASON reason) {
|
||||
if(reason == C11_DEBUGGER_NOSTOP) return;
|
||||
const char* reason_str = "unknown";
|
||||
switch(reason) {
|
||||
case C11_DEBUGGER_STEP: reason_str = "step"; break;
|
||||
case C11_DEBUGGER_EXCEPTION: reason_str = "exception"; break;
|
||||
case C11_DEBUGGER_BP: reason_str = "breakpoint"; break;
|
||||
default: break;
|
||||
}
|
||||
c11_sbuf buf;
|
||||
c11_sbuf__ctor(&buf);
|
||||
pk_sprintf(&buf, "{\"reason\":\"%s\",\"threadId\":1,\"allThreadsStopped\":true", reason_str);
|
||||
c11_sbuf__write_char(&buf, '}');
|
||||
c11_string* body_json = c11_sbuf__submit(&buf);
|
||||
c11_dap_send_event("stopped", body_json->data);
|
||||
c11_string__delete(body_json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void c11_dap_send_exited_event(int exitCode) {
|
||||
char body[64];
|
||||
snprintf(body, sizeof(body), "{\"exitCode\":%d}", exitCode);
|
||||
@ -382,9 +431,11 @@ const char* c11_dap_read_message() {
|
||||
void c11_dap_init_server(const char* hostname, unsigned short port) {
|
||||
server.dap_next_seq = 1;
|
||||
server.isconfiguredone = false;
|
||||
server.isclientready = false;
|
||||
server.isfirstatttach = false;
|
||||
server.isattach = false;
|
||||
server.buffer_begin = server.buffer_data;
|
||||
server.server = c11_socket_create(C11_AF_INET, C11_SOCK_STREAM, 0);
|
||||
server.isclientready = false;
|
||||
c11_socket_bind(server.server, hostname, port);
|
||||
c11_socket_listen(server.server, 0);
|
||||
// c11_dap_send_output_event("console", "[DEBUGGER INFO] : listen on %s:%hu\n",hostname,port);
|
||||
@ -418,9 +469,10 @@ inline static void c11_dap_handle_message() {
|
||||
void c11_dap_configure_debugger() {
|
||||
while(server.isconfiguredone == false) {
|
||||
c11_dap_handle_message();
|
||||
if(server.isatttach) {
|
||||
if(server.isfirstatttach) {
|
||||
c11_dap_send_initialized_event();
|
||||
server.isatttach = false;
|
||||
server.isfirstatttach = false;
|
||||
server.isattach = true;
|
||||
} else if(server.isclientready) {
|
||||
server.isclientready = false;
|
||||
return;
|
||||
@ -431,6 +483,7 @@ void c11_dap_configure_debugger() {
|
||||
|
||||
void c11_dap_tracefunc(py_Frame* frame, enum py_TraceEvent event) {
|
||||
py_sys_settrace(NULL, false);
|
||||
server.isattach = false;
|
||||
C11_DEBUGGER_STATUS result = c11_debugger_on_trace(frame, event);
|
||||
if(result == C11_DEBUGGER_EXIT) {
|
||||
// c11_dap_send_output_event("console", "[DEBUGGER INFO] : program exit\n");
|
||||
@ -451,14 +504,16 @@ void c11_dap_tracefunc(py_Frame* frame, enum py_TraceEvent event) {
|
||||
exit(1);
|
||||
}
|
||||
c11_dap_handle_message();
|
||||
if(!c11_debugger_should_pause()) {
|
||||
C11_STOP_REASON reason = c11_debugger_should_pause();
|
||||
if(reason != C11_DEBUGGER_NOSTOP) {
|
||||
py_sys_settrace(c11_dap_tracefunc, false);
|
||||
return;
|
||||
}
|
||||
c11_dap_send_stop_event();
|
||||
c11_dap_send_stop_event(reason);
|
||||
while(c11_debugger_should_keep_pause()) {
|
||||
c11_dap_handle_message();
|
||||
}
|
||||
server.isattach = true;
|
||||
py_sys_settrace(c11_dap_tracefunc, false);
|
||||
}
|
||||
|
||||
@ -479,13 +534,20 @@ void py_debugger_waitforattach(const char* hostname, unsigned short port) {
|
||||
|
||||
void py_debugger_exit(int exitCode) { c11_dap_send_exited_event(exitCode); }
|
||||
|
||||
bool py_debugger_isattached() {
|
||||
return false; // TODO: implement this function
|
||||
}
|
||||
bool py_debugger_isattached() { return server.isattach; }
|
||||
|
||||
void py_debugger_exceptionbreakpoint(py_Ref exc) {
|
||||
assert(py_isinstance(exc, tp_BaseException));
|
||||
// BaseException* ud = py_touserdata(exc);
|
||||
// c11_vector* stacktrace = &ud->stacktrace;
|
||||
// TODO: implement this function
|
||||
|
||||
py_sys_settrace(NULL, true);
|
||||
server.isattach = false;
|
||||
|
||||
c11_debugger_exception_on_trace(exc);
|
||||
for(;;) {
|
||||
C11_STOP_REASON reason = c11_debugger_should_pause();
|
||||
c11_dap_send_stop_event(reason);
|
||||
while(c11_debugger_should_keep_pause()) {
|
||||
c11_dap_handle_message();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user