rdbg ships as three binaries — a daemon, a CLI, and a GUI — that all share one wire protocol. Here's what each layer brings.
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.
Every operation — launch, set_breakpoint, inspect_object, step_over — is one JSON object per line over TCP. Trivial to drive from any language.
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.
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.
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.
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.
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.
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.
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.
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.
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.
VCL Win64 native. Three columns, tabbed source viewer, project tree, structured event list. Built with the same wire that everything else speaks.
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.
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.
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.
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.
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.
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.
The CLI is a one-shot driver. The wire is the real interface — agents, scripts, and CI all live there.
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.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.
Sequential reconnects work since v1.2, so an agent can drop the connection between probes without killing the target.
Five new wire methods that let agents and tooling click through a Delphi VCL UI without a human in the loop.
Enumerate top-level + child HWNDs in the target process. Class name, caption, rect, owner.
Fire WM_* into a target HWND, or synthesize raw key/mouse input through SendInput on the daemon side.
Type a string into the focused control, or replay an ordered sequence with optional dwell times.
Synthesize a mouse click at a window-relative coordinate, optionally focusing the HWND first.
Bump a counter on every major window event so clients know when to re-enumerate.
Synchronous-return wire method for messages whose return value matters (e.g. RM_GetObjectInstance).
rdbg is opinionated about scope. These are explicit non-goals for v1.x — awareness, not regrets.
rdbgd64 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.
ptrace; that's a different project.
Pick up the binaries, or follow the 5-minute quickstart from a console window.