[Lldb-commits] [lldb] [lldb-dap] Adding a modules explorer to lldb-dap ext. (PR #138977)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Wed May 7 16:17:42 PDT 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/138977
>From 756174c282fc7101bfadb29556f39ce046ad0116 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 7 May 2025 14:54:29 -0700
Subject: [PATCH 1/2] [lldb-dap] Adding a modules explorer to lldb-dap ext.
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.
---
lldb/tools/lldb-dap/package-lock.json | 12 ++-
lldb/tools/lldb-dap/package.json | 15 +++-
.../src-ts/debug-configuration-provider.ts | 2 +-
.../lldb-dap/src-ts/debug-session-tracker.ts | 87 +++++++++++++++++++
.../lldb-dap/src-ts/disposable-context.ts | 4 +-
lldb/tools/lldb-dap/src-ts/extension.ts | 18 ++++
.../src-ts/ui/modules-data-provider.ts | 58 +++++++++++++
7 files changed, 189 insertions(+), 7 deletions(-)
create mode 100644 lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
create mode 100644 lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts
diff --git a/lldb/tools/lldb-dap/package-lock.json b/lldb/tools/lldb-dap/package-lock.json
index ab5c7dc33a8e5..0a2b9e764067e 100644
--- a/lldb/tools/lldb-dap/package-lock.json
+++ b/lldb/tools/lldb-dap/package-lock.json
@@ -1,16 +1,17 @@
{
"name": "lldb-dap",
- "version": "0.2.10",
+ "version": "0.2.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lldb-dap",
- "version": "0.2.10",
+ "version": "0.2.13",
"license": "Apache 2.0 License with LLVM exceptions",
"devDependencies": {
"@types/node": "^18.19.41",
"@types/vscode": "1.75.0",
+ "@vscode/debugprotocol": "^1.68.0",
"@vscode/vsce": "^3.2.2",
"prettier": "^3.4.2",
"prettier-plugin-curly": "^0.3.1",
@@ -405,6 +406,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@vscode/debugprotocol": {
+ "version": "1.68.0",
+ "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz",
+ "integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@vscode/vsce": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.2.2.tgz",
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index f66badc2a930f..a8dce7cab8a79 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -30,9 +30,10 @@
"devDependencies": {
"@types/node": "^18.19.41",
"@types/vscode": "1.75.0",
+ "@vscode/debugprotocol": "^1.68.0",
"@vscode/vsce": "^3.2.2",
- "prettier-plugin-curly": "^0.3.1",
"prettier": "^3.4.2",
+ "prettier-plugin-curly": "^0.3.1",
"typescript": "^5.7.3"
},
"activationEvents": [
@@ -763,6 +764,16 @@
}
]
}
- ]
+ ],
+ "views": {
+ "debug": [
+ {
+ "id": "lldb-dap.modulesExplorer",
+ "name": "LLDB Modules Explorer",
+ "when": "inDebugMode && debugType == 'lldb-dap'",
+ "icon": "$(symbol-module)"
+ }
+ ]
+ }
}
}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
index c91b101f4a9ba..957bc5e1eb956 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -78,7 +78,7 @@ export class LLDBDapConfigurationProvider
debugConfiguration: vscode.DebugConfiguration,
token?: vscode.CancellationToken,
): Promise<vscode.DebugConfiguration> {
- let config = vscode.workspace.getConfiguration("lldb-dap.defaults");
+ let config = vscode.workspace.getConfiguration("lldb-dap");
for (const [key, cfg] of Object.entries(configurations)) {
if (Reflect.has(debugConfiguration, key)) {
continue;
diff --git a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
new file mode 100644
index 0000000000000..d756e9e743bb0
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
@@ -0,0 +1,87 @@
+import { DebugProtocol } from "@vscode/debugprotocol";
+import * as vscode from "vscode";
+
+interface EventMap {
+ module: DebugProtocol.ModuleEvent;
+}
+
+function isEvent(
+ message: DebugProtocol.ProtocolMessage,
+): message is DebugProtocol.Event;
+function isEvent<K extends keyof EventMap>(
+ message: DebugProtocol.ProtocolMessage,
+ event: K,
+): message is EventMap[K];
+function isEvent(
+ message: DebugProtocol.ProtocolMessage,
+ event?: string,
+): boolean {
+ return (
+ message.type === "event" &&
+ (!event || (message as DebugProtocol.Event).event === event)
+ );
+}
+
+export class DebugSessionTracker
+ implements vscode.DebugAdapterTrackerFactory, vscode.Disposable
+{
+ private modules = new Map<vscode.DebugSession, DebugProtocol.Module[]>();
+ private modulesChanged = new vscode.EventEmitter<void>();
+ onDidChangeModules: vscode.Event<void> = this.modulesChanged.event;
+
+ dispose() {
+ this.modules.clear();
+ this.modulesChanged.dispose();
+ }
+
+ createDebugAdapterTracker(
+ session: vscode.DebugSession,
+ ): vscode.ProviderResult<vscode.DebugAdapterTracker> {
+ return {
+ onDidSendMessage: (message) => this.onDidSendMessage(session, message),
+ onExit: () => this.onExit(session),
+ };
+ }
+
+ debugSessionModules(session: vscode.DebugSession): DebugProtocol.Module[] {
+ return this.modules.get(session) ?? [];
+ }
+
+ private onExit(session: vscode.DebugSession) {
+ this.modules.delete(session);
+ }
+
+ private onDidSendMessage(
+ session: vscode.DebugSession,
+ message: DebugProtocol.ProtocolMessage,
+ ) {
+ if (isEvent(message, "module")) {
+ const { module, reason } = message.body;
+ const modules = this.modules.get(session) ?? [];
+ switch (reason) {
+ case "new":
+ case "changed": {
+ const index = modules.findIndex((m) => m.id === module.id);
+ if (index !== -1) {
+ modules[index] = module;
+ } else {
+ modules.push(module);
+ }
+ break;
+ }
+ case "removed": {
+ const index = modules.findIndex((m) => m.id === module.id);
+ if (index !== -1) {
+ modules.splice(index, 1);
+ }
+ break;
+ }
+ default:
+ console.error("unexpected module event reason");
+ break;
+ }
+ this.modules.set(session, modules);
+ this.modulesChanged.fire();
+ }
+ }
+}
diff --git a/lldb/tools/lldb-dap/src-ts/disposable-context.ts b/lldb/tools/lldb-dap/src-ts/disposable-context.ts
index 39d9f18d2d85f..42ece763d247f 100644
--- a/lldb/tools/lldb-dap/src-ts/disposable-context.ts
+++ b/lldb/tools/lldb-dap/src-ts/disposable-context.ts
@@ -21,7 +21,7 @@ export class DisposableContext implements vscode.Disposable {
*
* @param disposable The disposable to register.
*/
- public pushSubscription(disposable: vscode.Disposable) {
- this._disposables.push(disposable);
+ public pushSubscription(...disposable: vscode.Disposable[]) {
+ this._disposables.push(...disposable);
}
}
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts
index 0b014f953d5ba..1bca842480809 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -5,6 +5,8 @@ import { DisposableContext } from "./disposable-context";
import { LaunchUriHandler } from "./uri-launch-handler";
import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
import { LLDBDapServer } from "./lldb-dap-server";
+import { DebugSessionTracker } from "./debug-session-tracker";
+import { ModuleDataProvider } from "./ui/modules-data-provider";
/**
* This class represents the extension and manages its life cycle. Other extensions
@@ -31,6 +33,22 @@ export class LLDBDapExtension extends DisposableContext {
),
);
+ const sessionTracker = new DebugSessionTracker();
+
+ this.pushSubscription(
+ vscode.debug.registerDebugAdapterTrackerFactory(
+ "lldb-dap",
+ sessionTracker,
+ ),
+ );
+
+ this.pushSubscription(
+ vscode.window.registerTreeDataProvider(
+ "lldb-dap.modulesExplorer",
+ new ModuleDataProvider(sessionTracker),
+ ),
+ );
+
this.pushSubscription(
vscode.window.registerUriHandler(new LaunchUriHandler()),
);
diff --git a/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts
new file mode 100644
index 0000000000000..d5cea513d7ef4
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts
@@ -0,0 +1,58 @@
+import * as vscode from "vscode";
+import { DebugProtocol } from "@vscode/debugprotocol";
+import { DebugSessionTracker } from "../debug-session-tracker";
+
+export class ModuleDataProvider
+ implements vscode.TreeDataProvider<DebugProtocol.Module>
+{
+ private changeTreeData = new vscode.EventEmitter<void>();
+ readonly onDidChangeTreeData = this.changeTreeData.event;
+
+ constructor(private readonly tracker: DebugSessionTracker) {
+ tracker.onDidChangeModules(() => this.changeTreeData.fire());
+ vscode.debug.onDidChangeActiveDebugSession(() =>
+ this.changeTreeData.fire(),
+ );
+ }
+
+ getTreeItem(module: DebugProtocol.Module): vscode.TreeItem {
+ let treeItem = new vscode.TreeItem(/*label=*/ module.name);
+ if (module.path) {
+ treeItem.description = `${module.id} -- ${module.path}`;
+ } else {
+ treeItem.description = `${module.id}`;
+ }
+
+ const tooltip = new vscode.MarkdownString();
+ tooltip.appendMarkdown(`# Module '${module.name}'\n\n`);
+ tooltip.appendMarkdown(`- **id** : ${module.id}\n`);
+ if (module.addressRange) {
+ tooltip.appendMarkdown(`- **load address** : ${module.addressRange}\n`);
+ }
+ if (module.path) {
+ tooltip.appendMarkdown(`- **path** : ${module.path}\n`);
+ }
+ if (module.version) {
+ tooltip.appendMarkdown(`- **version** : ${module.version}\n`);
+ }
+ if (module.symbolStatus) {
+ tooltip.appendMarkdown(`- **symbol status** : ${module.symbolStatus}\n`);
+ }
+ if (module.symbolFilePath) {
+ tooltip.appendMarkdown(
+ `- **symbol file path** : ${module.symbolFilePath}\n`,
+ );
+ }
+
+ treeItem.tooltip = tooltip;
+ return treeItem;
+ }
+
+ getChildren(): DebugProtocol.Module[] {
+ if (!vscode.debug.activeDebugSession) {
+ return [];
+ }
+
+ return this.tracker.debugSessionModules(vscode.debug.activeDebugSession);
+ }
+}
>From da002083736f97692471d661cd31a64b37835205 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 7 May 2025 16:16:37 -0700
Subject: [PATCH 2/2] Adjusting the modules tree view name and adding some
documentation to help explain the code a bit.
---
lldb/tools/lldb-dap/package.json | 4 ++--
.../lldb-dap/src-ts/debug-session-tracker.ts | 24 ++++++++++++++++++-
lldb/tools/lldb-dap/src-ts/extension.ts | 24 +++++--------------
.../src-ts/ui/modules-data-provider.ts | 3 ++-
4 files changed, 33 insertions(+), 22 deletions(-)
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index a8dce7cab8a79..1149a33719ae5 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -768,8 +768,8 @@
"views": {
"debug": [
{
- "id": "lldb-dap.modulesExplorer",
- "name": "LLDB Modules Explorer",
+ "id": "lldb-dap.modules",
+ "name": "Modules",
"when": "inDebugMode && debugType == 'lldb-dap'",
"icon": "$(symbol-module)"
}
diff --git a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
index d756e9e743bb0..1ce190938d9c7 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
@@ -1,10 +1,13 @@
import { DebugProtocol } from "@vscode/debugprotocol";
import * as vscode from "vscode";
+/** A helper type for mapping event types to their corresponding data type. */
+// prettier-ignore
interface EventMap {
- module: DebugProtocol.ModuleEvent;
+ "module": DebugProtocol.ModuleEvent;
}
+/** A type assertion to check if a ProtocolMessage is an event or if it is a specific event. */
function isEvent(
message: DebugProtocol.ProtocolMessage,
): message is DebugProtocol.Event;
@@ -22,11 +25,23 @@ function isEvent(
);
}
+/** Tracks lldb-dap sessions for data visualizers. */
export class DebugSessionTracker
implements vscode.DebugAdapterTrackerFactory, vscode.Disposable
{
+ /**
+ * Tracks active modules for each debug sessions.
+ *
+ * The modules are kept in an array to maintain the load order of the modules.
+ */
private modules = new Map<vscode.DebugSession, DebugProtocol.Module[]>();
private modulesChanged = new vscode.EventEmitter<void>();
+
+ /**
+ * Fired when modules are changed for any active debug session.
+ *
+ * Use `debugSessionModules` to retieve the active modules for a given debug session.
+ */
onDidChangeModules: vscode.Event<void> = this.modulesChanged.event;
dispose() {
@@ -43,12 +58,19 @@ export class DebugSessionTracker
};
}
+ /**
+ * Retrieves the modules for the given debug session.
+ *
+ * Modules are returned in load order.
+ */
debugSessionModules(session: vscode.DebugSession): DebugProtocol.Module[] {
return this.modules.get(session) ?? [];
}
+ /** Clear information from the active session. */
private onExit(session: vscode.DebugSession) {
this.modules.delete(session);
+ this.modulesChanged.fire();
}
private onDidSendMessage(
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts
index 1bca842480809..a5c0a09ae60cf 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -6,7 +6,7 @@ import { LaunchUriHandler } from "./uri-launch-handler";
import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
import { LLDBDapServer } from "./lldb-dap-server";
import { DebugSessionTracker } from "./debug-session-tracker";
-import { ModuleDataProvider } from "./ui/modules-data-provider";
+import { ModulesDataProvider } from "./ui/modules-data-provider";
/**
* This class represents the extension and manages its life cycle. Other extensions
@@ -17,39 +17,27 @@ export class LLDBDapExtension extends DisposableContext {
super();
const lldbDapServer = new LLDBDapServer();
- this.pushSubscription(lldbDapServer);
+ const sessionTracker = new DebugSessionTracker();
this.pushSubscription(
+ lldbDapServer,
+ sessionTracker,
vscode.debug.registerDebugConfigurationProvider(
"lldb-dap",
new LLDBDapConfigurationProvider(lldbDapServer),
),
- );
-
- this.pushSubscription(
vscode.debug.registerDebugAdapterDescriptorFactory(
"lldb-dap",
new LLDBDapDescriptorFactory(),
),
- );
-
- const sessionTracker = new DebugSessionTracker();
-
- this.pushSubscription(
vscode.debug.registerDebugAdapterTrackerFactory(
"lldb-dap",
sessionTracker,
),
- );
-
- this.pushSubscription(
vscode.window.registerTreeDataProvider(
- "lldb-dap.modulesExplorer",
- new ModuleDataProvider(sessionTracker),
+ "lldb-dap.modules",
+ new ModulesDataProvider(sessionTracker),
),
- );
-
- this.pushSubscription(
vscode.window.registerUriHandler(new LaunchUriHandler()),
);
}
diff --git a/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts
index d5cea513d7ef4..5af3d52e9870c 100644
--- a/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts
+++ b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts
@@ -2,7 +2,8 @@ import * as vscode from "vscode";
import { DebugProtocol } from "@vscode/debugprotocol";
import { DebugSessionTracker } from "../debug-session-tracker";
-export class ModuleDataProvider
+/** A tree data provider for listing loaded modules for the active debug session. */
+export class ModulesDataProvider
implements vscode.TreeDataProvider<DebugProtocol.Module>
{
private changeTreeData = new vscode.EventEmitter<void>();
More information about the lldb-commits
mailing list