1. Preparing your project

A debugger can only show what the compiler emitted. Almost every “it does not work” report against this debugger turns out to be a build without the debug information it needs, so this chapter comes first.

Compiler and linker settings

In the Delphi project options, for the Debug configuration of the platform you are building:

SettingWhereValue
OptimizationCompilingOff (-$O-)
Debug informationCompilingOn
Local symbolsCompilingOn
Debug informationLinkingOn
Include remote debug symbolsLinkingOn — this is what emits the .rsm
Map fileLinkingDetailed
Use debug .dcusCompilingOn — optional, lets you step into RTL and VCL source instead of over it

On the command line that is:

dcc64 -$O- -V -VN -VR MyApp.dpr

Keep the .map and .rsm next to the .exe. The adapter looks for them there by default.

The same settings apply to every runtime package you want to step into. A BPL compiled without debug information stays a black box even when the host EXE has full symbols — see Multi-BPL applications and modules.
Project Options for Debugme.exe, Delphi Compiler, Compiling page. Optimization is off, Debug information is set to Debug information, Local symbols is on, and Use debug .dcus is on; Code inlining control off and Stack frames on are also highlighted.
Delphi Compiler → Compiling. Optimization off, Debug information on, Local symbols on, Use debug .dcus on if you want to step into RTL/VCL source. Code inlining control and Stack frames are also worth leaving as shown — inlined code and omitted frame pointers both make the debugger’s job harder.
Project Options for Debugme.exe, Delphi Compiler, Linking page. Debug information is on, Include remote debug symbols is on, and Map file is set to Detailed.
Delphi Compiler → Linking. Debug information on, Include remote debug symbols on — this is what emits the .rsm — and Map file set to Detailed.

What each artefact buys you

The settings above produce several separate pieces of debug information, and they do not all do the same job. What happens when one is missing:

Missing artefactEffect
TD32 section, .map, or JCL data — any one of the three No source lines: no breakpoints, no stepping
.rsm Breakpoints and stepping still work, but local variables, types and expression evaluation are severely limited
Optimizations left on Breakpoints land on the wrong lines and locals read as garbage, because the code no longer matches the source

The .rsm is genuinely optional — the debugger works without it, just with reduced fidelity on locals and types. The source-line sources are not. Symbol formats explains how the layers fit together.

On a 32-bit target there is one hard limit: locals and parameters require an unoptimized (-$O-) build. With -$O+ the compiler routinely omits the frame pointer and locals cannot be resolved at all. Run control — breakpoints, stepping, call stacks — works on optimized 32-bit builds either way.

The launch configuration

.vscode/launch.json in the project being debugged. The Edit in VS Code IDE plugin writes this for you from the real Delphi project, which is what you want on anything with a non-trivial search path. Here is the minimum by hand:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Delphi Win64",
      "type": "delphi-win64",
      "request": "launch",
      "program": "${workspaceFolder}/Win64/Debug/MyApp.exe",
      "sourceRoot": "${workspaceFolder}",
      "stopAtEntry": true
    }
  ]
}

The properties you will actually reach for:

PropertyDefaultWhat it does
programrequiredPath to the .exe to debug
mapFilesame path as program, .mapOnly needed when the MAP file is not beside the executable
rsmFilesame path, .rsmSame, for the file that carries local-variable information
sourceRootemptyRoot directory for source lookup
sourceSearchPathsemptyExtra source directories, each searched two levels deep
argsemptyCommand-line arguments passed to the debuggee
stopAtEntryfalseBreak at the process entry point before any user code runs
modulesemptyPre-binds debug-info files for runtime-loaded DLLs and BPLs — see chapter 9
exceptionRulesemptyPer-exception rule table — see chapter 4

That is the working subset. The complete schema — including rawStackScan, progressLocation, diagnosticLog, diagnosticsLocation and the global exception-rules properties — is in the settings and commands reference.

${env:BDS}/source is searched automatically, so RTL and VCL sources resolve without listing them.

VS Code with launch.json open, the type set to delphi-win64, request launch, program, sourceRoot, mapFile and rsmFile set, and a long generated sourceSearchPaths array.
A generated launch.json. program, mapFile and rsmFile point at the build output; sourceSearchPaths is the long, mechanical part — every include path the project used, spelled out so the plugin generates it rather than anyone typing it by hand.

Attaching instead of launching

Set "request": "attach" and give a processId or processName instead of launching the program yourself. The attach configuration shares program, sourceRoot, sourceSearchPaths, modules and exceptionRules with launch, and adds killOnDetach. Chapter 10 covers it, including one mistake worth knowing about before you try it.

Letting the IDE write it

A real Delphi project carries a couple of hundred unit search paths, an output directory that depends on the configuration, and a list of runtime packages. Transcribing that by hand is not a good use of an afternoon. The Edit in VS Code plugin adds an Open in Visual Studio Code command to the Delphi IDE that generates the workspace file, launch.json and tasks.json from the currently open project.

The Delphi IDE with the Tools menu open. The plugin's command sits at the top of the menu, highlighted, with its keyboard shortcut shown next to it.
Same command as in Getting started — run it once and the workspace, launch.json and tasks.json are all generated from the currently open project.