feat(debug): add GDB-based debug visualizer for linked lists
Replace the custom toDebugGraph() method that generated JSON directly in C++ with a generic GDB Python script (debug_graph.py). The script traverses pointers and structures to build a graph representation for the VS Code Debug Visualizer, enabling external pointer display and more extensible visualization.
Update launch configurations to load the script and to disable array/string truncation (needed for the visualizer JSON). Add a debugAdapterConfigurations entry in settings.json to use the expression template $debug_graph(${expr}) for the cppdbg adapter.
This commit is contained in:
@@ -10,6 +10,11 @@
|
||||
// Data shape produced by cpp/main.cpp's LinkedList<T>::toDebugGraph():
|
||||
// nodes: [{ id: "0x...", fields: [{ name, value, isPointer? }, ...] }]
|
||||
// edges: [{ from: "0x...", to: "0x...", label? }]
|
||||
// roots: [{ name: "top", value: "0x..." }] -- external pointer variables
|
||||
// (e.g. a local `top`/`end` in the caller's stack frame); drawn as a
|
||||
// small pill above the graph with an arrow to whichever node's id
|
||||
// matches its value, since a node's own address equals its first
|
||||
// member's address.
|
||||
|
||||
module.exports = function (register, lib) {
|
||||
var sj = lib.semanticJson;
|
||||
@@ -31,10 +36,16 @@ module.exports = function (register, lib) {
|
||||
label: sj.sOptionalProp(sj.sString(), {}),
|
||||
});
|
||||
|
||||
var sRoot = sj.sOpenObject({
|
||||
name: sj.sString(),
|
||||
value: sj.sString(),
|
||||
});
|
||||
|
||||
var sData = sj.sOpenObject({
|
||||
kind: sj.sOpenObject({ nodeTable: sj.sLiteral(true) }),
|
||||
nodes: sj.sArrayOf(sNode),
|
||||
edges: sj.sArrayOf(sEdge),
|
||||
roots: sj.sOptionalProp(sj.sArrayOf(sRoot), {}),
|
||||
});
|
||||
|
||||
var HEADER_HEIGHT = 24;
|
||||
@@ -44,6 +55,8 @@ module.exports = function (register, lib) {
|
||||
var H_GAP = 70;
|
||||
var V_GAP = 24;
|
||||
var PADDING = 20;
|
||||
var ROOT_ROW_HEIGHT = 50;
|
||||
var ROOT_BADGE_HEIGHT = 22;
|
||||
|
||||
function layout(nodes, edges) {
|
||||
var ids = nodes.map(function (n) {
|
||||
@@ -146,6 +159,8 @@ module.exports = function (register, lib) {
|
||||
};
|
||||
|
||||
var positions = layout(data.nodes, data.edges);
|
||||
var roots = data.roots || [];
|
||||
var topOffset = roots.length > 0 ? ROOT_ROW_HEIGHT : 0;
|
||||
|
||||
var byId = {};
|
||||
data.nodes.forEach(function (n) {
|
||||
@@ -164,12 +179,33 @@ module.exports = function (register, lib) {
|
||||
Object.keys(positions).forEach(function (id) {
|
||||
var p = positions[id];
|
||||
var x = p.level * (NODE_WIDTH + H_GAP);
|
||||
var y = p.col * (Math.max(boxHeights[id], HEADER_HEIGHT + ROW_HEIGHT) + V_GAP);
|
||||
var y = topOffset + p.col * (Math.max(boxHeights[id], HEADER_HEIGHT + ROW_HEIGHT) + V_GAP);
|
||||
pixelPos[id] = { x: x, y: y };
|
||||
maxX = Math.max(maxX, x + NODE_WIDTH);
|
||||
maxY = Math.max(maxY, y + boxHeights[id]);
|
||||
});
|
||||
|
||||
// Spread root badges above the nodes they point to; unmatched roots
|
||||
// (e.g. a pointer into the middle of a struct, or a stale/null value)
|
||||
// queue up along the top-left instead.
|
||||
var rootPos = {};
|
||||
var nextUnmatchedX = 0;
|
||||
var badgesPerTarget = {};
|
||||
roots.forEach(function (r) {
|
||||
var target = pixelPos[r.value];
|
||||
var x;
|
||||
if (target) {
|
||||
var stackIndex = badgesPerTarget[r.value] || 0;
|
||||
badgesPerTarget[r.value] = stackIndex + 1;
|
||||
x = target.x + stackIndex * 90;
|
||||
} else {
|
||||
x = nextUnmatchedX;
|
||||
nextUnmatchedX += NODE_WIDTH / 2 + 20;
|
||||
}
|
||||
rootPos[r.name] = { x: x, y: 0, matched: !!target };
|
||||
maxX = Math.max(maxX, x + NODE_WIDTH);
|
||||
});
|
||||
|
||||
var canvas = el("div", {
|
||||
position: "relative",
|
||||
width: maxX + "px",
|
||||
@@ -307,6 +343,48 @@ module.exports = function (register, lib) {
|
||||
canvas.appendChild(box);
|
||||
});
|
||||
|
||||
roots.forEach(function (r) {
|
||||
var pos = rootPos[r.name];
|
||||
if (pos.matched) {
|
||||
var targetPos = pixelPos[r.value];
|
||||
var x1 = pos.x + NODE_WIDTH / 2;
|
||||
var y1 = ROOT_BADGE_HEIGHT;
|
||||
var x2 = targetPos.x + NODE_WIDTH / 2;
|
||||
var y2 = targetPos.y;
|
||||
var line = document.createElementNS(svgNs, "path");
|
||||
line.setAttribute("d", "M" + x1 + "," + y1 + " L " + x2 + "," + y2);
|
||||
line.setAttribute("fill", "none");
|
||||
line.setAttribute("stroke", colors.edge);
|
||||
line.setAttribute("stroke-width", "1.5");
|
||||
line.setAttribute("stroke-dasharray", "3,3");
|
||||
line.setAttribute("marker-end", "url(#dv-arrow)");
|
||||
svg.appendChild(line);
|
||||
}
|
||||
|
||||
var badge = el("div", {
|
||||
position: "absolute",
|
||||
left: pos.x + "px",
|
||||
top: "0px",
|
||||
width: NODE_WIDTH / 2 + "px",
|
||||
height: ROOT_BADGE_HEIGHT + "px",
|
||||
lineHeight: ROOT_BADGE_HEIGHT + "px",
|
||||
textAlign: "center",
|
||||
borderRadius: "11px",
|
||||
border: "1px solid " + colors.border,
|
||||
background: pos.matched ? colors.headerBg : "transparent",
|
||||
color: pos.matched ? colors.headerFg : colors.pointerValue,
|
||||
fontFamily: "var(--vscode-editor-font-family, monospace)",
|
||||
fontSize: "12px",
|
||||
fontWeight: "bold",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
});
|
||||
badge.title = r.name + " = " + r.value;
|
||||
badge.textContent = r.name + (pos.matched ? "" : " (" + r.value + ")");
|
||||
canvas.appendChild(badge);
|
||||
});
|
||||
|
||||
target.appendChild(canvas);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user