Roadmap

Where rdbg is going.

The plan in one place. Everything below the “Now” marker has design notes in docs/plan-*.md — the bigger items are estimated in days, not vibes.

Shipped In progress Next up Future

Step Over — indirect / tail-call decoding Future

v1 shipped pre-emptive skip for the 5-byte direct E8 rel32 CALL. v2 extends the classifier to indirect calls (FF /2, including the FF 50 xx virtual-method-dispatch shape that's everywhere in Delphi RTTI code) and to E9 rel32 tail-JMPs (where RSP doesn't drop, so today's reactive return-addr-BP path misses them). Needs a minimal ModRM byte parser — not a full disassembler. ~150 lines.

Step Over — source-line-range BP planting Future

Bigger redesign of the inner step loop. Instead of TF + reactive return-addr BP, plant temp BPs at every plausible next-line RVA from the .map (plus the return address) at step start, continue, catch whichever fires first. Eliminates ALL per-instruction kernel round-trips for long source lines — Foo; Bar; Baz; on one line lands at the next line in a single trap. ~200 lines.

Step Over — noreturn / SEH timeout Future

Today, stepping over a function that doesn't return (Halt, raise, longjmp-style) leaves the daemon waiting forever for a return-address BP that never fires. v2 adds a wall-clock cap on the step wait; on timeout, surface a paused event with reason:'step_timeout' and the current RIP / source so the user sees where the target actually is.

.pdata / .xdata unwind-info stack walker Future

Today's walker follows x64 RBP chains, which return shorter frame arrays for targets built with {$STACKFRAMES OFF} / heavy {$O+}. A real unwinder would reach IDE parity for FPE targets. Medium-large effort.

inspect_locals via TD32/TDS Shipped

inspect_locals wire method (and matching MCP tool) returns parameter and stack-local information for the procedure containing the paused frame's RIP. Reads the EXE's .debug TD32/TDS PE section — Borland's CodeView 4 derivative (magic FB09) — produced when the target is built with -V (Project > Options > Linking > "Place debug information in EXE"). Decodes both stack-spilled (0x0200) and register-allocated (0x0002) parameter records; resolves names through the global names table; demangles procedure signatures via a tight Itanium-ABI-style parser (Borland's variant, with C1/C2/C3 ctor + D0/D1/D2 dtor tags). Auto-walks past kernel32!DbgBreakPoint at DebugBreak() to land on user code via RBP-chain or RSP-scan fallback.

Version-gating barriers at every layer: section magic, per-module language tag, per-record-type allowlist. A future Embarcadero format bump (FB0A) produces a structured version_warning wire error with fallback_hint and diagnostic_commandrdbgd64.exe --dump-tds <exe> is the bug-report payload — rather than silent miscompilation. v1 deliberately leaves type_name empty (type-table lookup defers to v2) and supports only frame_idx=0 (caller-frame walking is v1.x).

Step Over — direct-CALL skip (v1) Shipped

When the byte at RIP is 0xE8 (5-byte direct near-call — the dominant shape in Delphi-compiled binaries), step_over now plants a temp BP at RIP + 5 proactively and continues without setting the trap flag. Saves one kernel single-step round-trip per direct CALL on the most common Delphi codegen pattern. The wire response surfaces prearmed_call_skip:"0x..." when the new path fires; absent for fallback. Any non-E8 byte falls through to the existing TF + reactive-return-addr-BP loop — zero regression risk. ClassifyStepInstr in Rdbg.Cpu is the seam; v2 fills in indirect and tail-JMP cases.

Expression evaluator (path expressions) Shipped

evaluate wire method (and matching MCP tool) parses Pascal-style path expressions and walks them cross-process: Self.Caption, Form.Edit1.Text, 0x<hex>.ComponentCount. Returns the same per-prop wire shape inspect_object emits, so MCP / GUI / agent code paths render with zero extra logic. Reuses the existing inspect-object infrastructure: each dot step calls inspect_object internally, plucks the named field/property, and follows object_address to chain. Method-backed props go through cross-process RPC transparently; AV diagnostics carry through.

v1 deliberately covers dot-paths only. Indexing (Components[0]), arithmetic, type casts, and method calls with args all fail with parseable error reasons agents can branch on. Locals are now decodable via inspect_locals (above) and addressable by name in the evaluator is the natural follow-on. Adding them in v2 / v3 is a small grammar layer above the same evaluator.

Method-backed property values via parking + RPC (M17) Shipped

inspect_object eval_method_props:true cross-process-calls each property getter on the paused thread so values come back live — TForm.Caption, TEdit.Text, TStrings.Count, virtual getters, managed-result strings, records, sets, floats. Hijack the paused thread's CONTEXT, plant a return address at a 64 KB park region, drive to an INT3 sentinel, capture RAX/XMM0 + the hidden out-param slot, restore. Per-prop failures carry error_reason + (for AVs) target_exception_code/addr/data_addr/op — an agent can tell which pointer was bad without parsing EXCEPTION_RECORD. Bitness-clean: rdbgd64 (RCX/RDX, XMM0) and rdbgd32 (EAX/EDX, ST(0)) share the same RPC engine via a {$IFDEF CPUX64} shim.

Postscript: Caption initially AV'd inside user32!DefWindowProcW on x64. Five hypotheses later (was it threadstate? was it pCurMsg? was it CI_flags? was it CTI? was the documented API any safer?) the answer turned out to be a stack overflow: the 4 KB park region only gave callees ~2 KB of stack budget, and user32's wndproc-callback chain consumed more. Bumped to 64 KB and Caption decodes cleanly on both bitnesses.

Pascal syntax highlighting in the source viewer Shipped

Reserved words navy-bold, strings purple, comments green-italic, compiler directives olive-bold, numbers (decimal, float, exponent, Pascal hex $ABC, char literals #NN) navy. Multi-line { ... } and (* ... *) tracked across line boundaries. 119 reserved words + contextual keywords recognised, including method directives (virtual / override / stdcall / cdecl) and property-decl tokens (read / write / stored / default).

Win32 + Win64 daemons Shipped

v0.5 ships a parallel rdbgd32.exe alongside rdbgd64.exe, both built from the same source graph. Rdbg.Cpu shim parameterises every register / pointer-width / CONTEXT-flag site; PE parser handles both PE32 and PE32+. Wire protocol gains target_arch in hello + status, and get_context emits the right register names per ABI. Schtasks twin (RdbgDaemon32Interactive) lets SSH-spawned 32-bit daemons land in the user's interactive session.

WoW64 cross-bitness debug intentionally out of scope; pick the daemon that matches your target.

AI agent integration via MCP Shipped

rdbg-mcp.exe bridges the rdbg wire protocol to MCP (JSON-RPC 2.0 over stdin/stdout). The shipped rdbg-debugger plugin installs into Claude Code with two slash commands and ships skills, slash commands, and an autonomous bug-hunter subagent; ask your agent to reproduce a bug and it drives the daemon end-to-end — launch, breakpoint, stack walk, RTTI inspect, send_input. Tested with Claude Code; the underlying MCP bridge speaks an open protocol so other MCP-capable clients can wire it in directly.

Interrupt + detach Shipped

Mid-run pause and clean detach without killing the target. The run-loop's idle callback peeks the request socket on a 50 ms cadence so interrupt dispatches mid-run via DebugBreakProcess; the resulting first-chance BP surfaces as paused, reason:"interrupt". detach clears every installed BP, sets EFlags.RF on every live thread to suppress any pending HW BP on the resume instruction, then calls DebugActiveProcessStop. kill_session terminates the target outright. rdbgui's toolbar gained Pause + a Disconnect-or-Detach dropdown.

Multi-conn daemon followed: thread-per-accept, per-Conn context, zombie sessions surviving disconnect, bind_session / release_session for handing a paused target between clients.

Microsoft PDB symbol resolution Shipped

Per-session module table populated from LOAD_DLL_DEBUG_EVENT + in-target PE headers. On first stack walk through an unresolved DLL, the daemon synchronously fetches the matching PDB from Microsoft's public symbol server, caches it at %LOCALAPPDATA%\rdbg\symbols in the standard symsrv layout, and parses its Public Symbols stream so subsequent frames resolve to module!Symbol+offset.

Pure-Delphi MSF parser — no dbghelp.dll / symsrv.dll redistribution. Cache layout interoperable with dbghelp / symchk / Visual Studio. set_breakpoint accepts the same module!symbol form ({"symbol":"kernelbase!RaiseException"}); inspect_object annotates code-pointer fields and TMethod.Code slots.

Design notes: docs/plan-pdb-symbols.md.

Private / protected field walking Shipped

M16-followup decoded the Delphi 12 vmtFieldTableEx / vmtMethodTableEx layouts using Rdbg.RttiOffsets self-reflection. inspect_object now returns a unified props array with every visibility tag plus a methods array; record-typed fields decode per-field when the target opted in via {$RTTI EXPLICIT FIELDS}.

Input injection Shipped

Phases 1–4 of docs/plan-input-injection.md: list_windows enumeration, post_message, send_keys, send_key_sequence, click_at. Agents and GUIs can now drive a target's VCL UI cross-process.

Plus: persistent daemon (ServeForever); RAD Studio groupproj; daemon tray icon.

M1–M16 core Shipped

Sixteen milestones plus v1-plan gap closure.

  • M1–M2. Daemon scaffold; MAP-annotated stack traces with FPE fallback.
  • M3. JSON-RPC wire over TCP.
  • M4. Software and hardware breakpoints (M4.3).
  • M5. TObject inspection with recursive depth-capped expansion (M5.6).
  • M6. rdbgui — bespoke VCL GUI.
  • M7–M14. Project-oriented launch, source viewer, stepping, local/SSH/attach modes.
  • M15. Object explorer with typed property values across every tk*.
  • M16. Extended-RTTI properties + visibility tags + is_default annotation; Rdbg.RttiOffsets self-reflection.
Won't fix in v1.x

Out of scope, on purpose.

These aren't oversights — they're explicit non-goals so the v1.x line stays focused. v2 may revisit.

  • WoW64 cross-bitness debug. v0.5 ships rdbgd32 alongside rdbgd64 — pick the daemon that matches your target's bitness. A single daemon driving both bitnesses (cross-bitness attach via WoW64) is intentionally out of scope.
  • Non-Windows targets. Linux/macOS need ptrace — that's a different project, not a port.
  • TLS on the wire. Use an SSH tunnel for trusted-link transport.
  • Expression evaluation. rdbg reads memory and decodes values; no expression parser, no calling user code (except via the planned method-backed RPC path).
  • Concurrent multi-client daemon. --listen still serves one client at a time; sequential reconnects work since v1.2.

Want the full feature list?

Every shipped milestone is on the features page — what each one does, where it slots into the wire, and which client it lights up.

See features Get rdbg