Symbol formats

Delphi does not emit one debug-info format. It emits several, into different places, each carrying a different part of the picture — and the debugger reads all of them because no single one is sufficient.

What each artefact buys you

The plain-language version first, because it is what actually matters when a build is misconfigured:

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

Note the shape of the first row: any one of three sources will give you source lines. That is the whole reason this is not a precedence list.

The five formats

FormatWhere it livesWhat it carries
TD32 Embedded in the binary — Borland’s .debug PE section, magic FB09 Source lines, file names, symbols. The primary line source when present.
.map Sidecar file, emitted by the linker at Detailed Line numbers and segment/symbol addresses, plus nested-procedure linkage
.rsm Sidecar file, from Include remote debug symbols Local variables and type information. Optional — the debugger works without it, with reduced fidelity.
.dcp Sidecar file, produced by a package build The same format as .rsm, supplying a BPL’s debug information
JCL A JCLDEBUG PE section, or a sidecar .jdbg Line information. On by default; a fallback line source.

An external .tds (dcc64 -VT) is also read, carrying the same kind of content as the embedded TD32 section.

Contribution, not precedence

It is tempting to describe this as an ordered list — try TD32, fall back to MAP, fall back to JCL. That is not what happens, and describing it that way would mislead. Different formats answer different questions, and a build can be missing one capability entirely while another is fully served.

flowchart LR
  TD32["TD32
.debug PE section"] MAP["MAP
.map sidecar"] JCL["JCL
JCLDEBUG / .jdbg"] RSM["RSM
.rsm sidecar"] DCP["DCP
.dcp, for BPLs"] subgraph LINES["Source lines & breakpoints"] L["any ONE of these is enough"] end subgraph VARS["Local variables & types"] V["RSM or DCP
(optional — absence degrades, does not break)"] end subgraph NEST["Nested-procedure linkage"] N["MAP"] end TD32 --> L MAP --> L JCL --> L RSM --> V DCP --> V MAP --> N

Read it as three independent capabilities, each fed by whatever is available. Lose every line source and the debugger cannot set a breakpoint at all. Lose only the .rsm and it still stops exactly where you asked — you just cannot see the locals when it does.

Per module, not per session

All of this applies to each loaded image independently. A host EXE with complete debug information and a BPL built without any is entirely normal, and the debugger reports it per module rather than as a single session-wide state: loaded, noSymbols, or indexing while an index is still filling.

The formats that registered for a module are reported too, which distinguishes two situations that look identical from the outside: a binary that carries no debug information at all, and one whose sidecar simply has not been found. See get_loaded_modules and Multi-BPL and modules.

Not every format can enumerate source files. .rsm, .dcp and .jdbg map addresses but hold no file index, so for a module carrying only those, the file list is unknown rather than empty — and a breakpoint there may still bind.

The one thing none of them fix

TD32’s names table stores source files by basename only, with no directory. Two Oracle.pas files in different folders are therefore indistinguishable to any consumer of that table, and a frame can resolve to the wrong one at the “same” line.

There is no downstream fix: the information was not emitted. It is recorded in Known limitations as a property of the format rather than a defect of the debugger, because that distinction determines whether it is worth reporting as a bug.