-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[lldb-dap] Add process picker command to VS Code extension #128943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 16 commits
b9083ea
b423842
ee7b00e
82ef750
f4407b2
daf2618
b2c0382
18cba4b
85a19e4
f84f5cc
0a615f2
6f40eb3
d4c81e1
6deb671
582cd9b
548ac79
f967f6d
2492e1e
e9b4e6a
a18e5cc
55e0c95
6d01d9a
e4155ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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}", | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| 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. | ||
| * | ||
| * 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. | ||
| * | ||
| * @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( | ||
| configuration?: vscode.DebugConfiguration, | ||
| ): Promise<string | undefined> { | ||
| const processTree = createProcessTree(); | ||
| const selectedProcess = await vscode.window.showQuickPick<ProcessQuickPick>( | ||
| processTree.listAllProcesses().then((processes): 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, | ||
| }, | ||
| ); | ||
| return selectedProcess?.processId?.toString(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| 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 | ||
| { | ||
| 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) && | ||
matthewbastien marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| !("program" in debugConfiguration) | ||
| ) { | ||
| debugConfiguration.pid = "${command:pickProcess}"; | ||
|
||
| } | ||
| return debugConfiguration; | ||
| } | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import * as util from "util"; | ||
| import * as child_process from "child_process"; | ||
| import { Process, ProcessTree } from "."; | ||
|
|
||
| const exec = util.promisify(child_process.execFile); | ||
|
|
||
| /** 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 { | ||
| /** | ||
| * Get the command responsible for collecting all processes on the system. | ||
| */ | ||
| 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 | ||
| * spawned by {@link spawnProcess spawnProcess()}. | ||
| */ | ||
| protected abstract createParser(): ProcessTreeParser; | ||
|
|
||
| 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 []; | ||
| } | ||
| return [process]; | ||
| }); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { LinuxProcessTree } from "./linux-process-tree"; | ||
|
|
||
| export class DarwinProcessTree extends LinuxProcessTree { | ||
| protected override getCommandArguments(): string[] { | ||
| return [ | ||
| "-axo", | ||
| // The length of comm must be large enough or data will be truncated. | ||
| `pid=PID,state=STATE,lstart=START,comm=${"COMMAND".padEnd(256, "-")},args=ARGUMENTS`, | ||
| ]; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.