[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
Wed Feb 26 15:03:48 PST 2025


https://github.com/ashgti created https://github.com/llvm/llvm-project/pull/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.

>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] [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")) {



More information about the lldb-commits mailing list