From 533ba9824bd9f1b1cd1dd7abd822c0e6c7f330cb Mon Sep 17 00:00:00 2001 From: lightovernight Date: Tue, 26 Aug 2025 18:09:16 +0800 Subject: [PATCH] implement exception breakpoint --- include/pocketpy/debugger/core.h | 6 +- src/debugger/core.c | 104 ++++++++++++++++++++++++------ src/debugger/dap.c | 106 ++++++++++++++++++++++++------- 3 files changed, 174 insertions(+), 42 deletions(-) diff --git a/include/pocketpy/debugger/core.h b/include/pocketpy/debugger/core.h index d5b04051..e36d7df7 100644 --- a/include/pocketpy/debugger/core.h +++ b/include/pocketpy/debugger/core.h @@ -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); diff --git a/src/debugger/core.c b/src/debugger/core.c index 2b512978..591c9cec 100644 --- a/src/debugger/core.c +++ b/src/debugger/core.c @@ -1,4 +1,5 @@ #include "pocketpy/interpreter/frame.h" +#include "pocketpy/objects/exception.h" #include "pocketpy/pocketpy.h" #include @@ -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 = ""; + } 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); } diff --git a/src/debugger/dap.c b/src/debugger/dap.c index a09b6c5a..969b6eb7 100644 --- a/src/debugger/dap.c +++ b/src/debugger/dap.c @@ -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(); + } + } } \ No newline at end of file