Architecture
This section is for anyone reading or extending the source. It is not needed to use the debugger — the tutorial is that. It is here because the shape of the thing explains several of its behaviours, including the ones that look like limitations and are not.
The shape of it
One engine, two frontends, and a symbol layer feeding both. Nothing in the engine knows which frontend is driving it.
flowchart TD VSC["VS Code extension"] -- "DAP over stdio" --> DAP["Debug adapter
VisualStudioCodeDelphiDebugger.exe"] AGENT["AI agent"] -- "MCP protocol" --> MCP["MCP server
DelphiDebuggerMcp.exe"] DAP --> SESSION MCP --> SESSION SESSION["TDebugSession
facade: state machine, source resolution"] --> ITARGET["IDebugTarget
engine interface"] ITARGET -- "target PE header
says x64" --> W64["TWinDebugger
64-bit target"] ITARGET -- "target PE header
says x86" --> W32["TWin32Debugger
32-bit target, via WOW64"] SYM["Symbol resolution
TD32 · MAP · RSM · DCP · JCL"] --> ITARGET W64 --> OS[("Windows Debug API")] W32 --> OS
The one decision made before anything else exists is which engine class to instantiate, and it is read from the target executable’s PE header — not from a setting, not from the adapter’s own bitness.
In this section
-
Engine overview —
DebuggerCore, theIDebugTargetinterface, and why the adapter is always a 64-bit process whatever it debugs. -
DAP and MCP frontends — what each frontend adds,
and the sequence they share below
TDebugSession. - Symbol formats — five debug-information formats, what each actually contributes, and why it is not a precedence list.
-
Breakpoints and exceptions
internals —
INT3patching, the four hardware debug registers, and the exception rule engine’s evaluation order. - Threading model — per-thread stepping, and what stops together.
One principle worth stating up front
A recurring decision throughout this codebase: where the debugger cannot prove an answer, it refuses rather than guesses. Backward disassembly, instruction-level step out, stepping to an exception handler, an address breakpoint in an unloaded module, a hardware watchpoint on a local, a misaligned watch width — each of these returns a stated reason instead of a plausible result.
That is not conservatism for its own sake. A debugger is a measuring instrument, and a wrong reading from a measuring instrument is worse than no reading, because nothing downstream reveals it was wrong — you simply go and debug the wrong thing for an hour. The refusals are listed in Known limitations for exactly that reason: they are a feature that reads like a defect.