Skip to content
Merged
4 changes: 2 additions & 2 deletions lldb/tools/lldb-dap/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,16 @@
"description": "The path to the lldb-dap binary, e.g. /usr/local/bin/lldb-dap"
},
"lldb-dap.log-path": {
"scope": "machine-overridable",
"type": "string",
"description": "The log path for lldb-dap (if any)",
"markdownDeprecationMessage": "Use the `#lldb-dap.logFolder#` setting instead"
},
"lldb-dap.logFolder": {
"order": 0,
"scope": "machine-overridable",
"type": "string",
"description": "The log path for lldb-dap (if any)"
"markdownDescription": "The folder to persist lldb-dap logs. If no value is provided, logs will be persisted in the [Extension Logs Folder](command:workbench.action.openExtensionLogsFolder)."
},
"lldb-dap.serverMode": {
"order": 0,
Expand All @@ -110,6 +116,11 @@
"additionalProperties": {
"type": "string"
}
},
"lldb-dap.captureSessionLogs": {
"type": "boolean",
"description": "When enabled, LLDB-DAP session logs will be written to the Extension's log folder if the `lldb-dap.log-path` setting is not explicitly set.",
"default": false
}
}
},
Expand Down
37 changes: 35 additions & 2 deletions lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as child_process from "child_process";
import * as fs from "node:fs/promises";
import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message";
import { ErrorWithNotification } from "./ui/error-with-notification";
import { LogFilePathProvider, LogType } from "./logging";

const exec = util.promisify(child_process.execFile);

Expand Down Expand Up @@ -160,20 +161,30 @@ async function getDAPArguments(
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
*
* @param logger The {@link vscode.LogOutputChannel} to log setup diagnostics
* @param logFilePath The {@link LogFilePathProvider} for determining where to put session logs
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
* @throws An {@link ErrorWithNotification} if something went wrong
* @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap
*/
export async function createDebugAdapterExecutable(
logger: vscode.LogOutputChannel,
logFilePath: LogFilePathProvider,
workspaceFolder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
): Promise<vscode.DebugAdapterExecutable> {
const config = vscode.workspace.workspaceFile ? vscode.workspace.getConfiguration("lldb-dap") : vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
const config = vscode.workspace.workspaceFile
? vscode.workspace.getConfiguration("lldb-dap")
: vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);
const log_path = config.get<string>("log-path");
let env: { [key: string]: string } = {};
if (log_path) {
env["LLDBDAP_LOG"] = log_path;
} else if (
vscode.workspace.getConfiguration("lldb-dap").get("captureSessionLogs", false)
) {
env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION);
}
const configEnvironment =
config.get<{ [key: string]: string }>("environment") || {};
Expand All @@ -188,6 +199,11 @@ export async function createDebugAdapterExecutable(
};
const dbgArgs = await getDAPArguments(workspaceFolder, configuration);

logger.info(`lldb-dap path: ${dapPath}`);
logger.info(`lldb-dap args: ${dbgArgs}`);
logger.info(`cwd: ${dbgOptions.cwd}`);
logger.info(`env: ${JSON.stringify(dbgOptions.env)}`);

return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
}

Expand All @@ -198,25 +214,42 @@ export async function createDebugAdapterExecutable(
export class LLDBDapDescriptorFactory
implements vscode.DebugAdapterDescriptorFactory
{
constructor(
private readonly logger: vscode.LogOutputChannel,
private logFilePath: LogFilePathProvider,
) {}

async createDebugAdapterDescriptor(
session: vscode.DebugSession,
executable: vscode.DebugAdapterExecutable | undefined,
): Promise<vscode.DebugAdapterDescriptor | undefined> {
this.logger.info(`Creating debug adapter for session "${session.name}"`);
this.logger.info(
`Session "${session.name}" debug configuration:\n` +
JSON.stringify(session.configuration, undefined, 2),
);
if (executable) {
throw new Error(
const error = new Error(
"Setting the debug adapter executable in the package.json is not supported.",
);
this.logger.error(error);
throw error;
}

// Use a server connection if the debugAdapterPort is provided
if (session.configuration.debugAdapterPort) {
this.logger.info(
`Spawning debug adapter server on port ${session.configuration.debugAdapterPort}`,
);
return new vscode.DebugAdapterServer(
session.configuration.debugAdapterPort,
session.configuration.debugAdapterHostname,
);
}

return createDebugAdapterExecutable(
this.logger,
this.logFilePath,
session.workspaceFolder,
session.configuration,
);
Expand Down
22 changes: 21 additions & 1 deletion lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { LLDBDapServer } from "./lldb-dap-server";
import { createDebugAdapterExecutable } from "./debug-adapter-factory";
import { ConfigureButton, showErrorMessage } from "./ui/show-error-message";
import { ErrorWithNotification } from "./ui/error-with-notification";
import { LogFilePathProvider } from "./logging";

const exec = util.promisify(child_process.execFile);

Expand Down Expand Up @@ -71,13 +72,24 @@ const configurations: Record<string, DefaultConfig> = {
export class LLDBDapConfigurationProvider
implements vscode.DebugConfigurationProvider
{
constructor(private readonly server: LLDBDapServer) {}
constructor(
private readonly server: LLDBDapServer,
private readonly logger: vscode.LogOutputChannel,
private readonly logFilePath: LogFilePathProvider,
) {}

async resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,
token?: vscode.CancellationToken,
): Promise<vscode.DebugConfiguration> {
this.logger.info(
`Resolving debug configuration for "${debugConfiguration.name}"`,
);
this.logger.debug(
"Initial debug configuration:\n" +
JSON.stringify(debugConfiguration, undefined, 2),
);
let config = vscode.workspace.getConfiguration("lldb-dap");
for (const [key, cfg] of Object.entries(configurations)) {
if (Reflect.has(debugConfiguration, key)) {
Expand Down Expand Up @@ -152,6 +164,8 @@ export class LLDBDapConfigurationProvider
// Always try to create the debug adapter executable as this will show the user errors
// if there are any.
const executable = await createDebugAdapterExecutable(
this.logger,
this.logFilePath,
folder,
debugConfiguration,
);
Expand Down Expand Up @@ -184,8 +198,14 @@ export class LLDBDapConfigurationProvider
}
}

this.logger.info(
"Resolved debug configuration:\n" +
JSON.stringify(debugConfiguration, undefined, 2),
);

return debugConfiguration;
} catch (error) {
this.logger.error(error as Error);
// Show a better error message to the user if possible
if (!(error instanceof ErrorWithNotification)) {
throw error;
Expand Down
14 changes: 13 additions & 1 deletion lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as vscode from "vscode";
// prettier-ignore
interface EventMap {
"module": DebugProtocol.ModuleEvent;
"exited": DebugProtocol.ExitedEvent;
}

/** A type assertion to check if a ProtocolMessage is an event or if it is a specific event. */
Expand Down Expand Up @@ -47,7 +48,7 @@ export class DebugSessionTracker
onDidChangeModules: vscode.Event<vscode.DebugSession | undefined> =
this.modulesChanged.event;

constructor() {
constructor(private logger: vscode.LogOutputChannel) {
this.onDidChangeModules(this.moduleChangedListener, this);
vscode.debug.onDidChangeActiveDebugSession((session) =>
this.modulesChanged.fire(session),
Expand All @@ -62,8 +63,12 @@ export class DebugSessionTracker
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),
};
}
Expand Down Expand Up @@ -134,6 +139,13 @@ export class DebugSessionTracker
}
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}`,
);
}
}
}
26 changes: 20 additions & 6 deletions lldb/tools/lldb-dap/src-ts/extension.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as path from "path";
import * as vscode from "vscode";

import { LLDBDapDescriptorFactory } from "./debug-adapter-factory";
Expand All @@ -10,28 +11,35 @@ import {
ModulesDataProvider,
ModuleProperty,
} from "./ui/modules-data-provider";
import { LogFilePathProvider } from "./logging";

/**
* This class represents the extension and manages its life cycle. Other extensions
* using it as as library should use this class as the main entry point.
*/
export class LLDBDapExtension extends DisposableContext {
constructor() {
constructor(
logger: vscode.LogOutputChannel,
logFilePath: LogFilePathProvider,
outputChannel: vscode.OutputChannel,
) {
super();

const lldbDapServer = new LLDBDapServer();
const sessionTracker = new DebugSessionTracker();
const sessionTracker = new DebugSessionTracker(logger);

this.pushSubscription(
logger,
outputChannel,
lldbDapServer,
sessionTracker,
vscode.debug.registerDebugConfigurationProvider(
"lldb-dap",
new LLDBDapConfigurationProvider(lldbDapServer),
new LLDBDapConfigurationProvider(lldbDapServer, logger, logFilePath),
),
vscode.debug.registerDebugAdapterDescriptorFactory(
"lldb-dap",
new LLDBDapDescriptorFactory(),
new LLDBDapDescriptorFactory(logger, logFilePath),
),
vscode.debug.registerDebugAdapterTrackerFactory(
"lldb-dap",
Expand All @@ -54,6 +62,12 @@ export class LLDBDapExtension extends DisposableContext {
/**
* This is the entry point when initialized by VS Code.
*/
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(new LLDBDapExtension());
export async function activate(context: vscode.ExtensionContext) {
const outputChannel = vscode.window.createOutputChannel("LLDB-DAP", { log: true });
outputChannel.info("LLDB-DAP extension activating...");
const logFilePath = new LogFilePathProvider(context, outputChannel);
context.subscriptions.push(
new LLDBDapExtension(outputChannel, logFilePath, outputChannel),
);
outputChannel.info("LLDB-DAP extension activated");
}
2 changes: 1 addition & 1 deletion lldb/tools/lldb-dap/src-ts/lldb-dap-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class LLDBDapServer implements vscode.Disposable {
args: string[],
options?: child_process.SpawnOptionsWithoutStdio,
): Promise<{ host: string; port: number } | undefined> {
const dapArgs = [...args, "--connection", "listen://localhost:0" ];
const dapArgs = [...args, "--connection", "listen://localhost:0"];
if (!(await this.shouldContinueStartup(dapPath, dapArgs))) {
return undefined;
}
Expand Down
67 changes: 67 additions & 0 deletions lldb/tools/lldb-dap/src-ts/logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as path from "path";
import * as vscode from "vscode";

/**
* Formats the given date as a string in the form "YYYYMMddTHHMMSS".
*
* @param date The date to format as a string.
* @returns The formatted date.
*/
function formatDate(date: Date): string {
const year = date.getFullYear().toString().padStart(4, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hour = date.getHours().toString().padStart(2, "0");
const minute = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return `${year}${month}${day}T${hour}${minute}${seconds}`;
}

export enum LogType {
DEBUG_SESSION,
}

export class LogFilePathProvider {
private logFolder: string = "";

constructor(
private context: vscode.ExtensionContext,
private logger: vscode.LogOutputChannel,
) {
this.updateLogFolder();
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(e => {
if (
e.affectsConfiguration("lldb-dap.logFolder")
) {
this.updateLogFolder();
}
})
);
}

get(type: LogType): string {
const logFolder = this.logFolder || this.context.logUri.fsPath;
switch(type) {
case LogType.DEBUG_SESSION:
return path.join(logFolder, `lldb-dap-session-${formatDate(new Date())}.log`);
break;
}
}

private updateLogFolder() {
const config = vscode.workspace.getConfiguration("lldb-dap");
let logFolder =
config.get<string>("logFolder") || this.context.logUri.fsPath;
vscode.workspace.fs
.createDirectory(vscode.Uri.file(logFolder))
.then(undefined, (error) => {
this.logger.error(`Failed to create log folder ${logFolder}: ${error}`);
logFolder = this.context.logUri.fsPath;
})
.then(() => {
this.logFolder = logFolder;
this.logger.info(`Persisting lldb-dap logs to ${logFolder}`);
});
}
}
Loading