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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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_bast...@apple.com> Date: Thu, 6 Mar 2025 17:03:18 -0500 Subject: [PATCH 11/20] 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, + ), + ); } } >From 6f40eb3d6c5ff4a56c0f7cfb25c304d9865f0500 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Thu, 6 Mar 2025 18:34:35 -0500 Subject: [PATCH 12/20] use Get-CimInstance on Windows because WMIC is deprecated --- .../platforms/windows-process-tree.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) 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 696c218125d13..3181da8fb34bc 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 @@ -3,20 +3,18 @@ import { BaseProcessTree, ProcessTreeParser } from "../base-process-tree"; export class WindowsProcessTree extends BaseProcessTree { protected override getCommand(): string { - return path.join( - process.env["WINDIR"] || "C:\\Windows", - "System32", - "wbem", - "WMIC.exe", - ); + return "PowerShell"; } protected override getCommandArguments(): string[] { - return ["process", "get", "CommandLine,CreationDate,ProcessId"]; + return [ + "-Command", + 'Get-CimInstance -ClassName Win32_Process | Format-Table ProcessId, @{Label="CreationDate";Expression={"{0:yyyyMddHHmmss}" -f $_.CreationDate}}, CommandLine | Out-String -width 9999', + ]; } protected override createParser(): ProcessTreeParser { - const lineRegex = /^(.*)\s+([0-9]+)\.[0-9]+[+-][0-9]+\s+([0-9]+)$/; + const lineRegex = /^([0-9]+)\s+([0-9]+)\s+(.*)$/; return (line) => { const matches = lineRegex.exec(line.trim()); @@ -24,9 +22,9 @@ export class WindowsProcessTree extends BaseProcessTree { return; } - const id = Number(matches[3]); + const id = Number(matches[1]); const start = Number(matches[2]); - let fullCommandLine = matches[1].trim(); + let fullCommandLine = matches[3].trim(); if (isNaN(id) || !fullCommandLine) { return; } @@ -35,7 +33,7 @@ export class WindowsProcessTree extends BaseProcessTree { if (fullCommandLine[0] === '"') { const end = fullCommandLine.indexOf('"', 1); if (end > 0) { - command = fullCommandLine.slice(1, end - 1); + command = fullCommandLine.slice(1, end); } } else { const end = fullCommandLine.indexOf(" "); >From d4c81e1a5a69ce962b05af829d1c27836d643a49 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Thu, 6 Mar 2025 18:42:58 -0500 Subject: [PATCH 13/20] update README with more info about the 'program' property --- lldb/tools/lldb-dap/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index add8d23737ebf..a79f2000f38b2 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 substitution `${command:pickProcess}` to select a process at the start of the debug session instead of setting the pid manually: ```javascript @@ -73,10 +73,15 @@ process at the start of the debug session instead of setting the pid manually: "type": "lldb-dap", "request": "attach", "name": "Attach to PID", - "pid": "${command:pickProcess}" + "pid": "${command:pickProcess}", + "program": "/path/to/program" } ``` +Note: The `program` path above is optional. If specified, `pickProcess` will +filter the list of available process based on the full program path. If absent, +`pickProcess` will offer a list of all processes running on the system. + #### Attach by Name This will attach to an existing process whose base >From 6deb671d6f39d84e6da4aa916d385ee29191612b Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Thu, 6 Mar 2025 18:47:23 -0500 Subject: [PATCH 14/20] add code comment to LinuxProcessTree's parser --- .../lldb-dap/src-ts/process-tree/platforms/linux-process-tree.ts | 1 + 1 file changed, 1 insertion(+) 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 742cff4070d47..9b2536fb56b73 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 @@ -28,6 +28,7 @@ export class LinuxProcessTree extends BaseProcessTree { return; } + // ps will list "-" as the command if it does not know where the executable is located const command = line.slice(commandOffset, argumentsOffset).trim(); if (command === "-") { return; >From 582cd9bed225fa613f246f78db57392b67851c16 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Thu, 6 Mar 2025 19:09:16 -0500 Subject: [PATCH 15/20] use the pickProcess command in attach requests by default --- .../src-ts/debug-configuration-provider.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts index 02b8bd5aa8147..18e9ed0bc2c57 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts @@ -34,6 +34,23 @@ function convertToInteger(value: any): number | undefined { export class LLDBDapConfigurationProvider implements vscode.DebugConfigurationProvider { + resolveDebugConfiguration( + _folder: vscode.WorkspaceFolder | undefined, + debugConfiguration: vscode.DebugConfiguration, + _token?: vscode.CancellationToken, + ): vscode.ProviderResult<vscode.DebugConfiguration> { + // Default "pid" to ${command:pickProcess} if neither "pid" nor "program" are specified + // in an "attach" request. + if ( + debugConfiguration.request === "attach" && + !("pid" in debugConfiguration) && + !("program" in debugConfiguration) + ) { + debugConfiguration.pid = "${command:pickProcess}"; + } + return debugConfiguration; + } + resolveDebugConfigurationWithSubstitutedVariables( _folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, >From 548ac793a52562b84ca378eba651f5ac1978a594 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Thu, 6 Mar 2025 19:32:48 -0500 Subject: [PATCH 16/20] filter out zombie, trace, and debug state processes on macOS and Linux --- .../platforms/darwin-process-tree.ts | 2 +- .../platforms/linux-process-tree.ts | 18 +++++++++++++----- 2 files changed, 14 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 68822e570b126..add0fd05dc2b2 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 @@ -5,7 +5,7 @@ export class DarwinProcessTree extends LinuxProcessTree { return [ "-axo", // The length of comm must be large enough or data will be truncated. - `pid=PID,lstart=START,comm=${"COMMAND".padEnd(256, "-")},args=ARGUMENTS`, + `pid=PID,state=STATE,lstart=START,comm=${"COMMAND".padEnd(256, "-")},args=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 9b2536fb56b73..27c46c0f0c97b 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 @@ -9,7 +9,7 @@ export class LinuxProcessTree extends BaseProcessTree { return [ "-axo", // The length of exe must be large enough or data will be truncated. - `pid=PID,lstart=START,exe:128=COMMAND,args=ARGUMENTS`, + `pid=PID,state=STATE,lstart=START,exe:128=COMMAND,args=ARGUMENTS`, ]; } @@ -23,8 +23,14 @@ export class LinuxProcessTree extends BaseProcessTree { return; } - const pid = /^\s*([0-9]+)\s*/.exec(line); - if (!pid) { + const pidAndState = /^\s*([0-9]+)\s+([a-zA-Z<>+]+)\s+/.exec(line); + if (!pidAndState) { + return; + } + + // Make sure the process isn't in a trace/debug or zombie state as we cannot attach to them + const state = pidAndState[2]; + if (state.includes("X") || state.includes("Z")) { return; } @@ -35,10 +41,12 @@ export class LinuxProcessTree extends BaseProcessTree { } return { - id: Number(pid[1]), + id: Number(pidAndState[1]), command, arguments: line.slice(argumentsOffset).trim(), - start: Date.parse(line.slice(pid[0].length, commandOffset).trim()), + start: Date.parse( + line.slice(pidAndState[0].length, commandOffset).trim(), + ), }; }; } >From f967f6d3ec721e846802cdfc5eb10c61571b1f98 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Fri, 7 Mar 2025 09:42:38 -0500 Subject: [PATCH 17/20] allow searching by pid in the process picker Co-authored-by: Da-Viper <57949090+da-vi...@users.noreply.github.com> --- 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 79bdeed0a2401..30eb86dc7eade 100644 --- a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts +++ b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts @@ -51,6 +51,7 @@ export async function pickProcess( { placeHolder: "Select a process to attach the debugger to", matchOnDetail: true, + matchOnDescription: true, }, ); return selectedProcess?.processId?.toString(); >From 2492e1ec4597e5405413dd66f7353905fad0b741 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Fri, 7 Mar 2025 11:39:30 -0500 Subject: [PATCH 18/20] match program by basename like the docs say --- lldb/tools/lldb-dap/src-ts/commands/pick-process.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 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 30eb86dc7eade..fc8560d0032bc 100644 --- a/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts +++ b/lldb/tools/lldb-dap/src-ts/commands/pick-process.ts @@ -26,9 +26,16 @@ export async function pickProcess( 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, - ); + const program = configuration.program; + const programBaseName = path.basename(program); + processes = processes + .filter((proc) => path.basename(proc.command) === programBaseName) + .sort((a, b) => { + // Bring exact command matches to the top + const aIsExactMatch = a.command === program ? 1 : 0; + const bIsExactMatch = b.command === program ? 1 : 0; + return bIsExactMatch - aIsExactMatch; + }); // Show a better message if all processes were filtered out if (processes.length === 0) { return [ >From e9b4e6a86c1dafd3835eb47ddacd3ec5645f555d Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Fri, 7 Mar 2025 11:40:13 -0500 Subject: [PATCH 19/20] use the process picker even if `program` is set --- .../src-ts/debug-configuration-provider.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts index 18e9ed0bc2c57..7a748e93d9047 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts @@ -39,14 +39,16 @@ export class LLDBDapConfigurationProvider debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken, ): vscode.ProviderResult<vscode.DebugConfiguration> { - // Default "pid" to ${command:pickProcess} if neither "pid" nor "program" are specified - // in an "attach" request. - if ( - debugConfiguration.request === "attach" && - !("pid" in debugConfiguration) && - !("program" in debugConfiguration) - ) { - debugConfiguration.pid = "${command:pickProcess}"; + if (debugConfiguration.request === "attach") { + // Use the process picker by default in attach mode to select the pid. + if (!("pid" in debugConfiguration)) { + debugConfiguration.pid = "${command:pickProcess}"; + } + // The process picker cannot be used in "waitFor" mode. + // Remove the property even if the user explicitly requested it. + if (debugConfiguration.waitFor === true) { + delete debugConfiguration.pid; + } } return debugConfiguration; } >From a18e5ccd3fd95cc7e003850674515bac6549dd95 Mon Sep 17 00:00:00 2001 From: Matthew Bastien <matthew_bast...@apple.com> Date: Fri, 7 Mar 2025 12:06:05 -0500 Subject: [PATCH 20/20] update documentation for attaching to a process --- lldb/tools/lldb-dap/README.md | 73 ++++++++++++++++++-------------- lldb/tools/lldb-dap/package.json | 3 +- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index a79f2000f38b2..bdc74b5ee59ed 100644 --- a/lldb/tools/lldb-dap/README.md +++ b/lldb/tools/lldb-dap/README.md @@ -45,73 +45,82 @@ adds `FOO=1` and `bar` to the environment: ### Attaching to a process -When attaching to a process using LLDB, you can attach in multiple ways: +You can attach to a running process on the system in one of two ways: -1. Attach to an existing process using the process ID -2. Attach to an existing process by name -3. Attach by name by waiting for the next instance of a process to launch +1. Using the `LLDB DAP: Attach to Process...` command +2. Creating a launch configuration with `"request"` set to `"attach"` -#### Attach using PID +#### Using the Attach to Process Command -This will attach to a process `a.out` whose process ID is 123: +The `LLDB DAP: Attach to Process...` command can be accessed from the command +palette. It will show a list of available processes running on the system. +Choosing one of these processes will start a debugging session where `lldb-dap` +will attach to the chosen process. -```javascript +#### Creating a Launch Configuration + +Sometimes you will want to rename an attach debug session or configure lldb-dap +on a per-session basis. For this you can create a new launch configuration +with the `"request"` property set to `"attach"`: + +```jsonc { "type": "lldb-dap", "request": "attach", - "name": "Attach to PID", - "program": "/tmp/a.out", - "pid": 123 + "name": "My Custom Attach", + "pid": "${command:pickProcess}" // optional: the process picker is used by default } ``` -You can also use the variable substitution `${command:pickProcess}` to select a -process at the start of the debug session instead of setting the pid manually: +These launch configurations can be accessed from the Run and Debug view in the +VS Code side panel. You will once again be shown a process picker upon launching +this debug session. Selecting a process will start a debugging session where +`lldb-dap` will attach to the specified process. -```javascript +Specifying the pid as a number will attach to that specific process ID and avoid +the process picker entirely: + +```jsonc { "type": "lldb-dap", "request": "attach", - "name": "Attach to PID", - "pid": "${command:pickProcess}", - "program": "/path/to/program" + "name": "My Custom Attach", + "pid": 123 // Will attach to the process with ID 123 } ``` -Note: The `program` path above is optional. If specified, `pickProcess` will -filter the list of available process based on the full program path. If absent, -`pickProcess` will offer a list of all processes running on the system. +#### Filtering by Program Name -#### Attach by Name +The process picker allows for fuzzy matching of program name, arguments, and +process ID. However, you can also provide an optional `"program"` property to +your launch configuration in order to filter the process picker by the basename +of the program by default: -This will attach to an existing process whose base -name matches `a.out`. All we have to do is leave the `pid` value out of the -above configuration: - -```javascript +```jsonc { - "name": "Attach to Name", "type": "lldb-dap", "request": "attach", - "program": "/tmp/a.out", + "name": "My Custom Attach", + "program": "${workspaceFolder}/build/a.out" // Will show only processes that match "a.out" } ``` -If you want to ignore any existing a.out processes and wait for the next instance -to be launched you can add the "waitFor" key value pair: +If you want to ignore any existing `a.out` processes and wait for the next instance +to be launched you can set the `"waitFor"` property: -```javascript +```jsonc { "name": "Attach to Name (wait)", "type": "lldb-dap", "request": "attach", - "program": "/tmp/a.out", - "waitFor": true + "program": "${workspaceFolder}/build/a.out", + "waitFor": true // Forces lldb-dap to wait for a new process to launch } ``` This will work as long as the architecture, vendor and OS supports waiting for processes. Currently MacOS is the only platform that supports this. +Additionally, the process picker will not be used in this case. ### Loading a Core File diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index e7a4f8b204dc1..b04d3c92bd738 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -358,7 +358,8 @@ "number", "string" ], - "description": "System process ID to attach to." + "description": "Optional process ID to attach to. Defaults to the process picker UI.", + "default": "${command:pickProcess}" }, "waitFor": { "type": "boolean", _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits