[Lldb-commits] [lldb] a3ac1f2 - [lldb-dap] Adding server mode support to lldb-dap VSCode extension. (#128957)

via lldb-commits lldb-commits at lists.llvm.org
Fri Feb 28 10:49:27 PST 2025


Author: John Harrison
Date: 2025-02-28T10:49:24-08:00
New Revision: a3ac1f2278dec155e0e0b4d06ec816ba325f6979

URL: https://github.com/llvm/llvm-project/commit/a3ac1f2278dec155e0e0b4d06ec816ba325f6979
DIFF: https://github.com/llvm/llvm-project/commit/a3ac1f2278dec155e0e0b4d06ec816ba325f6979.diff

LOG: [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.

Added: 
    

Modified: 
    lldb/tools/lldb-dap/package.json
    lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
    lldb/tools/lldb-dap/src-ts/extension.ts

Removed: 
    


################################################################################
diff  --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 31d808eda4c35..cd450a614b3f7 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -88,6 +88,12 @@
           "additionalProperties": {
             "type": "string"
           }
+        },
+        "lldb-dap.serverMode": {
+          "scope": "resource",
+          "type": "boolean",
+          "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.",
+          "default": false
         }
       }
     },
@@ -543,4 +549,4 @@
       }
     ]
   }
-}
+}
\ No newline at end of file

diff  --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
index 36107336ebc4d..1f76fe31b00ad 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -4,6 +4,8 @@ import * as vscode from "vscode";
 import * as child_process from "child_process";
 import * as fs from "node:fs/promises";
 
+const exec = util.promisify(child_process.execFile);
+
 export async function isExecutable(path: string): Promise<Boolean> {
   try {
     await fs.access(path, fs.constants.X_OK);
@@ -16,7 +18,6 @@ export async function isExecutable(path: string): Promise<Boolean> {
 async function findWithXcrun(executable: string): Promise<string | undefined> {
   if (process.platform === "darwin") {
     try {
-      const exec = util.promisify(child_process.execFile);
       let { stdout, stderr } = await exec("/usr/bin/xcrun", [
         "-find",
         executable,
@@ -24,7 +25,7 @@ async function findWithXcrun(executable: string): Promise<string | undefined> {
       if (stdout) {
         return stdout.toString().trimEnd();
       }
-    } catch (error) {}
+    } catch (error) { }
   }
   return undefined;
 }
@@ -97,8 +98,15 @@ async function getDAPExecutable(
  * depending on the session configuration.
  */
 export class LLDBDapDescriptorFactory
-  implements vscode.DebugAdapterDescriptorFactory
-{
+  implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable {
+  private server?: Promise<{ process: child_process.ChildProcess, host: string, port: number }>;
+
+  dispose() {
+    this.server?.then(({ process }) => {
+      process.kill();
+    });
+  }
+
   async createDebugAdapterDescriptor(
     session: vscode.DebugSession,
     executable: vscode.DebugAdapterExecutable | undefined,
@@ -115,7 +123,18 @@ export class LLDBDapDescriptorFactory
     }
     const configEnvironment =
       config.get<{ [key: string]: string }>("environment") || {};
-    const dapPath = await getDAPExecutable(session);
+    const dapPath = (await getDAPExecutable(session)) ?? executable?.command;
+
+    if (!dapPath) {
+      LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage();
+      return undefined;
+    }
+
+    if (!(await isExecutable(dapPath))) {
+      LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
+      return;
+    }
+
     const dbgOptions = {
       env: {
         ...executable?.options?.env,
@@ -123,33 +142,52 @@ export class LLDBDapDescriptorFactory
         ...env,
       },
     };
-    if (dapPath) {
-      if (!(await isExecutable(dapPath))) {
-        LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(dapPath);
-        return undefined;
-      }
-      return new vscode.DebugAdapterExecutable(dapPath, [], dbgOptions);
-    } else if (executable) {
-      if (!(await isExecutable(executable.command))) {
-        LLDBDapDescriptorFactory.showLLDBDapNotFoundMessage(executable.command);
-        return undefined;
-      }
-      return new vscode.DebugAdapterExecutable(
-        executable.command,
-        executable.args,
-        dbgOptions,
-      );
+    const dbgArgs = executable?.args ?? [];
+
+    const serverMode = config.get<boolean>('serverMode', false);
+    if (serverMode) {
+      const { host, port } = await this.startServer(dapPath, dbgArgs, dbgOptions);
+      return new vscode.DebugAdapterServer(port, host);
     }
-    return undefined;
+
+    return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
+  }
+
+  startServer(dapPath: string, args: string[], options: child_process.CommonSpawnOptions): Promise<{ host: string, port: number }> {
+    if (this.server) return this.server;
+
+    this.server = new Promise(resolve => {
+      args.push(
+        '--connection',
+        'connect://localhost:0'
+      );
+      const server = child_process.spawn(dapPath, args, options);
+      server.stdout!.setEncoding('utf8').once('data', (data: string) => {
+        const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
+        if (connection) {
+          const host = connection[1];
+          const port = Number(connection[2]);
+          resolve({ process: server, host, port });
+        }
+      });
+      server.on('exit', () => {
+        this.server = undefined;
+      })
+    });
+    return this.server;
   }
 
   /**
    * Shows a message box when the debug adapter's path is not found
    */
-  static async showLLDBDapNotFoundMessage(path: string) {
+  static async showLLDBDapNotFoundMessage(path?: string) {
+    const message =
+      path
+        ? `Debug adapter path: ${path} is not a valid file.`
+        : "Unable to find the path to the LLDB debug adapter executable.";
     const openSettingsAction = "Open Settings";
     const callbackValue = await vscode.window.showErrorMessage(
-      `Debug adapter path: ${path} is not a valid file`,
+      message,
       openSettingsAction,
     );
 

diff  --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts
index 71fd48298f8f5..a07bcdebcb68b 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -1,5 +1,3 @@
-import * as path from "path";
-import * as util from "util";
 import * as vscode from "vscode";
 
 import {
@@ -15,13 +13,14 @@ import { DisposableContext } from "./disposable-context";
 export class LLDBDapExtension extends DisposableContext {
   constructor() {
     super();
+    const factory = new LLDBDapDescriptorFactory();
+    this.pushSubscription(factory);
     this.pushSubscription(
       vscode.debug.registerDebugAdapterDescriptorFactory(
         "lldb-dap",
-        new LLDBDapDescriptorFactory(),
-      ),
+        factory,
+      )
     );
-
     this.pushSubscription(
       vscode.workspace.onDidChangeConfiguration(async (event) => {
         if (event.affectsConfiguration("lldb-dap.executable-path")) {


        


More information about the lldb-commits mailing list