[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
Fri Feb 28 11:17:17 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 1/5] 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 2/5] 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 3/5] 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 4/5] 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 c015ed90ac215f552f0bce347d821fae75d94f01 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 5/5] use exec() instead of spawn() to simplify logic
---
.../src-ts/process-tree/base-process-tree.ts | 96 ++++---------------
.../platforms/darwin-process-tree.ts | 10 +-
.../platforms/linux-process-tree.ts | 11 +--
.../platforms/windows-process-tree.ts | 9 +-
4 files changed, 23 insertions(+), 103 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..014b78b469ea3 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.exec);
/** Parses process information from a given line of process output. */
export type ProcessTreeParser = (line: string) => Process | undefined;
@@ -12,7 +14,7 @@ export abstract class BaseProcessTree implements ProcessTree {
/**
* Spawn the process responsible for collecting all processes on the system.
*/
- protected abstract spawnProcess(): ChildProcessWithoutNullStreams;
+ protected abstract getCommand(): string;
/**
* Create a new parser that can read the process information from stdout of the process
@@ -20,83 +22,17 @@ 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 command = this.getCommand();
+ const execCommand = exec(command);
+ const parser = this.createParser();
+ // Capture processes from stdout
+ 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..14837d73c56fe 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,8 @@ function fill(prefix: string, suffix: string, length: number): string {
}
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`,
- ]);
+ protected override getCommand(): string {
+ // The length of comm must be large enough or data will be truncated.
+ return `ps -xo '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..f6512fb91d37d 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,8 @@
-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 -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..1d442d567fba0 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,15 @@
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 {
+ protected override getCommand(): string {
const wmic = path.join(
process.env["WINDIR"] || "C:\\Windows",
"System32",
"wbem",
"WMIC.exe",
);
- return spawn(
- wmic,
- ["process", "get", "CommandLine,CreationDate,ProcessId"],
- { stdio: "pipe" },
- );
+ return `'${wmic}' process get 'CommandLine,CreationDate,ProcessId'`;
}
protected override createParser(): ProcessTreeParser {
More information about the lldb-commits
mailing list