From 4f693f8cffe01e60cc8afdea3730c104bb4c8746 Mon Sep 17 00:00:00 2001 From: mynameisdeleted Date: Thu, 9 Jul 2026 16:08:01 -0400 Subject: [PATCH] feat(debug): add Python debug visualizer and enhance gdb integration --- .gitignore | 1 + .vscode/launch.json | 7 +++++-- .vscode/settings.json | 2 +- .../gdb_debug_graph.py | 0 .../py_debug_graph.py | 4 ++-- python/__pycache__/debug_graph.cpython-314.pyc | Bin 8990 -> 0 bytes python/__pycache__/main.cpython-314.pyc | Bin 3368 -> 0 bytes scripts/dump_debug_graph.py | 12 +++++++----- vis-plugins/node-table-visualizer.js | 2 +- 9 files changed, 17 insertions(+), 11 deletions(-) rename gdb/debug_graph.py => debuggers/gdb_debug_graph.py (100%) rename python/debug_graph.py => debuggers/py_debug_graph.py (98%) delete mode 100644 python/__pycache__/debug_graph.cpython-314.pyc delete mode 100644 python/__pycache__/main.cpython-314.pyc diff --git a/.gitignore b/.gitignore index 326b1f7..e9370b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /cpp/list_example_cpp +__pycache__ diff --git a/.vscode/launch.json b/.vscode/launch.json index 5334fd9..14cf5db 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,7 +32,7 @@ }, { "description": "Load the generic $debug_graph(...) Debug Visualizer helper", - "text": "source ${workspaceFolder}/gdb/debug_graph.py", + "text": "source ${workspaceFolder}/debuggers/gdb_debug_graph.py", "ignoreFailures": false } ] @@ -43,6 +43,9 @@ "request": "launch", "program": "${workspaceFolder}/python/main.py", "cwd": "${workspaceFolder}/python", + "env": { + "PYTHONPATH": "${workspaceFolder}/debuggers" + }, "console": "integratedTerminal", "justMyCode": true }, @@ -77,7 +80,7 @@ }, { "description": "Load the generic $debug_graph(...) Debug Visualizer helper", - "text": "source ${workspaceFolder}/gdb/debug_graph.py", + "text": "source ${workspaceFolder}/debuggers/gdb_debug_graph.py", "ignoreFailures": false } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 61ff48d..7ae770b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,7 @@ "expressionTemplate": "$debug_graph(${expr})" }, "debugpy": { - "expressionTemplate": "__import__('debug_graph').debug_graph(${expr})" + "expressionTemplate": "__import__('py_debug_graph').debug_graph(${expr})" } } } diff --git a/gdb/debug_graph.py b/debuggers/gdb_debug_graph.py similarity index 100% rename from gdb/debug_graph.py rename to debuggers/gdb_debug_graph.py diff --git a/python/debug_graph.py b/debuggers/py_debug_graph.py similarity index 98% rename from python/debug_graph.py rename to debuggers/py_debug_graph.py index d09b776..7e40a89 100644 --- a/python/debug_graph.py +++ b/debuggers/py_debug_graph.py @@ -1,5 +1,5 @@ """Generic "reference-graph" -> Debug Visualizer JSON walker, the debugpy -analog of gdb/debug_graph.py. +analog of debuggers/gdb_debug_graph.py. Works on any plain Python object reachable from an expression, purely by walking `vars(obj)` -- no changes to the debuggee's source are required. @@ -23,7 +23,7 @@ walks and labels, as additional roots: - every other local variable visible in the caller's frame -- kind "local" - every module-level global visible from the caller's frame, skipping dunders, modules, classes and functions -- kind "global" -This mirrors gdb/debug_graph.py's auto-discovery: it's what lets +This mirrors debuggers/gdb_debug_graph.py's auto-discovery: it's what lets `debug_graph(top)` -- a bare int with no object graph of its own -- still show the whole list, because `lst` (a sibling local) gets auto-walked too. """ diff --git a/python/__pycache__/debug_graph.cpython-314.pyc b/python/__pycache__/debug_graph.cpython-314.pyc deleted file mode 100644 index 69efbe5b0877280ea0c775d3344df767f4b9b1e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8990 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 diff --git a/python/__pycache__/main.cpython-314.pyc b/python/__pycache__/main.cpython-314.pyc deleted file mode 100644 index 39622141ed0de6fe5db9b1280b548b88e7b9c16d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3368 zcmbVOTW=Fb6rR1=>s==S5)&?gTnRV{3DC4b0r7%>P#}R->Q;cWGVzAEIO}D0LLo1> zg@TdV22@p8>I;1W`qZD$pFjb%VMSG{st>#+GKklnGrPWkl7bj%&zzleX6DRyzPa?) z*J%XGfoEf%uL}|K5B`)(*jI$oub||~1TloeWJF*>E9&B5aYSMg*QLYqh{BYS5DSfj zS-6!X!|lY72L*owrY5Brffl8i7@?$+RQopg7Ll0x*_7+X zB`31SrLlC zkn7}`re8UAea=cBg>Wa4bOG@Q&IXARN-cc01ep+bUj?E-TMIcMObdo!hyy|= zxi39OUoS*0HcM^H5D)*X`xj1SEjiDbvA7G1#Xs%A*Uu#$cu5KfUJb6U~ zW&09}Vi&JCK0H{k^AZFbDIs!sVvq!}0~7FwK#;=deZf=AX)BcmMRu+16c4J~4+>b4 zu`>(;YJ4pqzmW$M;X^NM>nq6LY1I(Qc*$Vqc(rlAu~@FXONE>ari9Z#2|CZDc4 zUhBu5;a~~c0LF?Z!bsb}Aujp^?gah5NVh=67vf^4?f_8DnRTblDVnpH7psA^+RWF1 z$mYq@*!p7EUF|3BQLJ})baCi+WTTHP;yI^R&W7>iTX985D~wZ=rX>QRxQAEObb}Kw zK|qmG0e<3|{7~QYq_KISp>clh#@zhn8nKtj7INFb)`p3q~gA*?p1f(ERN`SIb zlD7K3B;AZB+X~?0GTta~Io9lmD&$?@S9`Q*j%A%&ZYNZl`J!;Y1Kb_mcYE&k-03N> zGSUy!kACR_!IwwTcQhWM^-$9W6su6IMuAr)?L~pPqj-~3#EPTpcgGW$!a{w4y?2R+11Vgg%TfD0~Ah*Qe<{`fPqn zAEi8NwXUo|N=O!*k1gbM@SMaaQ9_Es?Vb>#WLM>p*jnR}5Nl>6$MMp7qm;+rBRH}J z40vsPB_YGeHDeqBIvaRng(&WUIeps7W^-*{sHqyM64|vG2zo=$(ZRA8I?$T`k6n;Rp3?zpL#_647^ZD&vzB7h#&C@j85!PR+u@ z2_8cfS?B4MSGDC*8Cyc^l9RE|Qbd9x_bC#>e>d=_Bu5w_@s9;0b9K9W>R$iZyN~V% z1D=WQ84!z-AP9ewj(`). VS Code's whatever you type in the Watch box as $debug_graph(${expr}), so typing plain `top` evaluates $debug_graph(top) -- not $debug_graph(list, ...): we replay that exact call and verify it still delves into the full list. This -relies on gdb/debug_graph.py's auto-discovery of sibling locals (here, +relies on debuggers/gdb_debug_graph.py's auto-discovery of sibling locals (here, `list`, a struct type it *can* walk) merging into the same node graph as `top`'s address, rather than requiring `top` to be passed as an explicit watched root. The Python build has no pointers -- `top` there is just a plain int, not a -value interior to any struct -- but python/debug_graph.py's own +value interior to any struct -- but debuggers/py_debug_graph.py's own auto-discovery of sibling locals (here `lst`) means evaluating debug_graph(top) still delves into the whole list for the same underlying reason: verify_top_delves_into_list_py exercises that. @@ -57,7 +57,7 @@ def run_gdb(gdb_bin, binary, break_file, break_line, expr="$debug_graph(list)"): commands = [ "-gdb-set print elements 0", "-gdb-set print characters 0", - "source %s" % (REPO / "gdb" / "debug_graph.py"), + "source %s" % (REPO / "debuggers" / "gdb_debug_graph.py"), "-break-insert %s:%d" % (break_file, break_line), "-exec-run", '-data-evaluate-expression "%s"' % expr.replace('"', '\\"'), @@ -199,9 +199,11 @@ def run_debugpy(python_bin, script_path, break_line, expr): probe.bind(("127.0.0.1", 0)) port = probe.getsockname()[1] + env = dict(os.environ, PYTHONPATH=str(REPO / "debuggers")) proc = subprocess.Popen( [python_bin, "-m", "debugpy", "--listen", "127.0.0.1:%d" % port, "--wait-for-client", str(script_path)], cwd=str(Path(script_path).parent), + env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) try: @@ -380,7 +382,7 @@ def dump(label, gdb_bin, binary, break_file, break_line, out_dir, expr="$debug_g def dump_python(label, python_bin, script_path, break_line, out_dir, - expr="__import__('debug_graph').debug_graph(lst)", verify=None): + expr="__import__('py_debug_graph').debug_graph(lst)", verify=None): ok = True try: raw_result = run_debugpy(python_bin, script_path, break_line, expr) @@ -423,7 +425,7 @@ def main(): ok_python = dump_python("python", python_bin, python_script, 54, out_dir) ok_python_top = dump_python( "python_top", python_bin, python_script, 54, out_dir, - expr="__import__('debug_graph').debug_graph(top)", + expr="__import__('py_debug_graph').debug_graph(top)", verify=verify_top_delves_into_list_py, ) diff --git a/vis-plugins/node-table-visualizer.js b/vis-plugins/node-table-visualizer.js index 3533165..a8295b5 100644 --- a/vis-plugins/node-table-visualizer.js +++ b/vis-plugins/node-table-visualizer.js @@ -11,7 +11,7 @@ // Registered via the "debugVisualizer.customVisualizerScriptPaths" setting; // the extension hot-reloads this file on save, no debug session restart needed. // -// Data shape produced by gdb/debug_graph.py's $debug_graph(...): +// Data shape produced by debuggers/gdb_debug_graph.py's $debug_graph(...): // nodes: [{ id: "0x...", fields: [{ name, value, isPointer? }, ...] }] // edges: [{ from: "0x...", to: "0x...", label? }] // roots: [{ name: "top", value: "0x...", kind? }] -- external pointer