Skip to content

[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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b9083ea
add a process picker for attaching by PID
matthewbastien Feb 26, 2025
b423842
convert pid to a number so that lldb-dap can properly consume it
matthewbastien Feb 26, 2025
ee7b00e
update extension README
matthewbastien Feb 26, 2025
82ef750
allow matching on the full command line arguments in the process picker
matthewbastien Feb 26, 2025
f4407b2
use execFile() instead of spawn() to simplify logic
matthewbastien Feb 28, 2025
daf2618
use camel case for ${command:pickProcess}
matthewbastien Mar 6, 2025
b2c0382
allow filtering processes by program
matthewbastien Mar 6, 2025
18cba4b
fix Linux process tree to include the full executable path
matthewbastien Mar 6, 2025
85a19e4
minor fixes to Darwin process tree
matthewbastien Mar 6, 2025
f84f5cc
remove program property from attach to process ID example
matthewbastien Mar 6, 2025
0a615f2
add `lldb-dap.attachToProcess` command
matthewbastien Mar 6, 2025
6f40eb3
use Get-CimInstance on Windows because WMIC is deprecated
matthewbastien Mar 6, 2025
d4c81e1
update README with more info about the 'program' property
matthewbastien Mar 6, 2025
6deb671
add code comment to LinuxProcessTree's parser
matthewbastien Mar 6, 2025
582cd9b
use the pickProcess command in attach requests by default
matthewbastien Mar 7, 2025
548ac79
filter out zombie, trace, and debug state processes on macOS and Linux
matthewbastien Mar 7, 2025
f967f6d
allow searching by pid in the process picker
matthewbastien Mar 7, 2025
2492e1e
match program by basename like the docs say
matthewbastien Mar 7, 2025
e9b4e6a
use the process picker even if `program` is set
matthewbastien Mar 7, 2025
a18e5cc
update documentation for attaching to a process
matthewbastien Mar 7, 2025
55e0c95
update attach parameter table in the README
matthewbastien Mar 7, 2025
6d01d9a
show an error message if no program is provided for waitFor
matthewbastien Mar 7, 2025
e4155ec
update description of waitFor in the package.json to indicate that th…
matthewbastien Mar 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 50 additions & 24 deletions lldb/tools/lldb-dap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,56 +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
}
```

#### Attach by Name
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.

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:
Specifying the pid as a number will attach to that specific process ID and avoid
the process picker entirely:

```javascript
```jsonc
{
"name": "Attach to Name",
"type": "lldb-dap",
"request": "attach",
"program": "/tmp/a.out",
"name": "My Custom Attach",
"pid": 123 // Will attach to the process with ID 123
}
```

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:
#### Filtering by Program Name

```javascript
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:

```jsonc
{
"type": "lldb-dap",
"request": "attach",
"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 set the `"waitFor"` property:

```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

Expand Down Expand Up @@ -224,8 +250,8 @@ 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.
| **waitFor** | boolean | | Wait for the process to launch.
| **pid** | number | | The process id of the process you wish to attach to. If **pid** is omitted or set to `"${command:pickProcess}"` then the extension will show a process picker with a list of running processes on the system. Choosing one of these process will launch a new debug session where `lldb-dap` will attach to the chosen process.
| **waitFor** | boolean | | Wait for a new process that matches the **program** basename to launch. The process picker will not be shown in this case.
| **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.

## Debug Console
Expand Down
40 changes: 37 additions & 3 deletions lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@
"windows": {
"program": "./bin/lldb-dap.exe"
},
"variables": {
"pickProcess": "lldb-dap.pickProcess"
},
"configurationAttributes": {
"launch": {
"required": [
Expand Down Expand Up @@ -355,11 +358,12 @@
"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",
"description": "If set to true, then wait for the process to launch by looking for a process with a basename that matches `program`. No process ID needs to be specified when using this flag.",
"description": "If set to true, then wait for the process to launch by looking for a process with a basename that matches `program`. The process ID is ignored when using this flag.",
"default": true
},
"sourcePath": {
Expand Down Expand Up @@ -517,6 +521,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": "",
Expand All @@ -541,6 +555,26 @@
}
]
}
]
],
"commands": [
{
"command": "lldb-dap.attachToProcess",
"title": "Attach to Process...",
"category": "LLDB DAP"
},
{
"command": "lldb-dap.pickProcess",
"title": "Pick Process",
"category": "LLDB DAP"
}
],
"menus": {
"commandPalette": [
{
"command": "lldb-dap.pickProcess",
"when": "false"
}
]
}
}
}
10 changes: 10 additions & 0 deletions lldb/tools/lldb-dap/src-ts/commands/attach-to-process.ts
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}",
});
}
65 changes: 65 additions & 0 deletions lldb/tools/lldb-dap/src-ts/commands/pick-process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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") {
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 [
{
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,
matchOnDescription: true,
},
);
return selectedProcess?.processId?.toString();
}
90 changes: 90 additions & 0 deletions lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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> {
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;
if (!("program" in debugConfiguration)) {
return vscode.window
.showErrorMessage(
"Failed to attach to process: 'waitFor' requires that a 'program' be provided . Please update your launch configuration.",
{ modal: true },
"Configure",
)
.then((userChoice) => {
switch (userChoice) {
case "Configure":
// returning null from resolveDebugConfiguration() causes VS Code to open the launch configuration
return null;
default:
return undefined;
}
});
}
}
}
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;
}
}
21 changes: 19 additions & 2 deletions lldb/tools/lldb-dap/src-ts/extension.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as path from "path";
import * as util from "util";
import * as vscode from "vscode";

import { pickProcess } from "./commands/pick-process";
import {
LLDBDapDescriptorFactory,
isExecutable,
} 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
Expand All @@ -15,6 +16,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",
Expand All @@ -38,6 +45,16 @@ export class LLDBDapExtension extends DisposableContext {
}
}),
);

this.pushSubscription(
vscode.commands.registerCommand("lldb-dap.pickProcess", pickProcess),
);
this.pushSubscription(
vscode.commands.registerCommand(
"lldb-dap.attachToProcess",
attachToProcess,
),
);
}
}

Expand Down
Loading