[Lldb-commits] [lldb] [lldb-dap] Add process picker command to VS Code extension (PR #128943)
Matthew Bastien via lldb-commits
lldb-commits at lists.llvm.org
Thu Mar 6 14:06:52 PST 2025
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_bastien at 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_bastien at 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_bastien at 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_bastien at 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_bastien at 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_bastien at 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_bastien at 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_bastien at 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_bastien at 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_bastien at 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 0a615f248152313101c769efa136a636edbe89ba Mon Sep 17 00:00:00 2001
From: Matthew Bastien <matthew_bastien at apple.com>
Date: Thu, 6 Mar 2025 17:03:18 -0500
Subject: [PATCH 11/11] add `lldb-dap.attachToProcess` command
---
lldb/tools/lldb-dap/package.json | 5 +++++
.../lldb-dap/src-ts/commands/attach-to-process.ts | 10 ++++++++++
lldb/tools/lldb-dap/src-ts/extension.ts | 7 +++++++
3 files changed, 22 insertions(+)
create mode 100644 lldb/tools/lldb-dap/src-ts/commands/attach-to-process.ts
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index 452cb290a496d..e7a4f8b204dc1 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -556,6 +556,11 @@
}
],
"commands": [
+ {
+ "command": "lldb-dap.attachToProcess",
+ "title": "Attach to Process...",
+ "category": "LLDB DAP"
+ },
{
"command": "lldb-dap.pickProcess",
"title": "Pick Process",
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..77f618ecf5925
--- /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,
+ ),
+ );
}
}
More information about the lldb-commits
mailing list