Skip to content

Commit a3ac1f2

Browse files
authored
[lldb-dap] Adding server mode support to lldb-dap VSCode extension. (#128957)
This adds support for launching lldb-dap in server mode. The extension will start lldb-dap in server mode on-demand and retain the server until the VSCode window is closed (when the extension context is disposed). While running in server mode, launch performance for binaries is greatly improved by improving caching between debug sessions. For example, on my local M1 Max laptop it takes ~5s to attach for the first attach to an iOS Simulator process and ~0.5s to attach each time after the first.
1 parent af2dd15 commit a3ac1f2

File tree

3 files changed

+73
-30
lines changed

3 files changed

+73
-30
lines changed

lldb/tools/lldb-dap/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@
8888
"additionalProperties": {
8989
"type": "string"
9090
}
91+
},
92+
"lldb-dap.serverMode": {
93+
"scope": "resource",
94+
"type": "boolean",
95+
"markdownDescription": "Run lldb-dap in server mode.\n\nWhen enabled, lldb-dap will start a background server that will be reused between debug sessions. This allows caching of debug symbols between sessions and improves launch performance.",
96+
"default": false
9197
}
9298
}
9399
},
@@ -543,4 +549,4 @@
543549
}
544550
]
545551
}
546-
}
552+
}

lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as vscode from "vscode";
44
import * as child_process from "child_process";
55
import * as fs from "node:fs/promises";
66

7+
const exec = util.promisify(child_process.execFile);
8+
79
export async function isExecutable(path: string): Promise<Boolean> {
810
try {
911
await fs.access(path, fs.constants.X_OK);
@@ -16,15 +18,14 @@ export async function isExecutable(path: string): Promise<Boolean> {
1618
async function findWithXcrun(executable: string): Promise<string | undefined> {
1719
if (process.platform === "darwin") {
1820
try {
19-
const exec = util.promisify(child_process.execFile);
2021
let { stdout, stderr } = await exec("/usr/bin/xcrun", [
2122
"-find",
2223
executable,
2324
]);
2425
if (stdout) {
2526
return stdout.toString().trimEnd();
2627
}
27-
} catch (error) {}
28+
} catch (error) { }
2829
}
2930
return undefined;
3031
}
@@ -97,8 +98,15 @@ async function getDAPExecutable(
9798
* depending on the session configuration.
9899
*/
99100
export class LLDBDapDescriptorFactory
100-
implements vscode.DebugAdapterDescriptorFactory
101-
{
101+
implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable {
102+
private server?: Promise<{ process: child_process.ChildProcess, host: string, port: number }>;
103+
104+
dispose() {
105+
this.server?.then(({ process }) => {
106+
process.kill();
107+
});
108+
}
109+
102110
async createDebugAdapterDescriptor(
103111
session: vscode.DebugSession,
104112
executable: vscode.DebugAdapterExecutable | undefined,
@@ -115,41 +123,71 @@ export class LLDBDapDescriptorFactory
115123
}
116124
const configEnvironment =
117125
config.get<{ [key: string]: string }>("environment") || {};
118-
const dapPath = await getDAPExecutable(session);
126+
const dapPath = (await getDAPExecutable(session)) ?? executable?.command;
127+
128+
if (!dapPath) {
129+
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
130+
return undefined;
131+
}
132+
133+
if (!(await isExecutable(dapPath))) {
134+
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
135+
return;
136+
}
137+
119138
const dbgOptions = {
120139
env: {
121140
...executable?.options?.env,
122141
...configEnvironment,
123142
...env,
124143
},
125144
};
126-
if (dapPath) {
127-
if (!(await isExecutable(dapPath))) {
128-
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
129-
return undefined;
130-
}
131-
return new vscode.DebugAdapterExecutable(dapPath, [], dbgOptions);
132-
} else if (executable) {
133-
if (!(await isExecutable(executable.command))) {
134-
LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(executable.command);
135-
return undefined;
136-
}
137-
return new vscode.DebugAdapterExecutable(
138-
executable.command,
139-
executable.args,
140-
dbgOptions,
141-
);
145+
const dbgArgs = executable?.args ?? [];
146+
147+
const serverMode = config.get<boolean>('serverMode', false);
148+
if (serverMode) {
149+
const { host, port } = await this.startServer(dapPath, dbgArgs, dbgOptions);
150+
return new vscode.DebugAdapterServer(port, host);
142151
}
143-
return undefined;
152+
153+
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
154+
}
155+
156+
startServer(dapPath: string, args: string[], options: child_process.CommonSpawnOptions): Promise<{ host: string, port: number }> {
157+
if (this.server) return this.server;
158+
159+
this.server = new Promise(resolve => {
160+
args.push(
161+
'--connection',
162+
'connect://localhost:0'
163+
);
164+
const server = child_process.spawn(dapPath, args, options);
165+
server.stdout!.setEncoding('utf8').once('data', (data: string) => {
166+
const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
167+
if (connection) {
168+
const host = connection[1];
169+
const port = Number(connection[2]);
170+
resolve({ process: server, host, port });
171+
}
172+
});
173+
server.on('exit', () => {
174+
this.server = undefined;
175+
})
176+
});
177+
return this.server;
144178
}
145179

146180
/**
147181
* Shows a message box when the debug adapter's path is not found
148182
*/
149-
static async showLLDBDapNotFoundMessage(path: string) {
183+
static async showLLDBDapNotFoundMessage(path?: string) {
184+
const message =
185+
path
186+
? `Debug adapter path: ${path} is not a valid file.`
187+
: "Unable to find the path to the LLDB debug adapter executable.";
150188
const openSettingsAction = "Open Settings";
151189
const callbackValue = await vscode.window.showErrorMessage(
152-
`Debug adapter path: ${path} is not a valid file`,
190+
message,
153191
openSettingsAction,
154192
);
155193

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import * as path from "path";
2-
import * as util from "util";
31
import * as vscode from "vscode";
42

53
import {
@@ -15,13 +13,14 @@ import { DisposableContext } from "./disposable-context";
1513
export class LLDBDapExtension extends DisposableContext {
1614
constructor() {
1715
super();
16+
const factory = new LLDBDapDescriptorFactory();
17+
this.pushSubscription(factory);
1818
this.pushSubscription(
1919
vscode.debug.registerDebugAdapterDescriptorFactory(
2020
"lldb-dap",
21-
new LLDBDapDescriptorFactory(),
22-
),
21+
factory,
22+
)
2323
);
24-
2524
this.pushSubscription(
2625
vscode.workspace.onDidChangeConfiguration(async (event) => {
2726
if (event.affectsConfiguration("lldb-dap.executable-path")) {

0 commit comments

Comments
 (0)