https://github.com/matthewbastien updated 
https://github.com/llvm/llvm-project/pull/128943

>From b9083ea16c7b1dba70cc04acf78f5001f0fb86c6 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Wed, 26 Feb 2025 11:18:21 -0500
Subject: [PATCH 01/11] add a process picker for attaching by PID

---
 lldb/tools/lldb-dap/package.json              |  30 +++++-
 .../lldb-dap/src-ts/commands/pick-process.ts  |  37 +++++++
 lldb/tools/lldb-dap/src-ts/extension.ts       |   7 +-
 .../src-ts/process-tree/base-process-tree.ts  | 102 ++++++++++++++++++
 .../lldb-dap/src-ts/process-tree/index.ts     |  36 +++++++
 .../platforms/darwin-process-tree.ts          |  16 +++
 .../platforms/linux-process-tree.ts           |  38 +++++++
 .../platforms/windows-process-tree.ts         |  52 +++++++++
 8 files changed, 315 insertions(+), 3 deletions(-)
 create mode 100644 lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
 create mode 100644 lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts
 create mode 100644 lldb/tools/lldb-dap/src-ts/process-tree/index.ts
 create mode 100644 
lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
 create mode 100644 
lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
 create mode 100644 
lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts

diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 31d808eda4c35..1bbdbf045dd1b 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -146,6 +146,9 @@
         "windows": {
           "program": "./bin/lldb-dap.exe"
         },
+        "variables": {
+          "PickProcess": "lldb-dap.pickProcess"
+        },
         "configurationAttributes": {
           "launch": {
             "required": [
@@ -517,6 +520,16 @@
               "cwd": "^\"\\${workspaceRoot}\""
             }
           },
+          {
+            "label": "LLDB: Attach to Process",
+            "description": "",
+            "body": {
+              "type": "lldb-dap",
+              "request": "attach",
+              "name": "${1:Attach}",
+              "pid": "^\"\\${command:PickProcess}\""
+            }
+          },
           {
             "label": "LLDB: Attach",
             "description": "",
@@ -541,6 +554,21 @@
           }
         ]
       }
-    ]
+    ],
+    "commands": [
+      {
+        "command": "lldb-dap.pickProcess",
+        "title": "Pick Process",
+        "category": "LLDB DAP"
+      }
+    ],
+    "menus": {
+      "commandPalette": [
+        {
+          "command": "lldb-dap.pickProcess",
+          "when": "false"
+        }
+      ]
+    }
   }
 }
diff --git a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts 
b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
new file mode 100644
index 0000000000000..b83e749e7da7b
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
@@ -0,0 +1,37 @@
+import * as path from "path";
+import * as vscode from "vscode";
+import { createProcessTree } from "../process-tree";
+
+interface ProcessQuickPick extends vscode.QuickPickItem {
+  processId: number;
+}
+
+/**
+ * Prompts the user to select a running process.
+ *
+ * @returns The pid of the process as a string or undefined if cancelled.
+ */
+export async function pickProcess(): Promise<string | undefined> {
+  const processTree = createProcessTree();
+  const selectedProcess = await vscode.window.showQuickPick<ProcessQuickPick>(
+    processTree.listAllProcesses().then((processes): ProcessQuickPick[] => {
+      return processes
+        .sort((a, b) => b.start - a.start) // Sort by start date in descending 
order
+        .map((proc) => {
+          return {
+            processId: proc.id,
+            label: path.basename(proc.command),
+            description: proc.id.toString(),
+            detail: proc.arguments,
+          } satisfies ProcessQuickPick;
+        });
+    }),
+    {
+      placeHolder: "Select a process to attach the debugger to",
+    },
+  );
+  if (!selectedProcess) {
+    return;
+  }
+  return selectedProcess.processId.toString();
+}
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts 
b/lldb/tools/lldb-dap/src-ts/extension.ts
index 71fd48298f8f5..3532a2143155b 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -1,7 +1,6 @@
-import * as path from "path";
-import * as util from "util";
 import * as vscode from "vscode";
 
+import { pickProcess } from "./commands/pick-process";
 import {
   LLDBDapDescriptorFactory,
   isExecutable,
@@ -38,6 +37,10 @@ export class LLDBDapExtension extends DisposableContext {
         }
       }),
     );
+
+    this.pushSubscription(
+      vscode.commands.registerCommand("lldb-dap.pickProcess", pickProcess),
+    );
   }
 }
 
diff --git a/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts
new file mode 100644
index 0000000000000..3c08f49035b35
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts
@@ -0,0 +1,102 @@
+import { ChildProcessWithoutNullStreams } from "child_process";
+import { Process, ProcessTree } from ".";
+import { Transform } from "stream";
+
+/** Parses process information from a given line of process output. */
+export type ProcessTreeParser = (line: string) => Process | undefined;
+
+/**
+ * Implements common behavior between the different {@link ProcessTree} 
implementations.
+ */
+export abstract class BaseProcessTree implements ProcessTree {
+  /**
+   * Spawn the process responsible for collecting all processes on the system.
+   */
+  protected abstract spawnProcess(): ChildProcessWithoutNullStreams;
+
+  /**
+   * Create a new parser that can read the process information from stdout of 
the process
+   * spawned by {@link spawnProcess spawnProcess()}.
+   */
+  protected abstract createParser(): ProcessTreeParser;
+
+  listAllProcesses(): Promise<Process[]> {
+    return new Promise<Process[]>((resolve, reject) => {
+      const proc = this.spawnProcess();
+      const parser = this.createParser();
+
+      // Capture processes from stdout
+      const processes: Process[] = [];
+      proc.stdout.pipe(new LineBasedStream()).on("data", (line) => {
+        const process = parser(line.toString());
+        if (process && process.id !== proc.pid) {
+          processes.push(process);
+        }
+      });
+
+      // Resolve or reject the promise based on exit code/signal/error
+      proc.on("error", reject);
+      proc.on("exit", (code, signal) => {
+        if (code === 0) {
+          resolve(processes);
+        } else if (signal) {
+          reject(
+            new Error(
+              `Unable to list processes: process exited due to signal 
${signal}`,
+            ),
+          );
+        } else {
+          reject(
+            new Error(
+              `Unable to list processes: process exited with code ${code}`,
+            ),
+          );
+        }
+      });
+    });
+  }
+}
+
+/**
+ * A stream that emits each line as a single chunk of data. The end of a line 
is denoted
+ * by the newline character '\n'.
+ */
+export class LineBasedStream extends Transform {
+  private readonly newline: number = "\n".charCodeAt(0);
+  private buffer: Buffer = Buffer.alloc(0);
+
+  override _transform(
+    chunk: Buffer,
+    _encoding: string,
+    callback: () => void,
+  ): void {
+    let currentIndex = 0;
+    while (currentIndex < chunk.length) {
+      const newlineIndex = chunk.indexOf(this.newline, currentIndex);
+      if (newlineIndex === -1) {
+        this.buffer = Buffer.concat([
+          this.buffer,
+          chunk.subarray(currentIndex),
+        ]);
+        break;
+      }
+
+      const newlineChunk = chunk.subarray(currentIndex, newlineIndex);
+      const line = Buffer.concat([this.buffer, newlineChunk]);
+      this.push(line);
+      this.buffer = Buffer.alloc(0);
+
+      currentIndex = newlineIndex + 1;
+    }
+
+    callback();
+  }
+
+  override _flush(callback: () => void): void {
+    if (this.buffer.length) {
+      this.push(this.buffer);
+    }
+
+    callback();
+  }
+}
diff --git a/lldb/tools/lldb-dap/src-ts/process-tree/index.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/index.ts
new file mode 100644
index 0000000000000..9c46bc92d8548
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/index.ts
@@ -0,0 +1,36 @@
+import { DarwinProcessTree } from "./platforms/darwin-process-tree";
+import { LinuxProcessTree } from "./platforms/linux-process-tree";
+import { WindowsProcessTree } from "./platforms/windows-process-tree";
+
+/**
+ * Represents a single process running on the system.
+ */
+export interface Process {
+  /** Process ID */
+  id: number;
+
+  /** Command that was used to start the process */
+  command: string;
+
+  /** The full command including arguments that was used to start the process 
*/
+  arguments: string;
+
+  /** The date when the process was started */
+  start: number;
+}
+
+export interface ProcessTree {
+  listAllProcesses(): Promise<Process[]>;
+}
+
+/** Returns a {@link ProcessTree} based on the current platform. */
+export function createProcessTree(): ProcessTree {
+  switch (process.platform) {
+    case "darwin":
+      return new DarwinProcessTree();
+    case "win32":
+      return new WindowsProcessTree();
+    default:
+      return new LinuxProcessTree();
+  }
+}
diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
new file mode 100644
index 0000000000000..954644288869e
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
@@ -0,0 +1,16 @@
+import { ChildProcessWithoutNullStreams, spawn } from "child_process";
+import { LinuxProcessTree } from "./linux-process-tree";
+
+function fill(prefix: string, suffix: string, length: number): string {
+  return prefix + suffix.repeat(length - prefix.length);
+}
+
+export class DarwinProcessTree extends LinuxProcessTree {
+  protected override spawnProcess(): ChildProcessWithoutNullStreams {
+    return spawn("ps", [
+      "-xo",
+      // The length of comm must be large enough or data will be truncated.
+      `pid=PID,lstart=START,comm=${fill("COMMAND", "-", 
256)},command=ARGUMENTS`,
+    ]);
+  }
+}
diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
new file mode 100644
index 0000000000000..65733f6c547b3
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
@@ -0,0 +1,38 @@
+import { ChildProcessWithoutNullStreams, spawn } from "child_process";
+import { BaseProcessTree, ProcessTreeParser } from "../base-process-tree";
+
+export class LinuxProcessTree extends BaseProcessTree {
+  protected override spawnProcess(): ChildProcessWithoutNullStreams {
+    return spawn(
+      "ps",
+      ["-axo", `pid=PID,lstart=START,comm:128=COMMAND,command=ARGUMENTS`],
+      {
+        stdio: "pipe",
+      },
+    );
+  }
+
+  protected override createParser(): ProcessTreeParser {
+    let commandOffset: number | undefined;
+    let argumentsOffset: number | undefined;
+    return (line) => {
+      if (!commandOffset || !argumentsOffset) {
+        commandOffset = line.indexOf("COMMAND");
+        argumentsOffset = line.indexOf("ARGUMENTS");
+        return;
+      }
+
+      const pid = /^\s*([0-9]+)\s*/.exec(line);
+      if (!pid) {
+        return;
+      }
+
+      return {
+        id: Number(pid[1]),
+        command: line.slice(commandOffset, argumentsOffset).trim(),
+        arguments: line.slice(argumentsOffset).trim(),
+        start: Date.parse(line.slice(pid[0].length, commandOffset).trim()),
+      };
+    };
+  }
+}
diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts
new file mode 100644
index 0000000000000..9cfbfa29ab5d3
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts
@@ -0,0 +1,52 @@
+import * as path from "path";
+import { BaseProcessTree, ProcessTreeParser } from "../base-process-tree";
+import { ChildProcessWithoutNullStreams, spawn } from "child_process";
+
+export class WindowsProcessTree extends BaseProcessTree {
+  protected override spawnProcess(): ChildProcessWithoutNullStreams {
+    const wmic = path.join(
+      process.env["WINDIR"] || "C:\\Windows",
+      "System32",
+      "wbem",
+      "WMIC.exe",
+    );
+    return spawn(
+      wmic,
+      ["process", "get", "CommandLine,CreationDate,ProcessId"],
+      { stdio: "pipe" },
+    );
+  }
+
+  protected override createParser(): ProcessTreeParser {
+    const lineRegex = /^(.*)\s+([0-9]+)\.[0-9]+[+-][0-9]+\s+([0-9]+)$/;
+
+    return (line) => {
+      const matches = lineRegex.exec(line.trim());
+      if (!matches || matches.length !== 4) {
+        return;
+      }
+
+      const id = Number(matches[3]);
+      const start = Number(matches[2]);
+      let fullCommandLine = matches[1].trim();
+      if (isNaN(id) || !fullCommandLine) {
+        return;
+      }
+      // Extract the command from the full command line
+      let command = fullCommandLine;
+      if (fullCommandLine[0] === '"') {
+        const end = fullCommandLine.indexOf('"', 1);
+        if (end > 0) {
+          command = fullCommandLine.slice(1, end - 1);
+        }
+      } else {
+        const end = fullCommandLine.indexOf(" ");
+        if (end > 0) {
+          command = fullCommandLine.slice(0, end);
+        }
+      }
+
+      return { id, command, arguments: fullCommandLine, start };
+    };
+  }
+}

>From b4238421d732437fd01d3ee8658f72e2a3805232 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Wed, 26 Feb 2025 15:16:05 -0500
Subject: [PATCH 02/11] convert pid to a number so that lldb-dap can properly
 consume it

---
 .../lldb-dap/src-ts/commands/pick-process.ts  |  4 ++
 .../src-ts/debug-configuration-provider.ts    | 54 +++++++++++++++++++
 lldb/tools/lldb-dap/src-ts/extension.ts       |  7 +++
 3 files changed, 65 insertions(+)
 create mode 100644 lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts

diff --git a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts 
b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
index b83e749e7da7b..355d508075080 100644
--- a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
+++ b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
@@ -9,6 +9,10 @@ interface ProcessQuickPick extends vscode.QuickPickItem {
 /**
  * Prompts the user to select a running process.
  *
+ * The return value must be a string so that it is compatible with VS Code's
+ * string substitution infrastructure. The value will eventually be converted
+ * to a number by the debug configuration provider.
+ *
  * @returns The pid of the process as a string or undefined if cancelled.
  */
 export async function pickProcess(): Promise<string | undefined> {
diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts 
b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
new file mode 100644
index 0000000000000..02b8bd5aa8147
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
@@ -0,0 +1,54 @@
+import * as vscode from "vscode";
+
+/**
+ * Converts the given value to an integer if it isn't already.
+ *
+ * If the value cannot be converted then this function will return undefined.
+ *
+ * @param value the value to to be converted
+ * @returns the integer value or undefined if unable to convert
+ */
+function convertToInteger(value: any): number | undefined {
+  let result: number | undefined;
+  switch (typeof value) {
+    case "number":
+      result = value;
+      break;
+    case "string":
+      result = Number(value);
+      break;
+    default:
+      return undefined;
+  }
+  if (!Number.isInteger(result)) {
+    return undefined;
+  }
+  return result;
+}
+
+/**
+ * A {@link vscode.DebugConfigurationProvider} used to resolve LLDB DAP debug 
configurations.
+ *
+ * Performs checks on the debug configuration before launching a debug session.
+ */
+export class LLDBDapConfigurationProvider
+  implements vscode.DebugConfigurationProvider
+{
+  resolveDebugConfigurationWithSubstitutedVariables(
+    _folder: vscode.WorkspaceFolder | undefined,
+    debugConfiguration: vscode.DebugConfiguration,
+  ): vscode.ProviderResult<vscode.DebugConfiguration> {
+    // Convert the "pid" option to a number if it is a string
+    if ("pid" in debugConfiguration) {
+      const pid = convertToInteger(debugConfiguration.pid);
+      if (pid === undefined) {
+        vscode.window.showErrorMessage(
+          "Invalid debug configuration: property 'pid' must either be an 
integer or a string containing an integer value.",
+        );
+        return null;
+      }
+      debugConfiguration.pid = pid;
+    }
+    return debugConfiguration;
+  }
+}
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts 
b/lldb/tools/lldb-dap/src-ts/extension.ts
index 3532a2143155b..74815022d468a 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -6,6 +6,7 @@ import {
   isExecutable,
 } from "./debug-adapter-factory";
 import { DisposableContext } from "./disposable-context";
+import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
 
 /**
  * This class represents the extension and manages its life cycle. Other 
extensions
@@ -14,6 +15,12 @@ import { DisposableContext } from "./disposable-context";
 export class LLDBDapExtension extends DisposableContext {
   constructor() {
     super();
+    this.pushSubscription(
+      vscode.debug.registerDebugConfigurationProvider(
+        "lldb-dap",
+        new LLDBDapConfigurationProvider(),
+      ),
+    );
     this.pushSubscription(
       vscode.debug.registerDebugAdapterDescriptorFactory(
         "lldb-dap",

>From ee7b00ee773996736e9a79c91b1fb447bc365707 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Wed, 26 Feb 2025 15:49:58 -0500
Subject: [PATCH 03/11] update extension README

---
 lldb/tools/lldb-dap/README.md | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index 123869a033724..7244a0aa18d73 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -65,6 +65,19 @@ This will attach to a process `a.out` whose process ID is 
123:
 }
 ```
 
+You can also use the variable substituion `${command:PickProcess}` to select a
+process at the start of the debug session instead of setting the pid manually:
+
+```javascript
+{
+  "type": "lldb-dap",
+  "request": "attach",
+  "name": "Attach to PID",
+  "program": "/tmp/a.out",
+  "pid": "${command:PickProcess}"
+}
+```
+
 #### Attach by Name
 
 This will attach to an existing process whose base
@@ -224,7 +237,7 @@ the following `lldb-dap` specific key/value pairs:
 | Parameter                         | Type        | Req |         |
 |-----------------------------------|-------------|:---:|---------|
 | **program**                       | string      |     | Path to the 
executable to attach to. This value is optional but can help to resolve 
breakpoints prior the attaching to the program.
-| **pid**                           | number      |     | The process id of 
the process you wish to attach to. If **pid** is omitted, the debugger will 
attempt to attach to the program by finding a process whose file name matches 
the file name from **porgram**. Setting this value to 
`${command:pickMyProcess}` will allow interactive process selection in the IDE.
+| **pid**                           | number      |     | The process id of 
the process you wish to attach to. If **pid** is omitted, the debugger will 
attempt to attach to the program by finding a process whose file name matches 
the file name from **program**. Setting this value to `${command:PickProcess}` 
will allow interactive process selection in the IDE.
 | **waitFor**                       | boolean     |     | Wait for the process 
to launch.
 | **attachCommands**                | [string]    |     | LLDB commands that 
will be executed after **preRunCommands** which take place of the code that 
normally does the attach. The commands can create a new target and attach or 
launch it however desired. This allows custom launch and attach configurations. 
Core files can use `target create --core /path/to/core` to attach to core files.
 

>From 82ef750cbfc374c5314ad1a503a6084a1c976edb Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Wed, 26 Feb 2025 16:52:00 -0500
Subject: [PATCH 04/11] allow matching on the full command line arguments in
 the process picker

---
 lldb/tools/lldb-dap/src-ts/commands/pick-process.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts 
b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
index 355d508075080..2f02a78aae192 100644
--- a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
+++ b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
@@ -32,6 +32,7 @@ export async function pickProcess(): Promise<string | 
undefined> {
     }),
     {
       placeHolder: "Select a process to attach the debugger to",
+      matchOnDetail: true,
     },
   );
   if (!selectedProcess) {

>From f4407b20dca35a164d2a515608dd93b86e8bdf85 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Fri, 28 Feb 2025 14:13:50 -0500
Subject: [PATCH 05/11] use execFile() instead of spawn() to simplify logic

---
 .../src-ts/process-tree/base-process-tree.ts  | 101 ++++--------------
 .../platforms/darwin-process-tree.ts          |   7 +-
 .../platforms/linux-process-tree.ts           |  15 ++-
 .../platforms/windows-process-tree.ts         |  14 ++-
 4 files changed, 35 insertions(+), 102 deletions(-)

diff --git a/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts
index 3c08f49035b35..1885dc07a47a8 100644
--- a/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/base-process-tree.ts
@@ -1,6 +1,8 @@
-import { ChildProcessWithoutNullStreams } from "child_process";
+import * as util from "util";
+import * as child_process from "child_process";
 import { Process, ProcessTree } from ".";
-import { Transform } from "stream";
+
+const exec = util.promisify(child_process.execFile);
 
 /** Parses process information from a given line of process output. */
 export type ProcessTreeParser = (line: string) => Process | undefined;
@@ -10,9 +12,14 @@ export type ProcessTreeParser = (line: string) => Process | 
undefined;
  */
 export abstract class BaseProcessTree implements ProcessTree {
   /**
-   * Spawn the process responsible for collecting all processes on the system.
+   * Get the command responsible for collecting all processes on the system.
    */
-  protected abstract spawnProcess(): ChildProcessWithoutNullStreams;
+  protected abstract getCommand(): string;
+
+  /**
+   * Get the list of arguments used to launch the command.
+   */
+  protected abstract getCommandArguments(): string[];
 
   /**
    * Create a new parser that can read the process information from stdout of 
the process
@@ -20,83 +27,15 @@ export abstract class BaseProcessTree implements 
ProcessTree {
    */
   protected abstract createParser(): ProcessTreeParser;
 
-  listAllProcesses(): Promise<Process[]> {
-    return new Promise<Process[]>((resolve, reject) => {
-      const proc = this.spawnProcess();
-      const parser = this.createParser();
-
-      // Capture processes from stdout
-      const processes: Process[] = [];
-      proc.stdout.pipe(new LineBasedStream()).on("data", (line) => {
-        const process = parser(line.toString());
-        if (process && process.id !== proc.pid) {
-          processes.push(process);
-        }
-      });
-
-      // Resolve or reject the promise based on exit code/signal/error
-      proc.on("error", reject);
-      proc.on("exit", (code, signal) => {
-        if (code === 0) {
-          resolve(processes);
-        } else if (signal) {
-          reject(
-            new Error(
-              `Unable to list processes: process exited due to signal 
${signal}`,
-            ),
-          );
-        } else {
-          reject(
-            new Error(
-              `Unable to list processes: process exited with code ${code}`,
-            ),
-          );
-        }
-      });
-    });
-  }
-}
-
-/**
- * A stream that emits each line as a single chunk of data. The end of a line 
is denoted
- * by the newline character '\n'.
- */
-export class LineBasedStream extends Transform {
-  private readonly newline: number = "\n".charCodeAt(0);
-  private buffer: Buffer = Buffer.alloc(0);
-
-  override _transform(
-    chunk: Buffer,
-    _encoding: string,
-    callback: () => void,
-  ): void {
-    let currentIndex = 0;
-    while (currentIndex < chunk.length) {
-      const newlineIndex = chunk.indexOf(this.newline, currentIndex);
-      if (newlineIndex === -1) {
-        this.buffer = Buffer.concat([
-          this.buffer,
-          chunk.subarray(currentIndex),
-        ]);
-        break;
+  async listAllProcesses(): Promise<Process[]> {
+    const execCommand = exec(this.getCommand(), this.getCommandArguments());
+    const parser = this.createParser();
+    return (await execCommand).stdout.split("\n").flatMap((line) => {
+      const process = parser(line.toString());
+      if (!process || process.id === execCommand.child.pid) {
+        return [];
       }
-
-      const newlineChunk = chunk.subarray(currentIndex, newlineIndex);
-      const line = Buffer.concat([this.buffer, newlineChunk]);
-      this.push(line);
-      this.buffer = Buffer.alloc(0);
-
-      currentIndex = newlineIndex + 1;
-    }
-
-    callback();
-  }
-
-  override _flush(callback: () => void): void {
-    if (this.buffer.length) {
-      this.push(this.buffer);
-    }
-
-    callback();
+      return [process];
+    });
   }
 }
diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
index 954644288869e..e6c37c9507a7a 100644
--- a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
@@ -1,4 +1,3 @@
-import { ChildProcessWithoutNullStreams, spawn } from "child_process";
 import { LinuxProcessTree } from "./linux-process-tree";
 
 function fill(prefix: string, suffix: string, length: number): string {
@@ -6,11 +5,11 @@ function fill(prefix: string, suffix: string, length: 
number): string {
 }
 
 export class DarwinProcessTree extends LinuxProcessTree {
-  protected override spawnProcess(): ChildProcessWithoutNullStreams {
-    return spawn("ps", [
+  protected override getCommandArguments(): string[] {
+    return [
       "-xo",
       // The length of comm must be large enough or data will be truncated.
       `pid=PID,lstart=START,comm=${fill("COMMAND", "-", 
256)},command=ARGUMENTS`,
-    ]);
+    ];
   }
 }
diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
index 65733f6c547b3..41d2303df8e52 100644
--- a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
@@ -1,15 +1,12 @@
-import { ChildProcessWithoutNullStreams, spawn } from "child_process";
 import { BaseProcessTree, ProcessTreeParser } from "../base-process-tree";
 
 export class LinuxProcessTree extends BaseProcessTree {
-  protected override spawnProcess(): ChildProcessWithoutNullStreams {
-    return spawn(
-      "ps",
-      ["-axo", `pid=PID,lstart=START,comm:128=COMMAND,command=ARGUMENTS`],
-      {
-        stdio: "pipe",
-      },
-    );
+  protected override getCommand(): string {
+    return "ps";
+  }
+
+  protected override getCommandArguments(): string[] {
+    return ["-axo", "pid=PID,lstart=START,comm:128=COMMAND,command=ARGUMENTS"];
   }
 
   protected override createParser(): ProcessTreeParser {
diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts
index 9cfbfa29ab5d3..696c218125d13 100644
--- a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/windows-process-tree.ts
@@ -1,20 +1,18 @@
 import * as path from "path";
 import { BaseProcessTree, ProcessTreeParser } from "../base-process-tree";
-import { ChildProcessWithoutNullStreams, spawn } from "child_process";
 
 export class WindowsProcessTree extends BaseProcessTree {
-  protected override spawnProcess(): ChildProcessWithoutNullStreams {
-    const wmic = path.join(
+  protected override getCommand(): string {
+    return path.join(
       process.env["WINDIR"] || "C:\\Windows",
       "System32",
       "wbem",
       "WMIC.exe",
     );
-    return spawn(
-      wmic,
-      ["process", "get", "CommandLine,CreationDate,ProcessId"],
-      { stdio: "pipe" },
-    );
+  }
+
+  protected override getCommandArguments(): string[] {
+    return ["process", "get", "CommandLine,CreationDate,ProcessId"];
   }
 
   protected override createParser(): ProcessTreeParser {

>From daf2618819f59c1233a197b07db91d5135581faa Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Thu, 6 Mar 2025 15:07:32 -0500
Subject: [PATCH 06/11] use camel case for ${command:pickProcess}

---
 lldb/tools/lldb-dap/README.md    | 6 +++---
 lldb/tools/lldb-dap/package.json | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index 7244a0aa18d73..fb6f3c0415ecc 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -65,7 +65,7 @@ This will attach to a process `a.out` whose process ID is 123:
 }
 ```
 
-You can also use the variable substituion `${command:PickProcess}` to select a
+You can also use the variable substituion `${command:pickProcess}` to select a
 process at the start of the debug session instead of setting the pid manually:
 
 ```javascript
@@ -74,7 +74,7 @@ process at the start of the debug session instead of setting 
the pid manually:
   "request": "attach",
   "name": "Attach to PID",
   "program": "/tmp/a.out",
-  "pid": "${command:PickProcess}"
+  "pid": "${command:pickProcess}"
 }
 ```
 
@@ -237,7 +237,7 @@ the following `lldb-dap` specific key/value pairs:
 | Parameter                         | Type        | Req |         |
 |-----------------------------------|-------------|:---:|---------|
 | **program**                       | string      |     | Path to the 
executable to attach to. This value is optional but can help to resolve 
breakpoints prior the attaching to the program.
-| **pid**                           | number      |     | The process id of 
the process you wish to attach to. If **pid** is omitted, the debugger will 
attempt to attach to the program by finding a process whose file name matches 
the file name from **program**. Setting this value to `${command:PickProcess}` 
will allow interactive process selection in the IDE.
+| **pid**                           | number      |     | The process id of 
the process you wish to attach to. If **pid** is omitted, the debugger will 
attempt to attach to the program by finding a process whose file name matches 
the file name from **program**. Setting this value to `${command:pickProcess}` 
will allow interactive process selection in the IDE.
 | **waitFor**                       | boolean     |     | Wait for the process 
to launch.
 | **attachCommands**                | [string]    |     | LLDB commands that 
will be executed after **preRunCommands** which take place of the code that 
normally does the attach. The commands can create a new target and attach or 
launch it however desired. This allows custom launch and attach configurations. 
Core files can use `target create --core /path/to/core` to attach to core files.
 
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 1bbdbf045dd1b..452cb290a496d 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -147,7 +147,7 @@
           "program": "./bin/lldb-dap.exe"
         },
         "variables": {
-          "PickProcess": "lldb-dap.pickProcess"
+          "pickProcess": "lldb-dap.pickProcess"
         },
         "configurationAttributes": {
           "launch": {
@@ -527,7 +527,7 @@
               "type": "lldb-dap",
               "request": "attach",
               "name": "${1:Attach}",
-              "pid": "^\"\\${command:PickProcess}\""
+              "pid": "^\"\\${command:pickProcess}\""
             }
           },
           {

>From b2c03823b86c397f229c0e111e8a0924dc0199fb Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Thu, 6 Mar 2025 15:50:35 -0500
Subject: [PATCH 07/11] allow filtering processes by program

---
 .../lldb-dap/src-ts/commands/pick-process.ts  | 47 ++++++++++++-------
 1 file changed, 31 insertions(+), 16 deletions(-)

diff --git a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts 
b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
index 2f02a78aae192..79bdeed0a2401 100644
--- a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
+++ b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
@@ -3,7 +3,7 @@ import * as vscode from "vscode";
 import { createProcessTree } from "../process-tree";
 
 interface ProcessQuickPick extends vscode.QuickPickItem {
-  processId: number;
+  processId?: number;
 }
 
 /**
@@ -13,30 +13,45 @@ interface ProcessQuickPick extends vscode.QuickPickItem {
  * string substitution infrastructure. The value will eventually be converted
  * to a number by the debug configuration provider.
  *
+ * @param configuration The related debug configuration, if any
  * @returns The pid of the process as a string or undefined if cancelled.
  */
-export async function pickProcess(): Promise<string | undefined> {
+export async function pickProcess(
+  configuration?: vscode.DebugConfiguration,
+): Promise<string | undefined> {
   const processTree = createProcessTree();
   const selectedProcess = await vscode.window.showQuickPick<ProcessQuickPick>(
     processTree.listAllProcesses().then((processes): ProcessQuickPick[] => {
-      return processes
-        .sort((a, b) => b.start - a.start) // Sort by start date in descending 
order
-        .map((proc) => {
-          return {
-            processId: proc.id,
-            label: path.basename(proc.command),
-            description: proc.id.toString(),
-            detail: proc.arguments,
-          } satisfies ProcessQuickPick;
-        });
+      // Sort by start date in descending order
+      processes.sort((a, b) => b.start - a.start);
+      // Filter by program if requested
+      if (typeof configuration?.program === "string") {
+        processes = processes.filter(
+          (proc) => proc.command === configuration.program,
+        );
+        // Show a better message if all processes were filtered out
+        if (processes.length === 0) {
+          return [
+            {
+              label: "No processes matched the debug configuration's program",
+            },
+          ];
+        }
+      }
+      // Convert to a QuickPickItem
+      return processes.map((proc) => {
+        return {
+          processId: proc.id,
+          label: path.basename(proc.command),
+          description: proc.id.toString(),
+          detail: proc.arguments,
+        } satisfies ProcessQuickPick;
+      });
     }),
     {
       placeHolder: "Select a process to attach the debugger to",
       matchOnDetail: true,
     },
   );
-  if (!selectedProcess) {
-    return;
-  }
-  return selectedProcess.processId.toString();
+  return selectedProcess?.processId?.toString();
 }

>From 18cba4bb5b3b203eaa3028bf5bce5d6c49c32b98 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Thu, 6 Mar 2025 16:35:00 -0500
Subject: [PATCH 08/11] fix Linux process tree to include the full executable
 path

---
 .../process-tree/platforms/linux-process-tree.ts    | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
index 41d2303df8e52..742cff4070d47 100644
--- a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts
@@ -6,7 +6,11 @@ export class LinuxProcessTree extends BaseProcessTree {
   }
 
   protected override getCommandArguments(): string[] {
-    return ["-axo", "pid=PID,lstart=START,comm:128=COMMAND,command=ARGUMENTS"];
+    return [
+      "-axo",
+      // The length of exe must be large enough or data will be truncated.
+      `pid=PID,lstart=START,exe:128=COMMAND,args=ARGUMENTS`,
+    ];
   }
 
   protected override createParser(): ProcessTreeParser {
@@ -24,9 +28,14 @@ export class LinuxProcessTree extends BaseProcessTree {
         return;
       }
 
+      const command = line.slice(commandOffset, argumentsOffset).trim();
+      if (command === "-") {
+        return;
+      }
+
       return {
         id: Number(pid[1]),
-        command: line.slice(commandOffset, argumentsOffset).trim(),
+        command,
         arguments: line.slice(argumentsOffset).trim(),
         start: Date.parse(line.slice(pid[0].length, commandOffset).trim()),
       };

>From 85a19e4406e3b6e05e1f90a61803f810f7d1f363 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Thu, 6 Mar 2025 16:35:16 -0500
Subject: [PATCH 09/11] minor fixes to Darwin process tree

---
 .../src-ts/process-tree/platforms/darwin-process-tree.ts  | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git 
a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts 
b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
index e6c37c9507a7a..68822e570b126 100644
--- a/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
+++ b/lldb/tools/lldb-dap/src-ts/process-tree/platforms/darwin-process-tree.ts
@@ -1,15 +1,11 @@
 import { LinuxProcessTree } from "./linux-process-tree";
 
-function fill(prefix: string, suffix: string, length: number): string {
-  return prefix + suffix.repeat(length - prefix.length);
-}
-
 export class DarwinProcessTree extends LinuxProcessTree {
   protected override getCommandArguments(): string[] {
     return [
-      "-xo",
+      "-axo",
       // The length of comm must be large enough or data will be truncated.
-      `pid=PID,lstart=START,comm=${fill("COMMAND", "-", 
256)},command=ARGUMENTS`,
+      `pid=PID,lstart=START,comm=${"COMMAND".padEnd(256, "-")},args=ARGUMENTS`,
     ];
   }
 }

>From f84f5cc40a22a1609d23d25cb418c225d419fdf8 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Thu, 6 Mar 2025 16:40:09 -0500
Subject: [PATCH 10/11] remove program property from attach to process ID
 example

---
 lldb/tools/lldb-dap/README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index fb6f3c0415ecc..add8d23737ebf 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -73,7 +73,6 @@ process at the start of the debug session instead of setting 
the pid manually:
   "type": "lldb-dap",
   "request": "attach",
   "name": "Attach to PID",
-  "program": "/tmp/a.out",
   "pid": "${command:pickProcess}"
 }
 ```

>From 4137b529540aa6a1276c3ae46ba4161b7b1d1bc5 Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bast...@apple.com>
Date: Thu, 6 Mar 2025 17:03:18 -0500
Subject: [PATCH 11/11] add `lldb-dap.attachToProcess` command

---
 .../lldb-dap/src-ts/commands/attach-to-process.ts      | 10 ++++++++++
 lldb/tools/lldb-dap/src-ts/extension.ts                |  7 +++++++
 2 files changed, 17 insertions(+)
 create mode 100644 lldb/tools/lldb-dap/src-ts/commands/attach-to-process.ts

diff --git a/lldb/tools/lldb-dap/src-ts/commands/attach-to-process.ts 
b/lldb/tools/lldb-dap/src-ts/commands/attach-to-process.ts
new file mode 100644
index 0000000000000..0499ee8c5ecbe
--- /dev/null
+++ b/lldb/tools/lldb-dap/src-ts/commands/attach-to-process.ts
@@ -0,0 +1,10 @@
+import * as vscode from "vscode";
+
+export async function attachToProcess(): Promise<boolean> {
+  return await vscode.debug.startDebugging(undefined, {
+    type: "lldb-dap",
+    request: "attach",
+    name: "Attach to process",
+    pid: "${command:pickProcess}",
+  });
+}
diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts 
b/lldb/tools/lldb-dap/src-ts/extension.ts
index 74815022d468a..9167616d393cb 100644
--- a/lldb/tools/lldb-dap/src-ts/extension.ts
+++ b/lldb/tools/lldb-dap/src-ts/extension.ts
@@ -7,6 +7,7 @@ import {
 } from "./debug-adapter-factory";
 import { DisposableContext } from "./disposable-context";
 import { LLDBDapConfigurationProvider } from "./debug-configuration-provider";
+import { attachToProcess } from "./commands/attach-to-process";
 
 /**
  * This class represents the extension and manages its life cycle. Other 
extensions
@@ -48,6 +49,12 @@ export class LLDBDapExtension extends DisposableContext {
     this.pushSubscription(
       vscode.commands.registerCommand("lldb-dap.pickProcess", pickProcess),
     );
+    this.pushSubscription(
+      vscode.commands.registerCommand(
+        "lldb-dap.attachToProcess",
+        attachToProcess,
+      ),
+    );
   }
 }
 

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to