Преглед изворни кода

Gsoc 2025 debugger (#385)

* add debugger module

* simplify the workdir process

* Update main.c

* Update main.c

* update debugger

* change port to 6110

* Update main.c

* simplify the workdir process and minor optimizations

* implement exit event

* Fix memory management in setBreakpoints

---------

Co-authored-by: blueloveTH <blueloveth@foxmail.com>
lightovernight пре 6 месеци
родитељ
комит
d0980c6934
5 измењених фајлова са 93 додато и 38 уклоњено
  1. 10 3
      include/pocketpy/debugger/core.h
  2. 0 1
      include/pocketpy/debugger/dap.h
  3. 45 24
      src/debugger/core.c
  4. 38 9
      src/debugger/dap.c
  5. 0 1
      src2/main.c

+ 10 - 3
include/pocketpy/debugger/core.h

@@ -5,11 +5,18 @@
 #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 };
+typedef enum { C11_STEP_IN, C11_STEP_OVER, C11_STEP_OUT, C11_STEP_CONTINUE } C11_STEP_MODE;
+
+typedef enum {
+    C11_DEBUGGER_SUCCESS = 0,
+    C11_DEBUGGER_EXIT = 1,
+    C11_DEBUGGER_UNKNOW_ERROR = 3,
+    C11_DEBUGGER_FILEPATH_ERROR = 7
+} C11_DEBUGGER_STATUS;
 
 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_set_step_mode(C11_STEP_MODE mode);
+C11_DEBUGGER_STATUS 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);

+ 0 - 1
include/pocketpy/debugger/dap.h

@@ -1 +0,0 @@
-#pragma once

+ 45 - 24
src/debugger/core.c

@@ -34,14 +34,14 @@ static struct c11_debugger {
     int current_line;
     int pause_allowed_depth;
     int step_line;
-    enum C11_STEP_MODE step_mode;
+    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()
+#define python_vars py_r7()
 
 } debugger;
 
@@ -65,6 +65,13 @@ inline static py_Ref get_variable(int var_ref) {
     return py_list_getitem(python_vars, var_ref);
 }
 
+const inline static char* format_filepath(const char* path) {
+    if(strstr(path, "..")) { return NULL; }
+    if(strstr(path + 1, "./") || strstr(path + 1, ".\\")) { return NULL; }
+    if(path[0] == '.' && (path[1] == '/' || path[1] == '\\')) { return path + 2; }
+    return path;
+}
+
 void c11_debugger_init() {
     debugger.curr_stack_depth = 0;
     debugger.current_line = -1;
@@ -75,16 +82,23 @@ void c11_debugger_init() {
     init_structures();
 }
 
-void c11_debugger_on_trace(py_Frame* frame, enum py_TraceEvent event) {
+C11_DEBUGGER_STATUS 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);
+    const char* source_name = py_Frame_sourceloc(debugger.current_frame, &debugger.current_line);
+    debugger.current_filename = format_filepath(source_name);
+    if(debugger.current_filename == NULL) { return C11_DEBUGGER_FILEPATH_ERROR; }
     clear_structures();
-    if(event == TRACE_EVENT_LINE) return;
-    event == TRACE_EVENT_PUSH ? debugger.curr_stack_depth++ : debugger.curr_stack_depth--;
+    switch(event) {
+        case TRACE_EVENT_PUSH: debugger.curr_stack_depth++; break;
+        case TRACE_EVENT_POP: debugger.curr_stack_depth--; break;
+        default: break;
+    }
+    if(debugger.curr_stack_depth == 0) return C11_DEBUGGER_EXIT;
+    return C11_DEBUGGER_SUCCESS;
 }
 
-void c11_debugger_set_step_mode(enum C11_STEP_MODE mode) {
+void c11_debugger_set_step_mode(C11_STEP_MODE mode) {
     switch(mode) {
         case C11_STEP_IN: debugger.pause_allowed_depth = INT32_MAX; break;
         case C11_STEP_OVER:
@@ -98,7 +112,6 @@ void c11_debugger_set_step_mode(enum C11_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);
@@ -156,7 +169,6 @@ int c11_debugger_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;
@@ -164,7 +176,7 @@ inline static c11_sv sv_from_cstr(const char* str) {
 
 const inline static char* get_basename(const char* path) {
     const char* last_slash = strrchr(path, '/');
-#ifdef _WIN32
+#if defined(_WIN32) || defined(_WIN64)
     const char* last_backslash = strrchr(path, '\\');
     if(!last_slash || (last_backslash && last_backslash > last_slash)) {
         last_slash = last_backslash;
@@ -254,31 +266,39 @@ bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) {
     py_Ref base_var_ref = py_pushtmp();
     py_newint(base_var_ref, base_index);
 
-    // 3. construct DAP JSON
+    // 3. construct DAP JSON and extend python_vars
+    py_Ref dap_obj = py_pushtmp();
+    py_newdict(dap_obj);  // 先创建字典
     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)) {
+        "_2['variables'] = []\n"
+        "var_ref = _1\n"
+        "for k, v in _0:\n"
+        "    has_children = isinstance(v, (dict, list, tuple)) or v.__dict__ is not None\n"
+        "    _2['variables'].append({\n"
+        "        'name': k if type(k) == str else str(k),\n"
+        "        'value': repr(v) if type(v) == str else str(v),\n"
+        "        'variablesReference': var_ref if has_children else 0,\n"
+        "        'type': type(v).__name__\n"
+        "    })\n"
+        "    if has_children: var_ref += 1\n";
+    if(!py_smartexec(dap_code, NULL, kv_list, base_var_ref, dap_obj)) {
         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)) {
+    if(!py_smartexec(
+           "_0.extend([v for k, v in _1 if isinstance(v, (dict, list, tuple)) or v.__dict__ is not None])",
+           NULL,
+           python_vars,
+           kv_list)) {
         py_printexc();
         return false;
     }
+
     // 5. dump & write
     if(!py_json_dumps(dap_obj, 0)) {
+        printf("dap_obj: %s\n", py_tpname(py_typeof(dap_obj)));
         py_printexc();
         return false;
     }
@@ -291,4 +311,5 @@ bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) {
     py_pop();  // kv_list
     return true;
 }
+
 #undef python_vars

+ 38 - 9
src/debugger/dap.c

@@ -1,4 +1,5 @@
 #include <stdbool.h>
+#include <stdio.h>
 #include "pocketpy/common/socket.h"
 #include "pocketpy/debugger/core.h"
 #include "pocketpy/objects/base.h"
@@ -54,10 +55,7 @@ void c11_dap_handle_initialize(py_Ref arguments, c11_sbuf* buffer) {
     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.isatttach = true; }
 
 void c11_dap_handle_next(py_Ref arguments, c11_sbuf* buffer) {
     c11_debugger_set_step_mode(C11_STEP_OVER);
@@ -96,7 +94,7 @@ void c11_dap_handle_setBreakpoints(py_Ref arguments, c11_sbuf* buffer) {
         py_printexc();
         return;
     }
-    const char* sourcename = py_tostr(py_retval());
+    const char* sourcename = c11_strdup(py_tostr(py_retval()));
     if(!py_smarteval("[bp['line'] for bp in _0['breakpoints']]", NULL, arguments)) {
         py_printexc();
         return;
@@ -112,6 +110,7 @@ void c11_dap_handle_setBreakpoints(py_Ref arguments, c11_sbuf* buffer) {
     }
     c11_sbuf__write_cstr(buffer, "]}");
     c11_sbuf__write_char(buffer, ',');
+    PK_FREE((void*)sourcename);
 }
 
 void c11_dap_handle_stackTrace(py_Ref arguments, c11_sbuf* buffer) {
@@ -230,7 +229,19 @@ void c11_dap_send_event(const char* event_name, const char* body_json) {
 
 void c11_dap_send_stop_event() {
     c11_dap_send_event("stopped",
-                       "{\"reason\":\"breakpoint\",\"threadId\":1,\"allThreadsStopped\":true}");
+                       "{\"threadId\":1,\"allThreadsStopped\":true}");
+}
+
+void c11_dap_send_exited_event(int exitCode) {
+    char body[64];
+    snprintf(body, sizeof(body), "{\"exitCode\":%d}", exitCode);
+    c11_dap_send_event("exited", body);
+}
+
+void c11_dap_send_fatal_event(const char* message) {
+    char body[128];
+    snprintf(body, sizeof(body), "{\"message\":\"%s\"}", message);
+    c11_dap_send_event("pkpy/fatalError", body);
 }
 
 void c11_dap_send_initialized_event() { c11_dap_send_event("initialized", "{}"); }
@@ -320,9 +331,9 @@ void c11_dap_init_server(const char* hostname, unsigned short port) {
 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);
+    // 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); }
+    // 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);
@@ -346,7 +357,25 @@ void c11_dap_configure_debugger() {
 
 void c11_dap_tracefunc(py_Frame* frame, enum py_TraceEvent event) {
     py_sys_settrace(NULL, false);
-    c11_debugger_on_trace(frame, event);
+    C11_DEBUGGER_STATUS result = c11_debugger_on_trace(frame, event);
+    if(result == C11_DEBUGGER_EXIT) {
+        c11_dap_send_exited_event(0);
+        printf("[DEBUGGER INFO] : program exit\n");
+        exit(0);
+    }
+    if(result != C11_DEBUGGER_SUCCESS) {
+        const char* message = NULL;
+        switch(result) {
+            case C11_DEBUGGER_FILEPATH_ERROR:
+                message = "Invalid py_file path: '..' forbidden, './' only allowed at start.";
+                break;
+            case C11_DEBUGGER_UNKNOW_ERROR:
+            default: message = "Unknown debugger failure."; break;
+        }
+        if(message) { c11_dap_send_fatal_event(message); }
+        c11_dap_send_exited_event(1);
+        exit(1);
+    }
     c11_dap_handle_message();
     if(!c11_debugger_should_pause()) {
         py_sys_settrace(c11_dap_tracefunc, false);

+ 0 - 1
src2/main.c

@@ -4,7 +4,6 @@
 #include <string.h>
 
 #include "pocketpy.h"
-
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>