10. Attach to a running process

For a program that is already running: a service, something started by another process, or a session you do not want to restart because reproducing the state took twenty minutes.

The attach configuration

You will not usually write this by hand. The IDE plugin generates an attach configuration for every project alongside its launch one — the same click, the same launch.json — so if you already opened a project in VS Code that way, it is already there, named Attach to <YourExe>.exe. What follows is what it contains, for when you want to understand it, adjust it, or write one from scratch for a case the plugin does not cover.

Set "request": "attach" and identify the process by id or by name:

{
  "name": "Attach to MyApp",
  "type": "delphi-win64",
  "request": "attach",
  "processName": "MyApp.exe",
  "program": "${workspaceFolder}/Win64/Debug/MyApp.exe",
  "sourceRoot": "${workspaceFolder}"
}

program is still worth giving: it is where the debugger reads symbols from. The attach configuration shares sourceRoot, sourceSearchPaths, modules, delphiProjectFile, progressLocation and diagnosticLog with launch, and adds processId, processName and killOnDetach. The full table is in the settings and commands reference.

Sharing delphiProjectFile is what keeps exception rules identical whichever way you connect — there is no per-configuration rule storage any more, launch and attach of the same project resolve the same project-scoped files.

Picking the process interactively

Use the pick-process command as the value of processId to choose at the moment the session starts:

"processId": "${command:delphi-win64.pickProcess}"

You may not need to write that at all: a configuration that names a processName but no usable processId brings up the picker by itself, so an attach configuration you already have gains it without editing.

Each row carries what you need to tell instances apart:

  • the executable name, its PID, architecture, start time and Windows session;
  • the full command line, and the main window’s caption if it has one;
  • a reason, for processes that cannot be debugged — those are listed and marked rather than hidden, so you are not left wondering where your process went.

Typing filters across all of it, not just the name, so a fragment of a command line narrows the list. Processes whose name matches your workspace folder are floated to the top and Windows’ own infrastructure is pushed down. And if a name filter matches exactly one process, the picker does not appear at all — it just attaches.

Pressing Escape in the picker cancels the whole session start. There is no fallback to some default process, deliberately: attaching a debugger to whichever process happened to be first in an enumeration is not a sane default.

The list comes from the debug adapter itself, not from Windows’ task list — which is also available on its own: running the adapter with --list-processes prints the same information as JSON and exits.

The process picker titled 'Select which VendingService.exe to attach to (2 running)', listing two rows: VendingService.exe PID 14604, x64, started 2026-08-23 20:47:51, session 1, with its window caption and full command line below; and a second instance, PID 45372, same architecture and session, a different start time.
Two running instances of the same executable, disambiguated by PID, start time and full command line — exactly the case this picker exists for.

Privileges

Attaching requires SeDebugPrivilege. The adapter tries to enable it itself through AdjustTokenPrivileges where the token allows, but in practice that means either running the editor as Administrator, or attaching to a process you already own.

Detaching

killOnDetach defaults to false for attach: ending the session leaves the process running, which is almost always what you want when you attached to something you did not start.

Detach. Never force-kill the adapter or VS Code during an attached session.

Breakpoints are INT3 bytes patched into the target’s code, and only the debugger that planted them can put the original bytes back. A clean detach does exactly that. An adapter that is killed outright cannot — so the INT3 bytes stay behind in a process that no longer has a debugger attached, and the next time execution reaches one the program takes a breakpoint exception with nobody to handle it. What the user sees is a mystifying External exception 80000003 (STATUS_BREAKPOINT) surfacing from the application’s own error handling, long after the debugger is gone and with nothing to connect it back.

This applies to attached sessions only. A launched target dies together with the adapter, so there is nothing left to corrupt.

From an agent

The same operations are available over MCP — attach_to_process, attach_from_config and detach_debugger — with the same rule about detaching rather than terminating. See Example prompts.