feat(debug): add Python debug visualizer and enhance gdb integration

This commit is contained in:
mynameisdeleted
2026-07-09 16:08:01 -04:00
parent 67e9a0d488
commit 4f693f8cff
9 changed files with 17 additions and 11 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target
/cpp/list_example_cpp
__pycache__

7
.vscode/launch.json vendored
View File

@@ -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
}
]

View File

@@ -7,7 +7,7 @@
"expressionTemplate": "$debug_graph(${expr})"
},
"debugpy": {
"expressionTemplate": "__import__('debug_graph').debug_graph(${expr})"
"expressionTemplate": "__import__('py_debug_graph').debug_graph(${expr})"
}
}
}

View File

@@ -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.
"""

View File

@@ -28,13 +28,13 @@ gdb has no way to know it's secretly interior to a `Node<int>`). 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,
)

View File

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