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

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Fri Feb 28 10:44:14 PST 2025


https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/128957

>From e95459a73ad5a1448841ed6c2422f66b23f6b486 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 26 Feb 2025 14:56:10 -0800
Subject: [PATCH 1/4] [lldb-dap] Adding server mode support to lldb-dap VSCode
 extension.

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.
---
 lldb/tools/lldb-dap/package.json              |  8 +-
 .../lldb-dap/src-ts/debug-adapter-factory.ts  | 80 +++++++++++++------
 lldb/tools/lldb-dap/src-ts/extension.ts       |  9 +--
 3 files changed, 68 insertions(+), 29 deletions(-)

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 31d808eda4c35..51f392e386b22 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",
+          "description": "Run lldb-dap in server mode. When enabled, lldb-dap will start a background server that will be reused between debug sessions. This can improve launch performance and caching of debug symbols between debug sessions.",
+          "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..89a76d7e6f40c 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,30 +142,45 @@ 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').on('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 openSettingsAction = "Open Settings";
     const callbackValue = await vscode.window.showErrorMessage(
       `Debug adapter path: ${path} is not a valid file`,
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")) {

>From e5f1861512d8e3fbd1c6cab342de35f245af0604 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 26 Feb 2025 15:07:42 -0800
Subject: [PATCH 2/4] Ensuring the error message is correct when path is
 undefined

---
 lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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 89a76d7e6f40c..a5f1a5b07b001 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -183,7 +183,8 @@ export class LLDBDapDescriptorFactory
   static async showLLDBDapNotFoundMessage(path?: string) {
     const openSettingsAction = "Open Settings";
     const callbackValue = await vscode.window.showErrorMessage(
-      `Debug adapter path: ${path} is not a valid file`,
+      path ? `Debug adapter path: ${path} is not a valid file` :
+        `Debug adapter executable not found`,
       openSettingsAction,
     );
 

>From 9cca26e45d6a094dbf7b84392d7dfea1f1bea307 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 27 Feb 2025 16:09:47 -0800
Subject: [PATCH 3/4] Updating the 'lldb-dap.serverMode' setting description
 and tweaking the handler to collect the connection parameter to only run
 once.

---
 lldb/tools/lldb-dap/package.json                    | 2 +-
 lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 51f392e386b22..cd450a614b3f7 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -92,7 +92,7 @@
         "lldb-dap.serverMode": {
           "scope": "resource",
           "type": "boolean",
-          "description": "Run lldb-dap in server mode. When enabled, lldb-dap will start a background server that will be reused between debug sessions. This can improve launch performance and caching of debug symbols between debug sessions.",
+          "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
         }
       }
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 a5f1a5b07b001..429bba55fa435 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -162,7 +162,7 @@ export class LLDBDapDescriptorFactory
         'connect://localhost:0'
       );
       const server = child_process.spawn(dapPath, args, options);
-      server.stdout!.setEncoding('utf8').on('data', (data: string) => {
+      server.stdout!.setEncoding('utf8').once('data', (data: string) => {
         const connection = /connection:\/\/\[([^\]]+)\]:(\d+)/.exec(data);
         if (connection) {
           const host = connection[1];

>From 88095b94b06b4e714724a022d437fcb4d19478e7 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 28 Feb 2025 10:43:59 -0800
Subject: [PATCH 4/4] Improving error message wording.

---
 lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

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 429bba55fa435..1f76fe31b00ad 100644
--- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
+++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts
@@ -181,10 +181,13 @@ export class LLDBDapDescriptorFactory
    * Shows a message box when the debug adapter's path is not found
    */
   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(
-      path ? `Debug adapter path: ${path} is not a valid file` :
-        `Debug adapter executable not found`,
+      message,
       openSettingsAction,
     );
 



More information about the lldb-commits mailing list