Skip to content

Commit 611d81b

Browse files
authored
[lldb-dap] Adding a modules explorer to lldb-dap ext. (#138977)
This creates a very basic module explorer for tracking and displaying loaded modules, reported by lldb-dap for the active debug session. This includes a basic session tracker that we can use to observe the debug session and collect specific information for additional visualizations in the lldb-dap ext. Here is a screenshot of the current visualization in the tree view. There is some unfortunate wrapping of the path, but it shows the basic support that could be extended in the future. <img width="1759" alt="Screenshot 2025-05-07 at 2 52 50 PM" src="https://github.com/user-attachments/assets/588baa2f-61d5-4434-8692-b1d0cce42875" />
1 parent fa43e8f commit 611d81b

File tree

7 files changed

+207
-14
lines changed

7 files changed

+207
-14
lines changed

lldb/tools/lldb-dap/package-lock.json

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lldb/tools/lldb-dap/package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@
3030
"devDependencies": {
3131
"@types/node": "^18.19.41",
3232
"@types/vscode": "1.75.0",
33+
"@vscode/debugprotocol": "^1.68.0",
3334
"@vscode/vsce": "^3.2.2",
34-
"prettier-plugin-curly": "^0.3.1",
3535
"prettier": "^3.4.2",
36+
"prettier-plugin-curly": "^0.3.1",
3637
"typescript": "^5.7.3"
3738
},
3839
"activationEvents": [
@@ -760,6 +761,16 @@
760761
}
761762
]
762763
}
763-
]
764+
],
765+
"views": {
766+
"debug": [
767+
{
768+
"id": "lldb-dap.modules",
769+
"name": "Modules",
770+
"when": "inDebugMode && debugType == 'lldb-dap'",
771+
"icon": "$(symbol-module)"
772+
}
773+
]
774+
}
764775
}
765776
}

lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class LLDBDapConfigurationProvider
7878
debugConfiguration: vscode.DebugConfiguration,
7979
token?: vscode.CancellationToken,
8080
): Promise<vscode.DebugConfiguration> {
81-
let config = vscode.workspace.getConfiguration("lldb-dap.defaults");
81+
let config = vscode.workspace.getConfiguration("lldb-dap");
8282
for (const [key, cfg] of Object.entries(configurations)) {
8383
if (Reflect.has(debugConfiguration, key)) {
8484
continue;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { DebugProtocol } from "@vscode/debugprotocol";
2+
import * as vscode from "vscode";
3+
4+
/** A helper type for mapping event types to their corresponding data type. */
5+
// prettier-ignore
6+
interface EventMap {
7+
"module": DebugProtocol.ModuleEvent;
8+
}
9+
10+
/** A type assertion to check if a ProtocolMessage is an event or if it is a specific event. */
11+
function isEvent(
12+
message: DebugProtocol.ProtocolMessage,
13+
): message is DebugProtocol.Event;
14+
function isEvent<K extends keyof EventMap>(
15+
message: DebugProtocol.ProtocolMessage,
16+
event: K,
17+
): message is EventMap[K];
18+
function isEvent(
19+
message: DebugProtocol.ProtocolMessage,
20+
event?: string,
21+
): boolean {
22+
return (
23+
message.type === "event" &&
24+
(!event || (message as DebugProtocol.Event).event === event)
25+
);
26+
}
27+
28+
/** Tracks lldb-dap sessions for data visualizers. */
29+
export class DebugSessionTracker
30+
implements vscode.DebugAdapterTrackerFactory, vscode.Disposable
31+
{
32+
/**
33+
* Tracks active modules for each debug sessions.
34+
*
35+
* The modules are kept in an array to maintain the load order of the modules.
36+
*/
37+
private modules = new Map<vscode.DebugSession, DebugProtocol.Module[]>();
38+
private modulesChanged = new vscode.EventEmitter<void>();
39+
40+
/**
41+
* Fired when modules are changed for any active debug session.
42+
*
43+
* Use `debugSessionModules` to retieve the active modules for a given debug session.
44+
*/
45+
onDidChangeModules: vscode.Event<void> = this.modulesChanged.event;
46+
47+
dispose() {
48+
this.modules.clear();
49+
this.modulesChanged.dispose();
50+
}
51+
52+
createDebugAdapterTracker(
53+
session: vscode.DebugSession,
54+
): vscode.ProviderResult<vscode.DebugAdapterTracker> {
55+
return {
56+
onDidSendMessage: (message) => this.onDidSendMessage(session, message),
57+
onExit: () => this.onExit(session),
58+
};
59+
}
60+
61+
/**
62+
* Retrieves the modules for the given debug session.
63+
*
64+
* Modules are returned in load order.
65+
*/
66+
debugSessionModules(session: vscode.DebugSession): DebugProtocol.Module[] {
67+
return this.modules.get(session) ?? [];
68+
}
69+
70+
/** Clear information from the active session. */
71+
private onExit(session: vscode.DebugSession) {
72+
this.modules.delete(session);
73+
this.modulesChanged.fire();
74+
}
75+
76+
private onDidSendMessage(
77+
session: vscode.DebugSession,
78+
message: DebugProtocol.ProtocolMessage,
79+
) {
80+
if (isEvent(message, "module")) {
81+
const { module, reason } = message.body;
82+
const modules = this.modules.get(session) ?? [];
83+
switch (reason) {
84+
case "new":
85+
case "changed": {
86+
const index = modules.findIndex((m) => m.id === module.id);
87+
if (index !== -1) {
88+
modules[index] = module;
89+
} else {
90+
modules.push(module);
91+
}
92+
break;
93+
}
94+
case "removed": {
95+
const index = modules.findIndex((m) => m.id === module.id);
96+
if (index !== -1) {
97+
modules.splice(index, 1);
98+
}
99+
break;
100+
}
101+
default:
102+
console.error("unexpected module event reason");
103+
break;
104+
}
105+
this.modules.set(session, modules);
106+
this.modulesChanged.fire();
107+
}
108+
}
109+
}

lldb/tools/lldb-dap/src-ts/disposable-context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class DisposableContext implements vscode.Disposable {
2121
*
2222
* @param disposable The disposable to register.
2323
*/
24-
public pushSubscription(disposable: vscode.Disposable) {
25-
this._disposables.push(disposable);
24+
public pushSubscription(...disposable: vscode.Disposable[]) {
25+
this._disposables.push(...disposable);
2626
}
2727
}

lldb/tools/lldb-dap/src-ts/extension.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { DisposableContext } from "./disposable-context";
55
import { LaunchUriHandler } from "./uri-launch-handler";
66
import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
77
import { LLDBDapServer } from "./lldb-dap-server";
8+
import { DebugSessionTracker } from "./debug-session-tracker";
9+
import { ModulesDataProvider } from "./ui/modules-data-provider";
810

911
/**
1012
* This class represents the extension and manages its life cycle. Other extensions
@@ -15,23 +17,27 @@ export class LLDBDapExtension extends DisposableContext {
1517
super();
1618

1719
const lldbDapServer = new LLDBDapServer();
18-
this.pushSubscription(lldbDapServer);
20+
const sessionTracker = new DebugSessionTracker();
1921

2022
this.pushSubscription(
23+
lldbDapServer,
24+
sessionTracker,
2125
vscode.debug.registerDebugConfigurationProvider(
2226
"lldb-dap",
2327
new LLDBDapConfigurationProvider(lldbDapServer),
2428
),
25-
);
26-
27-
this.pushSubscription(
2829
vscode.debug.registerDebugAdapterDescriptorFactory(
2930
"lldb-dap",
3031
new LLDBDapDescriptorFactory(),
3132
),
32-
);
33-
34-
this.pushSubscription(
33+
vscode.debug.registerDebugAdapterTrackerFactory(
34+
"lldb-dap",
35+
sessionTracker,
36+
),
37+
vscode.window.registerTreeDataProvider(
38+
"lldb-dap.modules",
39+
new ModulesDataProvider(sessionTracker),
40+
),
3541
vscode.window.registerUriHandler(new LaunchUriHandler()),
3642
);
3743
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as vscode from "vscode";
2+
import { DebugProtocol } from "@vscode/debugprotocol";
3+
import { DebugSessionTracker } from "../debug-session-tracker";
4+
5+
/** A tree data provider for listing loaded modules for the active debug session. */
6+
export class ModulesDataProvider
7+
implements vscode.TreeDataProvider<DebugProtocol.Module>
8+
{
9+
private changeTreeData = new vscode.EventEmitter<void>();
10+
readonly onDidChangeTreeData = this.changeTreeData.event;
11+
12+
constructor(private readonly tracker: DebugSessionTracker) {
13+
tracker.onDidChangeModules(() => this.changeTreeData.fire());
14+
vscode.debug.onDidChangeActiveDebugSession(() =>
15+
this.changeTreeData.fire(),
16+
);
17+
}
18+
19+
getTreeItem(module: DebugProtocol.Module): vscode.TreeItem {
20+
let treeItem = new vscode.TreeItem(/*label=*/ module.name);
21+
if (module.path) {
22+
treeItem.description = `${module.id} -- ${module.path}`;
23+
} else {
24+
treeItem.description = `${module.id}`;
25+
}
26+
27+
const tooltip = new vscode.MarkdownString();
28+
tooltip.appendMarkdown(`# Module '${module.name}'\n\n`);
29+
tooltip.appendMarkdown(`- **id** : ${module.id}\n`);
30+
if (module.addressRange) {
31+
tooltip.appendMarkdown(`- **load address** : ${module.addressRange}\n`);
32+
}
33+
if (module.path) {
34+
tooltip.appendMarkdown(`- **path** : ${module.path}\n`);
35+
}
36+
if (module.version) {
37+
tooltip.appendMarkdown(`- **version** : ${module.version}\n`);
38+
}
39+
if (module.symbolStatus) {
40+
tooltip.appendMarkdown(`- **symbol status** : ${module.symbolStatus}\n`);
41+
}
42+
if (module.symbolFilePath) {
43+
tooltip.appendMarkdown(
44+
`- **symbol file path** : ${module.symbolFilePath}\n`,
45+
);
46+
}
47+
48+
treeItem.tooltip = tooltip;
49+
return treeItem;
50+
}
51+
52+
getChildren(): DebugProtocol.Module[] {
53+
if (!vscode.debug.activeDebugSession) {
54+
return [];
55+
}
56+
57+
return this.tracker.debugSessionModules(vscode.debug.activeDebugSession);
58+
}
59+
}

0 commit comments

Comments
 (0)