From ec8f8f473eb53f95667bcb12bb1e31d84c21a2ee Mon Sep 17 00:00:00 2001 From: lightovernight <119399319+lightovernight@users.noreply.github.com> Date: Sat, 9 Aug 2025 13:04:04 +0800 Subject: [PATCH] Merge pull request #379 from lightovernight/gsoc-2025-debugger Gsoc 2025 debugger --- include/pocketpy/debugger/core.h | 19 ++ include/pocketpy/debugger/dap.h | 1 + include/pocketpy/pocketpy.h | 5 +- src/debugger/core.c | 294 ++++++++++++++++++++++++ src/debugger/dap.c | 374 +++++++++++++++++++++++++++++++ src2/main.c | 18 +- 6 files changed, 709 insertions(+), 2 deletions(-) create mode 100644 include/pocketpy/debugger/core.h create mode 100644 include/pocketpy/debugger/dap.h create mode 100644 src/debugger/core.c create mode 100644 src/debugger/dap.c diff --git a/include/pocketpy/debugger/core.h b/include/pocketpy/debugger/core.h new file mode 100644 index 00000000..aaa82339 --- /dev/null +++ b/include/pocketpy/debugger/core.h @@ -0,0 +1,19 @@ +#pragma once + +#include "pocketpy/common/sstream.h" +#include "pocketpy/common/str.h" +#include "pocketpy/common/vector.h" +#include "pocketpy/pocketpy.h" + +enum C11_STEP_MODE { C11_STEP_IN, C11_STEP_OVER, C11_STEP_OUT, C11_STEP_CONTINUE }; + +void c11_debugger_init(void); +void c11_debugger_set_step_mode(enum C11_STEP_MODE mode); +void c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event); +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); +int c11_debugger_should_keep_pause(void); diff --git a/include/pocketpy/debugger/dap.h b/include/pocketpy/debugger/dap.h new file mode 100644 index 00000000..6f70f09b --- /dev/null +++ b/include/pocketpy/debugger/dap.h @@ -0,0 +1 @@ +#pragma once diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index ecd00b59..764bfb50 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -703,12 +703,15 @@ PK_API bool py_pickle_dumps(py_Ref val) PY_RAISE PY_RETURN; PK_API bool py_pickle_loads(const unsigned char* data, int size) PY_RAISE PY_RETURN; /************* Profiler *************/ - PK_API void py_profiler_begin(); PK_API void py_profiler_end(); PK_API void py_profiler_reset(); PK_API char* py_profiler_report(); +/************* DAP *************/ +PK_API void py_debugger_waitforattach(const char* hostname, unsigned short port); +PK_API void py_debugger_exit(int exitCode); + /************* Unchecked Functions *************/ PK_API py_ObjectRef py_tuple_data(py_Ref self); diff --git a/src/debugger/core.c b/src/debugger/core.c new file mode 100644 index 00000000..bdd97896 --- /dev/null +++ b/src/debugger/core.c @@ -0,0 +1,294 @@ + + +#include "pocketpy/interpreter/frame.h" +#include "pocketpy/pocketpy.h" +#include + +#include "pocketpy/debugger/core.h" + +typedef struct c11_debugger_breakpoint { + const char* sourcename; + int lineno; +} c11_debugger_breakpoint; + +typedef struct c11_debugger_scope_index { + int locals_ref; + int globals_ref; +} c11_debugger_scope_index; + +#define SMALLMAP_T__HEADER +#define SMALLMAP_T__SOURCE +#define K int +#define V c11_debugger_scope_index +#define NAME c11_smallmap_d2index +#include "pocketpy/xmacros/smallmap.h" +#undef SMALLMAP_T__SOURCE +#undef SMALLMAP_T__HEADER + +static struct c11_debugger { + py_Frame* current_frame; + const char* current_filename; + enum py_TraceEvent current_event; + + int curr_stack_depth; + int current_line; + int pause_allowed_depth; + int step_line; + enum C11_STEP_MODE step_mode; + bool keep_suspend; + + c11_vector breakpoints; + c11_vector py_frames; + c11_smallmap_d2index scopes_query_cache; + + #define python_vars py_r7() + +} debugger; + +inline static void init_structures() { + c11_vector__ctor(&debugger.breakpoints, sizeof(c11_debugger_breakpoint)); + c11_vector__ctor(&debugger.py_frames, sizeof(py_Frame*)); + c11_smallmap_d2index__ctor(&debugger.scopes_query_cache); + py_newlist(python_vars); + py_newnil(py_list_emplace(python_vars)); +} + +inline static void clear_structures() { + c11_vector__clear(&debugger.py_frames); + c11_smallmap_d2index__clear(&debugger.scopes_query_cache); + py_list_clear(python_vars); + py_newnone(py_list_emplace(python_vars)); +} + +inline static py_Ref get_variable(int var_ref) { + assert(var_ref < py_list_len(python_vars) && var_ref > 0); + return py_list_getitem(python_vars, var_ref); +} + +void c11_debugger_init() { + debugger.curr_stack_depth = 0; + debugger.current_line = -1; + debugger.pause_allowed_depth = -1; + debugger.step_line = -1; + debugger.keep_suspend = false; + debugger.step_mode = C11_STEP_CONTINUE; + init_structures(); +} + +void c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event) { + debugger.current_frame = frame; + debugger.current_event = event; + debugger.current_filename = py_Frame_sourceloc(debugger.current_frame, &debugger.current_line); + clear_structures(); + if(event == TRACE_EVENT_LINE) return; + event == TRACE_EVENT_PUSH ? debugger.curr_stack_depth++ : debugger.curr_stack_depth--; +} + +void c11_debugger_set_step_mode(enum C11_STEP_MODE mode) { + switch(mode) { + case C11_STEP_IN: debugger.pause_allowed_depth = INT32_MAX; break; + case C11_STEP_OVER: + debugger.pause_allowed_depth = debugger.curr_stack_depth; + debugger.step_line = debugger.current_line; + 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; + } + 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); + return debugger.breakpoints.length; +} + +int c11_debugger_reset_breakpoints_by_source(const char* sourcesname) { + c11_vector tmp_breakpoints; + c11_vector__ctor(&tmp_breakpoints, sizeof(c11_debugger_breakpoint)); + + c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, it) { + if(strcmp(it->sourcename, sourcesname) != 0) { + c11_debugger_breakpoint* dst = + (c11_debugger_breakpoint*)c11_vector__emplace(&tmp_breakpoints); + *dst = *it; + } else { + free((void*)it->sourcename); + } + } + + c11_vector__swap(&tmp_breakpoints, &debugger.breakpoints); + c11_vector__dtor(&tmp_breakpoints); + return debugger.breakpoints.length; +} + +int c11_debugger_should_pause() { + if(debugger.current_event == TRACE_EVENT_POP) return false; + bool should_pause = false; + 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_OVER: + if(is_new_line && is_out) should_pause = true; + break; + case C11_STEP_OUT: + if(is_out) should_pause = true; + break; + case C11_STEP_CONTINUE: + default: break; + } + if(debugger.step_mode == C11_STEP_CONTINUE) { + c11__foreach(c11_debugger_breakpoint, &debugger.breakpoints, bp) { + if(strcmp(debugger.current_filename, bp->sourcename) == 0 && + debugger.current_line == bp->lineno) { + should_pause = true; + break; + } + } + } + if(should_pause) { debugger.keep_suspend = true; } + return should_pause; +} + +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; +} + +const inline static char* get_basename(const char* path) { + const char* last_slash = strrchr(path, '/'); +#ifdef _WIN32 + const char* last_backslash = strrchr(path, '\\'); + if(!last_slash || (last_backslash && last_backslash > last_slash)) { + last_slash = last_backslash; + } +#endif + return last_slash ? last_slash + 1 : path; +} + +void c11_debugger_frames(c11_sbuf* buffer) { + c11_sbuf__write_cstr(buffer, "{\"stackFrames\": ["); + int idx = 0; + py_Frame* now_frame = debugger.current_frame; + debugger.py_frames.length = 0; + while(now_frame) { + if(idx > 0) c11_sbuf__write_char(buffer, ','); + int line; + const char* filename = py_Frame_sourceloc(now_frame, &line); + const char* basename = get_basename(filename); + const char* modname = now_frame->co->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)); + c11_vector__push(py_Frame*, &debugger.py_frames, now_frame); + now_frame = now_frame->f_back; + idx++; + } + pk_sprintf(buffer, "], \"totalFrames\": %d}", idx); +} + +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); + int base_index = py_list_len(python_vars); + py_Ref new_locals = py_list_emplace(python_vars); + py_Ref new_globals = py_list_emplace(python_vars); + py_Frame_newlocals(requested_frame, new_locals); + py_Frame_newglobals(requested_frame, new_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 = + c11_smallmap_d2index__try_get(&debugger.scopes_query_cache, frameid); + + c11_sbuf__write_cstr(buffer, "{\"scopes\":"); + const char* scopes_fmt = + "[{\"name\": \"locals\", \"variablesReference\": %d, \"expensive\": false}, " + "{\"name\": \"globals\", \"variablesReference\": %d, \"expensive\": true}]"; + 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_smallmap_d2index__set(&debugger.scopes_query_cache, frameid, new_record); + pk_sprintf(buffer, scopes_fmt, new_record.locals_ref, new_record.globals_ref); + } + c11_sbuf__write_char(buffer, '}'); +} + +bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) { + py_Ref var = get_variable(var_id); + if(!var) return false; + + // 1. extend + const char* expand_code = NULL; + switch(py_typeof(var)) { + case tp_dict: + case tp_namedict: expand_code = "[(k,v) for k,v in _0.items()]"; break; + case tp_list: + case tp_tuple: expand_code = "[(f'[{i}]',v) for i,v in enumerate(_0)]"; break; + default: expand_code = "[(k,v) for k,v in _0.__dict__.items()]"; break; + } + if(!py_smarteval(expand_code, NULL, var)) { + py_printexc(); + return false; + } + py_Ref kv_list = py_pushtmp(); + py_assign(kv_list, py_retval()); + // 2. prepare base_ref + int base_index = py_list_len(python_vars); + py_Ref base_var_ref = py_pushtmp(); + py_newint(base_var_ref, base_index); + + // 3. construct DAP JSON + const char* dap_code = + "{'variables': [" + " {" + " 'name': kv[0]," + " 'value': repr(kv[1])," + " 'variablesReference': (_1 + i) if (isinstance(kv[1], (dict, list, tuple)) or kv[1].__dict__ is not None) else 0," + " 'type': type(kv[1]).__name__" + " }" + " for i, kv in enumerate(_0)" + "]}"; + if(!py_smarteval(dap_code, NULL, kv_list, base_var_ref)) { + py_printexc(); + return false; + } + py_Ref dap_obj = py_pushtmp(); + py_assign(dap_obj, py_retval()); + + // 4. extend python_vars + if(!py_smartexec("_0.extend([kv[1] for kv in _1])", NULL, python_vars, kv_list)) { + py_printexc(); + return false; + } + // 5. dump & write + if(!py_json_dumps(dap_obj, 0)) { + py_printexc(); + return false; + } + + c11_sbuf__write_cstr(buffer, py_tostr(py_retval())); + + // 6. clear + py_pop(); // dap_obj + py_pop(); // base_var_ref + py_pop(); // kv_list + return true; +} +#undef python_vars diff --git a/src/debugger/dap.c b/src/debugger/dap.c new file mode 100644 index 00000000..a2fa919b --- /dev/null +++ b/src/debugger/dap.c @@ -0,0 +1,374 @@ +#include +#include "pocketpy/common/socket.h" +#include "pocketpy/debugger/core.h" +#include "pocketpy/objects/base.h" + +#define DAP_COMMAND_LIST(X) \ + X(initialize) \ + X(setBreakpoints) \ + X(attach) \ + X(next) \ + X(stepIn) \ + X(stepOut) \ + X(continue) \ + X(stackTrace) \ + X(scopes) \ + X(variables) \ + X(threads) \ + X(configurationDone) + +#define DECLARE_HANDLE_FN(name) void c11_dap_handle_##name(py_Ref arguments, c11_sbuf*); +DAP_COMMAND_LIST(DECLARE_HANDLE_FN) +#undef DECLARE_ARG_FN + +typedef void (*c11_dap_arg_parser_fn)(py_Ref, c11_sbuf*); + +typedef struct { + const char* command; + c11_dap_arg_parser_fn parser; +} dap_command_entry; + +#define DAP_ENTRY(name) {#name, c11_dap_handle_##name}, +static dap_command_entry dap_command_table[] = { + DAP_COMMAND_LIST(DAP_ENTRY){NULL, NULL} +}; + +#undef DAP_ENTRY + +// #undef DAP_COMMAND_LIST + +// static int dap_next_seq = 1; +static struct c11_dap_server { + int dap_next_seq; + char buffer_data[1024]; + char* buffer_begin; + int buffer_length; + c11_socket_handler server; + c11_socket_handler toclient; + bool isconfiguredone; + bool isatttach; +} server; + +void c11_dap_handle_initialize(py_Ref arguments, c11_sbuf* buffer) { + c11_sbuf__write_cstr(buffer, "\"body\":{\"supportsConfigurationDoneRequest\":true}"); + c11_sbuf__write_char(buffer, ','); +} + +void c11_dap_handle_attach(py_Ref arguments, c11_sbuf* buffer) { + server.isatttach = true; + +} + +void c11_dap_handle_next(py_Ref arguments, c11_sbuf* buffer) { + c11_debugger_set_step_mode(C11_STEP_OVER); +} + +void c11_dap_handle_stepIn(py_Ref arguments, c11_sbuf* buffer) { + c11_debugger_set_step_mode(C11_STEP_IN); +} + +void c11_dap_handle_stepOut(py_Ref arguments, c11_sbuf* buffer) { + c11_debugger_set_step_mode(C11_STEP_OUT); +} + +void c11_dap_handle_continue(py_Ref arguments, c11_sbuf* buffer) { + c11_debugger_set_step_mode(C11_STEP_CONTINUE); +} + +void c11_dap_handle_threads(py_Ref arguments, c11_sbuf* buffer) { + c11_sbuf__write_cstr(buffer, + "\"body\":{\"threads\":[" + "{\"id\":1,\"name\":\"MainThread\"}" + "]}"); + c11_sbuf__write_char(buffer, ','); +} + +void c11_dap_handle_configurationDone(py_Ref arguments, c11_sbuf* buffer) { + server.isconfiguredone = true; +} + +inline static void c11_dap_build_Breakpoint(int id, int line, c11_sbuf* buffer) { + pk_sprintf(buffer, "{\"id\":%d,\"verified\":true,\"line\":%d}", id, line); +} + +void c11_dap_handle_setBreakpoints(py_Ref arguments, c11_sbuf* buffer) { + if(!py_smarteval("_0['source']['path']", NULL, arguments)) { + py_printexc(); + return; + } + const char* sourcename = py_tostr(py_retval()); + if(!py_smarteval("[bp['line'] for bp in _0['breakpoints']]", NULL, arguments)) { + py_printexc(); + return; + } + int bp_numbers = c11_debugger_reset_breakpoints_by_source(sourcename); + c11_sbuf__write_cstr(buffer, "\"body\":"); + c11_sbuf__write_cstr(buffer, "{\"breakpoints\":["); + for(int i = 0; i < py_list_len(py_retval()); i++) { + if(i != 0) c11_sbuf__write_char(buffer, ','); + int line = py_toint(py_list_getitem(py_retval(), i)); + c11_debugger_setbreakpoint(sourcename, line); + c11_dap_build_Breakpoint(i + bp_numbers, line, buffer); + } + c11_sbuf__write_cstr(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); + c11_sbuf__write_char(buffer, ','); +} + +void c11_dap_handle_scopes(py_Ref arguments, c11_sbuf* buffer) { + int res = py_dict_getitem_by_str(arguments, "frameId"); + if(res <= 0) { + if(res == 0) { + printf("[DEBUGGER ERROR] no frameID found\n"); + } else { + py_printexc(); + } + return; + } + int frameid = py_toint(py_retval()); + c11_sbuf__write_cstr(buffer, "\"body\":"); + c11_debugger_scopes(frameid, buffer); + c11_sbuf__write_char(buffer, ','); +} + +void c11_dap_handle_variables(py_Ref arguments, c11_sbuf* buffer) { + int res = py_dict_getitem_by_str(arguments, "variablesReference"); + if(res <= 0) { + if(res == 0) { + printf("[DEBUGGER ERROR] no frameID found\n"); + } else { + py_printexc(); + } + return; + } + int variablesReference = py_toint(py_retval()); + c11_sbuf__write_cstr(buffer, "\"body\":"); + c11_debugger_unfold_var(variablesReference, buffer); + c11_sbuf__write_char(buffer, ','); +} + +const char* c11_dap_handle_request(const char* message) { + if(!py_json_loads(message)) { + py_printexc(); + return NULL; + } + py_Ref py_request = py_pushtmp(); + py_Ref py_arguments = py_pushtmp(); + py_Ref py_command = py_pushtmp(); + py_assign(py_request, py_retval()); + + int res = py_dict_getitem_by_str(py_request, "command"); + if(res == -1) { + py_printexc(); + return NULL; + } else if(res == 0) { + return "cannot find attribute command"; + } + py_assign(py_command, py_retval()); + const char* command = py_tostr(py_command); + + res = py_dict_getitem_by_str(py_request, "arguments"); + if(res == -1) { + py_printexc(); + return NULL; + } + py_assign(py_arguments, py_retval()); + + res = py_dict_getitem_by_str(py_request, "seq"); + if(res == -1) { + py_printexc(); + return NULL; + } + int request_seq = (res == 1) ? py_toint(py_retval()) : 0; + + c11_sbuf response_buffer; + c11_sbuf__ctor(&response_buffer); + pk_sprintf(&response_buffer, + "{\"seq\":%d,\"type\":\"response\",\"request_seq\":%d,\"command\":\"%s\",", + server.dap_next_seq++, + request_seq, + command); + for(dap_command_entry* entry = dap_command_table; entry->command != NULL; entry++) { + if(strcmp(entry->command, command) == 0) { + entry->parser(py_arguments, &response_buffer); + break; + } + } + c11_sbuf__write_cstr(&response_buffer, "\"success\":true}"); + + c11_string* c11_string_response = c11_sbuf__submit(&response_buffer); + const char* response = c11_strdup(c11_string_response->data); + c11_string__delete(c11_string_response); + + py_pop(); // py_arguments + py_pop(); // py_request + py_pop(); // py_command + + return response; +} + +void c11_dap_send_event(const char* event_name, const char* body_json) { + char json[256]; + int json_len = snprintf(json, + sizeof(json), + "{\"seq\":%d,\"type\":\"event\",\"event\":\"%s\",\"body\":%s}", + server.dap_next_seq++, + event_name, + body_json); + + char header[64]; + int header_len = snprintf(header, sizeof(header), "Content-Length: %d\r\n\r\n", json_len); + + c11_socket_send(server.toclient, header, header_len); + c11_socket_send(server.toclient, json, json_len); +} + +void c11_dap_send_stop_event() { + c11_dap_send_event("stopped", + "{\"reason\":\"breakpoint\",\"threadId\":1,\"allThreadsStopped\":true}"); +} + +void c11_dap_send_initialized_event() { c11_dap_send_event("initialized", "{}"); } + +int c11_dap_read_content_length(const char* buffer, int* header_length) { + const char* length_begin = strstr(buffer, "Content-Length: "); + if(!length_begin) { + printf("[DEBUGGER ERROR] : no Content-Length filed found\n"); + *header_length = 0; + return -1; + } + length_begin += strlen("Content-Length: "); + const char* length_end = strstr(length_begin, "\r\n\r\n"); + if(!length_end) { + printf("[DEBUGGER ERROR] : the seperator should br \\r\\n\\r\\n\n"); + *header_length = 0; + return -1; + } + char* endptr = NULL; + long value = strtol(length_begin, &endptr, 10); + if(endptr == length_begin) { + printf("[DEBUGGER EORRO] : the number is empty\n"); + *header_length = 0; + return -1; + } + *header_length = (int)(endptr - buffer) + 4; + return (int)value; +} + +const char* c11_dap_read_message() { + int message_length = + c11_socket_recv(server.toclient, server.buffer_begin, 1024 - server.buffer_length); + if(message_length == 0) { + printf("[DEBUGGER INFO] : client quit\n"); + exit(0); + } + if(message_length < 0) { return NULL; } + server.buffer_length += message_length; + if(server.buffer_length == 0) return NULL; + int header_length; + int content_length = c11_dap_read_content_length(server.buffer_begin, &header_length); + if(content_length <= 0 || header_length <= 0) { + printf("[DEBUGGER ERROR]: invalid DAP header\n"); + server.buffer_length = 0; + server.buffer_begin = server.buffer_data; + return NULL; + } + server.buffer_begin += header_length; + server.buffer_length -= header_length; + c11_sbuf result; + c11_sbuf__ctor(&result); + while(content_length > server.buffer_length) { + c11_sbuf__write_cstrn(&result, server.buffer_begin, server.buffer_length); + content_length -= server.buffer_length; + message_length = c11_socket_recv(server.toclient, server.buffer_data, 1024); + if(message_length == 0) { + printf("[DEBUGGER INFO] : client quit\n"); + exit(0); + } + if(message_length < 0) continue; + server.buffer_begin = server.buffer_data; + server.buffer_length = message_length; + } + c11_sbuf__write_cstrn(&result, server.buffer_begin, content_length); + server.buffer_begin += content_length; + server.buffer_length -= content_length; + memmove(server.buffer_data, server.buffer_begin, server.buffer_length); + server.buffer_begin = server.buffer_data; + c11_string* tmp_result = c11_sbuf__submit(&result); + const char* dap_message = c11_strdup(tmp_result->data); + c11_string__delete(tmp_result); + return dap_message; +} + +void c11_dap_init_server(const char* hostname, unsigned short port) { + server.dap_next_seq = 1; + server.isconfiguredone = false; + server.buffer_begin = server.buffer_data; + server.server = c11_socket_create(C11_AF_INET, C11_SOCK_STREAM, 0); + c11_socket_bind(server.server, hostname, port); + c11_socket_listen(server.server, 0); + printf("[DEBUGGER INFO] : listen on %s:%hu\n", hostname, port); + server.toclient = c11_socket_accept(server.server, NULL, NULL); + printf("[DEBUGGER INFO] : connected client\n"); +} + +inline static void c11_dap_handle_message() { + const char* message = c11_dap_read_message(); + if(message == NULL) return; + printf("[DEBUGGER INFO] read request %s\n", message); + const char* response_content = c11_dap_handle_request(message); + if(response_content != NULL) { printf("[DEBUGGER INFO] send response %s\n", response_content); } + c11_sbuf buffer; + c11_sbuf__ctor(&buffer); + pk_sprintf(&buffer, "Content-Length: %d\r\n\r\n%s", strlen(response_content), response_content); + c11_string* response = c11_sbuf__submit(&buffer); + c11_socket_send(server.toclient, response->data, response->size); + free((void*)message); + free((void*)response_content); + c11_string__delete(response); +} + +void c11_dap_configure_debugger() { + while(server.isconfiguredone == false) { + c11_dap_handle_message(); + if(server.isatttach) { + c11_dap_send_initialized_event(); + server.isatttach = false; + } + } + printf("[DEBUGGER INFO] : configure done\n"); +} + +void c11_dap_tracefunc(py_Frame* frame, enum py_TraceEvent event) { + py_sys_settrace(NULL, false); + c11_debugger_on_trace(frame, event); + c11_dap_handle_message(); + if(!c11_debugger_should_pause()) { + py_sys_settrace(c11_dap_tracefunc, false); + return; + } + c11_dap_send_stop_event(); + while(c11_debugger_should_keep_pause()) { + c11_dap_handle_message(); + } + py_sys_settrace(c11_dap_tracefunc, false); +} + +void py_debugger_waitforattach(const char* hostname, unsigned short port) { + c11_dap_init_server(hostname, port); + c11_debugger_init(); + c11_dap_configure_debugger(); + c11_socket_set_block(server.toclient, 0); + py_sys_settrace(c11_dap_tracefunc, true); +} + +void py_debugger_exit(int exitCode) { + char body[64]; + snprintf(body, sizeof(body), "{\"exitCode\":%d}", exitCode); + c11_dap_send_event("exited", body); +} \ No newline at end of file diff --git a/src2/main.c b/src2/main.c index 3ee066ad..5ebe61e7 100644 --- a/src2/main.c +++ b/src2/main.c @@ -34,6 +34,7 @@ int main(int argc, char** argv) { #endif bool profile = false; + bool debug = false; const char* filename = NULL; for(int i = 1; i < argc; i++) { @@ -41,11 +42,20 @@ int main(int argc, char** argv) { profile = true; continue; } + if(strcmp(argv[i], "--debug") == 0) { + debug = true; + continue; + } if(filename == NULL) { filename = argv[i]; continue; } - printf("Usage: pocketpy [--profile] filename\n"); + printf("Usage: pocketpy [--profile] [--debug] filename\n"); + } + + if(debug && profile) { + printf("Error: --debug and --profile cannot be used together.\n"); + return 1; } py_initialize(); @@ -53,6 +63,8 @@ int main(int argc, char** argv) { if(filename == NULL) { if(profile) printf("Warning: --profile is ignored in REPL mode.\n"); + if(debug) printf("Warning: --debug is ignored in REPL mode.\n"); + printf("pocketpy " PK_VERSION " (" __DATE__ ", " __TIME__ ") "); printf("[%d bit] on %s", (int)(sizeof(void*) * 8), PY_SYS_PLATFORM_STRING); #ifndef NDEBUG @@ -79,6 +91,8 @@ int main(int argc, char** argv) { } } else { if(profile) py_profiler_begin(); + if(debug) py_debugger_waitforattach("127.0.0.1", 6110); + char* source = read_file(filename); if(source) { if(!py_exec(source, filename, EXEC_MODE, NULL)) @@ -101,5 +115,7 @@ int main(int argc, char** argv) { int code = py_checkexc(false) ? 1 : 0; py_finalize(); + + if(debug) py_debugger_exit(code); return code; }