Features

Everything in the box.

rdbg ships as three binaries — a daemon, a CLI, and a GUI — that all share one wire protocol. Here's what each layer brings.

Core debug engine

Built directly on the Win32 Debugging API.

The daemon (rdbgd64.exe for Win64 targets, rdbgd32.exe for Win32 targets) launches your target under DEBUG_PROCESS and pumps the debug event loop in-process. No middleman; the wire protocol is identical between the two flavours.

Line-delimited JSON wire protocol

Every operation — launch, set_breakpoint, inspect_object, step_over — is one JSON object per line over TCP. Trivial to drive from any language.

v0.19.00 (M3) · docs/protocol.md · multi-conn since v0.19.19, zombie sessions survive disconnect

Software breakpoints (0xCC patching)

Resolve source:line via the loaded MAP, write 0xCC, handle the hit with restore-byte / rewind-RIP / trap-flag single-step / re-patch. Re-arm survives stepping right after a hit.

v0.19.00 (M4) · unlimited slots · set_breakpoint / clear_breakpoint / list_breakpoints

Hardware breakpoints (DR0–DR3)

Up to four DR-slot HW breakpoints alongside SW BPs. hw: prefix on a BP spec routes to the next free debug register. Address forms accepted: source:line, address, or module!symbol (resolved via PDB). Code BPs today; data watchpoints (read/write watches via DR slots) planned.

v0.19.00 (M4.3) · max 4 concurrent · per-thread CONTEXT propagation

Annotated stack traces — MAP and PDB

Hand-rolled MAP-file parser plus a cross-process x64 RBP-chain walker. Every frame resolves to unit.pas:line symbol+offset for the launched exe, and to module!Symbol+offset for any DLL the target loaded — including kernel32, ntdll, user32, etc. PDBs are auto-fetched from Microsoft's public symbol server on first use and cached at %LOCALAPPDATA%\rdbg\symbols. Pure-Delphi MSF parser; no dbghelp.dll redistribution.

v0.19.00 (MAP) · v0.19.07 (PDB) · Rdbg.MapFile + Rdbg.StackWalk + Rdbg.Pdb · symsrv-shaped cache (interoperable with dbghelp / symchk / VS)

Single-step and step-over

Trap-flag single-stepping with line-aware step-over: loops the TF step until RIP's MAP resolution moves to a different source:line, so CALL instructions step through cleanly.

v0.19.00 (M9–M12) · v0.19.29 direct-CALL skip (proactive temp-BP at RIP+5 on 0xE8) · 50k-instruction safety cap

TObject inspection via VMT walking

Given a target VA pointing at a Delphi object, walks the VMT chain returning class name + instance size for every ancestor up to TObject. Self-calibrates for the RTL build.

v0.19.00 (M5) · inspect_object · auto-issued on every exception

Recursive RTTI property decoding

Walks the entire props list cross-process: ordinals (any width), Int64, all float flavours, every string flavour, tkClass (recurses), classref, pointers, interfaces, variants, dyn / static arrays.

v0.19.00 (M15–M16) · v0.19.10 added private/protected visibility tags · is_default annotation · max-depth capped

Expression evaluator (path expressions)

One wire call: evaluate with expr:"Self.Caption" or "Form.Edit1.Text" or "0x<hex>.ComponentCount". Pascal-style path resolution — dotted member access, both field- and method-backed, chained any depth, with cross-process RPC for property getters carried through transparently. Same return shape as inspect_object's per-prop entries, so existing renderers work.

v0.19.27 · Rdbg.Expr parser · reuses BuildPropEntry + RTTI walker · AV diagnostics flow through

Local variable inspection (TD32/TDS reader)

inspect_locals returns parameter and stack-local information for the procedure containing the paused frame's RIP. Reads the EXE's .debug PE section — Borland's CodeView 4 derivative (magic FB09) — that Delphi emits when you build with -V ("Place debug information in EXE"). Decodes both stack-spilled and register-allocated parameters; resolves names through the global names table; demangles procedure signatures via a tight Itanium-ABI-style parser (Borland's variant). Auto-walks past kernel32!DbgBreakPoint at DebugBreak() to land on user code.

v0.19.32 · Rdbg.Td32 + Rdbg.Td32.LocalsLookup · version-gated on FB09; future format bumps surface as version_warning rather than silent miscompilation · --dump-tds diagnostic CLI for bug reports

Method-backed property values via cross-process RPC

eval_method_props:true hijacks the paused thread and calls each property getter on a 64 KB scratch stack so values come back live — TForm.Caption = 'MainWindow', TStrings.Count = 42, virtual getters dispatch through the VMT, managed-result strings/records decode out of a hidden out-param slot. Per-prop AV failures surface data_addr + op (read/write/execute) so an agent sees which pointer was bad without parsing EXCEPTION_RECORD. Methods array can be suppressed (include_methods:false) or scoped to the leaf class only (methods_from_leaf_only:true) to cut ~3 KB of noise on triage calls.

v0.19.25 (M17) · RunRpcCall · bitness-clean (RCX/RDX on x64, EAX/EDX on x86) · eval_timeout_ms bounds runaway getters

First-arg inspection without TD32 (read_param)

When the target wasn't built with -V, inspect_locals returns no_debug_section. read_param(index, kind?) is the escape hatch: reads the calling-convention argument register for the paused thread — RCX/RDX/R8/R9 on x64, EAX/EDX/ECX on x86 (Delphi register convention), with stack-slot fallback for index ≥ 4 (or ≥ 3). Optional kind:"string" decodes a Delphi string at the value via the same primitives read uses, so paused inside StrToInt('abc')read_param(0, kind:"string"){value:"0x...", decoded:{text:"abc",...}} in one call.

v0.19.33 · arch-aware via RDBG_TARGET_ARCH · surfaces register name + source:"register"|"stack" · in-pause only

Classified exception frames

Every entry in an exception event's frames[] now carries a kind: user (target's main exe), system (Windows DLLs — kernelbase, ntdll, user32, gdi32, ucrtbase, etc.), rtl (Delphi runtime packages, *.bpl), or unknown. Agents filter for kind == "user" to find the user-code frame instantly without re-implementing module-name heuristics. Combined with the existing MAP+PDB resolution, the first user frame typically carries source + line — the file:line you'd put in a bug report.

v0.19.33 · conservative classifier (unknown wins over guess) · basename + .bpl sniff · orthogonal to MAP/PDB resolution

Bridge introspection & cross-bitness probe

hello returns both daemons' status in one call ({x64:{running, port, version, ...}, x86:{...}}) so an agent sees the wrong-bitness situation before launch would have failed. The new bridge_config tool exposes the bridge's effective config — ports, daemon paths, auto_start state, current target arch, interactive-session flag, and LSP-side state — so "why didn't auto_start fire?" is one tool call, not a stderr expedition. Stale paused events are scrubbed from the queue before continue/step_*/run_to/set_rip/detach/kill_session, eliminating the loader-BP-event-still-queued footgun.

v0.19.33 · bridge-only (no daemon round-trip) · honours RDBG_AUTO_START + per-bitness ports · daemon-not-reachable bypass
DelphiLSP integration

Static analysis on the same wire as runtime debug.

The bridge embeds Embarcadero's DelphiLSP.exe as a child process and surfaces its standard LSP methods as MCP tools, capability-gated per Delphi version (11.3 → 13.1). Claude can navigate Delphi source the way an IDE does — without grepping — alongside the runtime-debug surface in the same plugin.

12 LSP-backed MCP tools

Navigation: lsp_definition, lsp_declaration (interface section), lsp_implementation (jumps from interface decl → body), lsp_hover, lsp_references (server-backed when advertised). Authoring: lsp_completion (with trigger-char metadata), lsp_signature_help. Outline: lsp_document_symbols (hierarchical), lsp_workspace_symbol (server-backed when available; per-file documentSymbol aggregation fallback otherwise with precision:"approximate"). Diagnostics: lsp_diagnostics (pull-style snapshot of cached publishDiagnostics; per-URI wait-event lets callers block briefly for first diagnostics after didOpen). Project lifecycle: lsp_open_project bootstraps a channel and sets it as the session's current default. Aggregators: lsp_explain (hover + definition + references in one call), lsp_outline_workspace.

v0.19.33 · capability-gated against initialize.result.capabilities · per-tool gating means D12 users never see lsp_references (not advertised on 12) · documentSymbol cached by (uri, mtime)

.delphilsp.json autogen from .dproj

DelphiLSP's project-config requirement is the biggest first-time-user cliff. The bridge auto-generates a .delphilsp.json from any .dproj by parsing dproj XML + reading registry library paths + walking $(BDS)\source for browsingPaths. No "tick the IDE checkbox, close, reopen" dance — point the bridge at a .dproj and the LSP child gets a working project model on the first call. If you'd rather use an IDE-generated config, drop one next to the dproj and the bridge prefers a fresh existing sibling over autogen. Disable autogen entirely with RDBG_LSP_NO_AUTOGEN=1.

v0.19.33 · dccOptions assembled from registry-first → dproj-second → hardcoded-defaults priority · oracle-diff validated against IDE-generated samples · first open-source tool to do this (verified across SkybuckFlying / DelphiLspProbe / rickard67 / Embarcadero VS Code extension)

Multi-project registry with idle reap

One DelphiLSP.exe child per .delphilsp.json, keyed by canonical (lowercased absolute) project path. Per-call resolution priority: explicit project arg → walk-up from the tool's file arg looking for sibling .delphilsp.json or .dproj → current default set by lsp_open_project or RDBG_LSP_PROJECT. Idle channels are reaped after 5 minutes (configurable); the current default is exempt so the typical "one project at a time" workflow never pays a respawn. Group-project workspaces switch via lsp_open_project.

v0.19.33 · supports D11.3 → D13.1 · registry walk auto-discovers DelphiLSP.exe via HKCU\Software\Embarcadero\BDS\<ver>\RootDir · bds.exe doesn't need to be running

--lsp mode for non-Claude clients

Launch rdbg-mcp.exe --lsp and the bridge speaks raw LSP over stdio instead of MCP — VS Code, Neovim, Zed, or any LSP client can connect. Initialize-time project resolution + autogen + multi-version DelphiLSP discovery all carry through; non-Claude users get the same zero-config Delphi LSP server Claude Code users get. The bridge runs as a transparent pump after initialize: client requests forward verbatim with their original IDs; server responses + notifications stream back via the channel's reader thread. License-error translation also applies, so a non-interactive session shows the actionable bridge-level message instead of bare "valid license not found."

v0.19.33 · Content-Length-framed JSON-RPC on stdin/stdout · serverInfo:{name:"rdbg-lsp"} · license caveat: DelphiLSP needs an interactive desktop session
rdbgui

A real debugger interface, not a wrapper.

VCL Win64 native. Three columns, tabbed source viewer, project tree, structured event list. Built with the same wire that everything else speaks.

Project-oriented launch

Open a .dproj, hit Go: connect → launch → install breakpoints → continue past loader BP, all in one click. Project sidecar persists target host:port and default BPs.

Three connection modes

Local spawns the daemon as a child. SSH tunnels through ssh -L and runs the daemon on the remote box. Attach connects to a daemon you started yourself.

Project validation

On Open Project, ValidateDproj checks the filesystem for missing .exe, missing .map next to the exe, non-Win64 platform, non-Debug config. Blocking issues gate Go.

Object explorer

An inspect_object response renders as a flat row table with Inspect-> buttons on every tkClass row. Multiple windows coexist, F5 refreshes, breadcrumb back to parent.

Source viewer with gutters

Owner-drawn line-number gutter + marker column: open circle for MAP-resolvable lines, filled red for set BPs. Click to toggle. Auto-navigates to the top frame on every break / step.

State-machine UI

Disconnected / connected / running / paused. Every menu and toolbar entry knows when it's relevant; only the verbs that make sense at any given moment are enabled.

rdbgc & agents

For when the GUI isn't there.

The CLI is a one-shot driver. The wire is the real interface — agents, scripts, and CI all live there.

Built-in CLI verbs

rdbgc ships with five canonical verbs that cover most workflows:

  • launch — spawn the target, stream events to stdout.
  • debug — one-shot "set BPs, print every hit".
  • inspect — auto inspect_object on every exception.
  • list — enumerate windows, threads, breakpoints.
  • send — cross-process input injection.

Agent-friendly

The wire's pause-loop mental model maps cleanly to autonomous tools. The daemon blocks on every BP hit; an agent reads the event, walks the stack, decides a next step, and sends continue.

Multi-conn daemon since v0.19.19 — an agent can drop the connection between probes without killing the target, and multiple agents can drive the same daemon concurrently. Zombie sessions survive disconnect; a fresh client adopts a paused target via bind_session.

Read the agent guide →

v0.19.04 — input injection

Drive a target's UI cross-process.

Five new wire methods that let agents and tooling click through a Delphi VCL UI without a human in the loop.

v0.19.04

list_windows

Enumerate top-level + child HWNDs in the target process. Class name, caption, rect, owner.

v0.19.04

post_message / send_input

Fire WM_* into a target HWND, or synthesize raw key/mouse input through SendInput on the daemon side.

v0.19.04

send_keys / send_key_sequence

Type a string into the focused control, or replay an ordered sequence with optional dwell times.

v0.19.04

click_at

Synthesize a mouse click at a window-relative coordinate, optionally focusing the HWND first.

Soon

HWND-generation counter

Bump a counter on every major window event so clients know when to re-enumerate.

Soon

send_message (sync)

Synchronous-return wire method for messages whose return value matters (e.g. RM_GetObjectInstance).

Honest scope

What rdbg doesn't do (yet).

rdbg is opinionated about scope. These are explicit non-goals for v1.x — awareness, not regrets.

  • WoW64 cross-bitness debugrdbgd64 debugs Win64 targets, rdbgd32 debugs Win32 targets. Each daemon stays in its own bitness; debugging a Win32 target from a Win64 daemon is intentionally not supported.
  • Non-Windows targets — Linux/macOS need ptrace; that's a different project.
  • TLS on the wire — use an SSH tunnel for trusted-link transport.
  • Bundling DelphiLSP.exe — license-prohibited (Embarcadero EULA, non-redistributable). The bridge locates a user-installed RAD Studio install via registry; non-licensed machines fall back to grep + Read for source navigation.
  • Arbitrary expression evaluation — the evaluate tool covers Pascal-style dot-paths (Self.Caption, Form.Edit1.Text) including method-backed property calls. Indexing (Components[0]), arithmetic, type casts, and method calls with args still fail with parseable error reasons; agents branch on those rather than getting silent wrong values.

Ready when you are.

Pick up the binaries, or follow the 5-minute quickstart from a console window.

Download Quickstart