Author: John Harrison Date: 2025-05-08T15:25:28-07:00 New Revision: 611d81bd9304768f3cdb101d37c81d36b9762723
URL: https://github.com/llvm/llvm-project/commit/611d81bd9304768f3cdb101d37c81d36b9762723 DIFF: https://github.com/llvm/llvm-project/commit/611d81bd9304768f3cdb101d37c81d36b9762723.diff LOG: [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" /> Added: lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts Modified: lldb/tools/lldb-dap/package-lock.json lldb/tools/lldb-dap/package.json lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts lldb/tools/lldb-dap/src-ts/disposable-context.ts lldb/tools/lldb-dap/src-ts/extension.ts Removed: ################################################################################ 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 a7631464d236a..e3e46526f379f 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": [ @@ -760,6 +761,16 @@ } ] } - ] + ], + "views": { + "debug": [ + { + "id": "lldb-dap.modules", + "name": "Modules", + "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..1ce190938d9c7 --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts @@ -0,0 +1,109 @@ +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; +} + +/** 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; +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) + ); +} + +/** 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() { + 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), + }; + } + + /** + * 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( + 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..a5c0a09ae60cf 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 { ModulesDataProvider } from "./ui/modules-data-provider"; /** * This class represents the extension and manages its life cycle. Other extensions @@ -15,23 +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(), ), - ); - - this.pushSubscription( + vscode.debug.registerDebugAdapterTrackerFactory( + "lldb-dap", + sessionTracker, + ), + vscode.window.registerTreeDataProvider( + "lldb-dap.modules", + new ModulesDataProvider(sessionTracker), + ), 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..5af3d52e9870c --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/ui/modules-data-provider.ts @@ -0,0 +1,59 @@ +import * as vscode from "vscode"; +import { DebugProtocol } from "@vscode/debugprotocol"; +import { DebugSessionTracker } from "../debug-session-tracker"; + +/** 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>(); + 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); + } +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits