---
name: delphi-debugging-workflow
description: Use when the user wants to debug a Delphi application — anything mentioning .pas, .dpr, .dproj, "Delphi", "rdbg", "debug this exe", "step through", or attaching to a Delphi process. Covers the daemon handshake, attach vs launch decision, breakpoint setting (source, module!symbol, address), the pause-loop vs request-loop distinction, and the event-pump rhythm.
---

# Delphi debugging workflow with rdbg

You drive a Delphi program through the rdbg debugger via MCP tools.
A daemon — `rdbgd64.exe` for Win64 targets, `rdbgd32.exe` for Win32
targets — is doing the OS-level Win32 debug API work; you tell it
what to do over JSON-RPC. The MCP bridge auto-switches daemons per
`launch` based on the target's PE machine type, so you don't have
to pick. `hello` reports `target_arch: "x64"` or `"x86"` so you
know which ABI is in play. Most state-change tools require the
target to be **paused** — issuing them while the target is running
returns an error.

## Confirm the daemon is up

Before any debugging, call `hello`. It returns daemon version,
protocol version, and `active_sessions`. If `hello` errors, run
`/rdbg:status` and surface the result to the user — they may need
to start the daemon (it must run in their interactive RDP desktop)
or close another client (`rdbgui.exe`, `rdbgc repl`) that's holding
the connection.

## Attach vs launch

- **`attach`** when the target is already running. Take `pid` from
  `list_processes` (filter by `name_match`).
- **`launch`** when starting fresh. Pass `exe` (full path) and
  optional `args`. The daemon waits at the loader breakpoint
  (`paused` event with `reason: initial_loader_breakpoint`); set
  any breakpoints you need, then `continue`.

Either way, an `attach` succeeds with a `paused` event. **Do not
issue another control command before draining that event** — the
target is paused waiting for you.

## Setting breakpoints

`set_breakpoint` takes a `kind`:

- `kind: "source"`, `source: "Unit1.pas", line: 42` — source-line
  breakpoint, resolved via the exe's `.map` file. Delphi compiles
  with `{$D+}` and `dcc64 -GD` produce a usable map.
- `kind: "symbol"`, `symbol: "kernelbase!RaiseException"` — a
  module!symbol breakpoint. PDBs are downloaded from
  msdl.microsoft.com on first use and cached at
  `%LOCALAPPDATA%\rdbg\symbols`. Use this to catch every Delphi
  raise *before* any handler runs.
- `kind: "address"`, `address: 0x...` — raw address. Useful when
  you've stepped to an address you want to revisit.

Add `kind: "hw"` (hardware breakpoint, DR0–DR3) when setting BPs
on read-only pages or system DLLs that can't be patched. Limited
to four hardware slots.

`list_breakpoints` shows what's set; `clear_breakpoint` removes by
`bp_id` or `address`.

## Stepping and the event pump

After every `continue`, `step_over`, `step_into`, or `run_to`,
**immediately call `events(timeout_ms=...)`** to drain the next
event. The target is now running and *will* push an event when it
stops (`paused`, `breakpoint_hit`, `exception`, `exit_process`).
Issuing another control command before draining the event causes
the request to block or fail.

Choose a timeout based on what you're waiting for:

- `300` ms for "next instruction" steps.
- `5000` ms for "continue until breakpoint."
- `30000` ms for "let the user demonstrate something."

If `events` times out without an event, the target is still
running. Either `interrupt` (forces a pause) or extend the
timeout.

## Reading state at a pause

Once paused:

- `get_registers` — GP register dump + `eflags`. Field names follow
  the target ABI: `rip`/`rsp`/`rbp`/`rax-r15` on x64, `eip`/`esp`/
  `ebp`/`eax-edi` on x86. The response includes `arch` so you can
  branch. The instruction pointer (rip/eip) combined with the map
  file lookup tells you the current source line.
- `get_self` — returns the `Self` object at the current frame. The
  daemon probes the ABI's first-arg register (RCX on x64, EAX on
  x86) plus a stack scan; you don't pick.
- `inspect_object(address, max_depth=3)` — walks any Delphi
  object's RTTI. Returns class hierarchy, fields, published props,
  methods, and pointer values for further walking.
- `inspect_locals(frame_idx=0)` — returns the parameters and
  stack-locals for the procedure containing the paused frame's RIP.
  Source comes from the EXE's `.debug` TD32/TDS section, so the
  target must be built with `-V` (Project > Options > Linking >
  "Place debug information in EXE"). Auto-walks past the
  `kernel32!DbgBreakPoint` frame at `DebugBreak()` to land on user
  code; `auto_walked_frames` in the response reports how many
  frames were skipped (positive = via RBP chain, negative = via
  RSP scan, zero = immediate frame was already user code).
  Pair with `evaluate("LocalName.Field")` to chase further.
- `read(kind: "string", address)` — materialize a Delphi `string`
  at an address surfaced by `inspect_object`.

### When `inspect_locals` returns an error

The response includes a `version_warning` field plus a
`fallback_hint` and `diagnostic_command` so you can tell the user
clearly what went wrong:

- `message: "no_debug_section"` — target wasn't built with `-V`.
  Tell the user to enable "Place debug information in EXE" and
  rebuild; pivot to `inspect_object` / `evaluate` for what you
  can still inspect.
- `message: "unsupported_tds_version"` with
  `version_warning: "unsupported_tds: FB0X"` — Embarcadero shipped
  a new TDS format and rdbg hasn't been taught it yet. Surface
  the warning verbatim and pivot to `inspect_object` /
  `evaluate`. The `diagnostic_command` (typically
  `rdbgd64.exe --dump-tds <exe>`) is the bug-report payload.
- `message: "tds_corrupt"` — section magic recognised but layout
  failed validation. Treat the same as `unsupported_tds_version`:
  surface and pivot.

## Common pitfalls

- **Don't issue a control command while a previous step is
  in-flight.** The pump blocks; surface the issue rather than
  waiting indefinitely.
- **Map file vs PDB.** Delphi exes use `.map`; system DLLs use
  PDB. Both are handled transparently for breakpoints, but
  only `.map`-backed code surfaces `source`/`line` in `paused`
  events. Frame walks through system DLLs show `module!symbol+0xNN`
  until they hit user code again.
- **Single-client daemon.** Only one connection at a time. If a
  second client is holding the daemon, your `attach` will sit on
  the accept queue. `/rdbg:status` flags this.
