implement exception breakpoint

This commit is contained in:
lightovernight 2025-08-26 18:09:16 +08:00
parent 97f18a99f2
commit 533ba9824b
3 changed files with 174 additions and 42 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}
}
}