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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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}" } ``` _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits