
- VS Code extension: - Add module symbol table viewer using [Tabulator](https://tabulator.info/) for sorting and formatting rows. - Add context menu action to the modules tree. - lldb-dap - Add `DAPGetModuleSymbolsRequest` to get symbols from a module. Fixes #140626 [Screencast From 2025-08-15 19-12-33.webm](https://github.com/user-attachments/assets/75e2f229-ac82-487c-812e-3ea33a575b70)
174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
import { DebugProtocol } from "@vscode/debugprotocol";
|
|
import * as vscode from "vscode";
|
|
|
|
export interface LLDBDapCapabilities extends DebugProtocol.Capabilities {
|
|
/** The debug adapter supports the `moduleSymbols` request. */
|
|
supportsModuleSymbolsRequest?: boolean;
|
|
}
|
|
|
|
/** A helper type for mapping event types to their corresponding data type. */
|
|
// prettier-ignore
|
|
interface EventMap {
|
|
"module": DebugProtocol.ModuleEvent;
|
|
"exited": DebugProtocol.ExitedEvent;
|
|
"capabilities": DebugProtocol.CapabilitiesEvent;
|
|
}
|
|
|
|
/** 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<
|
|
vscode.DebugSession | undefined
|
|
>();
|
|
private sessionReceivedCapabilities =
|
|
new vscode.EventEmitter<[ vscode.DebugSession, LLDBDapCapabilities ]>();
|
|
private sessionExited = new vscode.EventEmitter<vscode.DebugSession>();
|
|
|
|
/**
|
|
* 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<vscode.DebugSession | undefined> =
|
|
this.modulesChanged.event;
|
|
|
|
/** Fired when a debug session is initialized. */
|
|
onDidReceiveSessionCapabilities:
|
|
vscode.Event<[ vscode.DebugSession, LLDBDapCapabilities ]> =
|
|
this.sessionReceivedCapabilities.event;
|
|
|
|
/** Fired when a debug session is exiting. */
|
|
onDidExitSession: vscode.Event<vscode.DebugSession> =
|
|
this.sessionExited.event;
|
|
|
|
constructor(private logger: vscode.LogOutputChannel) {
|
|
this.onDidChangeModules(this.moduleChangedListener, this);
|
|
vscode.debug.onDidChangeActiveDebugSession((session) =>
|
|
this.modulesChanged.fire(session),
|
|
);
|
|
}
|
|
|
|
dispose() {
|
|
this.modules.clear();
|
|
this.modulesChanged.dispose();
|
|
}
|
|
|
|
createDebugAdapterTracker(
|
|
session: vscode.DebugSession,
|
|
): vscode.ProviderResult<vscode.DebugAdapterTracker> {
|
|
this.logger.info(`Starting debug session "${session.name}"`);
|
|
let stopping = false;
|
|
return {
|
|
onError: (error) => !stopping && this.logger.error(error), // Can throw benign read errors when shutting down.
|
|
onDidSendMessage: (message) => this.onDidSendMessage(session, message),
|
|
onWillStopSession: () => (stopping = true),
|
|
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(undefined);
|
|
}
|
|
|
|
private showModulesTreeView(showModules: boolean) {
|
|
vscode.commands.executeCommand(
|
|
"setContext",
|
|
"lldb-dap.showModules",
|
|
showModules,
|
|
);
|
|
}
|
|
|
|
private moduleChangedListener(session: vscode.DebugSession | undefined) {
|
|
if (!session) {
|
|
this.showModulesTreeView(false);
|
|
return;
|
|
}
|
|
|
|
if (session == vscode.debug.activeDebugSession) {
|
|
const sessionHasModules = this.modules.get(session) != undefined;
|
|
this.showModulesTreeView(sessionHasModules);
|
|
}
|
|
}
|
|
|
|
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(session);
|
|
} else if (isEvent(message, "exited")) {
|
|
// The vscode.DebugAdapterTracker#onExit event is sometimes called with
|
|
// exitCode = undefined but the exit event from LLDB-DAP always has the "exitCode"
|
|
const { exitCode } = message.body;
|
|
this.logger.info(
|
|
`Session "${session.name}" exited with code ${exitCode}`,
|
|
);
|
|
|
|
this.sessionExited.fire(session);
|
|
} else if (isEvent(message, "capabilities")) {
|
|
this.sessionReceivedCapabilities.fire([ session, message.body.capabilities ]);
|
|
}
|
|
}
|
|
}
|