Roadmap

Where rdbg is going.

The plan in one place. Bigger items are estimated in days, not vibes.

Shipped In progress Next up Future

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.

Cross-pollination — lsp_hover enriched with runtime values Future

The plan's headline differentiator over a vanilla LSP-MCP wrapper. When hovering on a variable name and a paused debug session has that variable's stack frame in scope, fold the runtime value into the hover markdown — "FCount: Integer — last observed value 42 at Form1.pas:128". Only possible because rdbg's static-analysis surface (DelphiLSP) and runtime-debug surface (rdbgd64/rdbgd32) share one bridge process; a pure LSP server can't do this. Deferred from v1 deliberately — the agent already gets the same answer in two calls (lsp_hover + inspect_locals); the auto-fold is the v2 ergonomic.

v0.19.00 — M1–M16 core Shipped

Sixteen milestones plus v1-plan gap closure. The bedrock everything else is built on.

  • 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.

v0.19.04 — Input injection Shipped

Cross-process input: 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.

v0.19.07 — 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.

v0.19.10 — Private / protected field walking Shipped

Decoded the Delphi 12 vmtFieldTableEx / vmtMethodTableEx layouts using Rdbg.RttiOffsets self-reflection (followup to the M15–M16 RTTI work in v0.19.00). 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}.

v0.19.13 — 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).

v0.19.16 — Win32 + Win64 daemons Shipped

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.

v0.19.19 — Interrupt + detach + multi-conn daemon 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 shipped in the same window: thread-per-accept, per-Conn context, zombie sessions surviving disconnect, bind_session / release_session for handing a paused target between clients. The daemon now serves multiple concurrent clients on one --listen.

v0.19.22 — 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 slash commands and ships skills + 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.

v0.19.25 — 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.

v0.19.27 — 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. Adding them in v2 / v3 is a small grammar layer above the same evaluator.

v0.19.29 — Step Over / direct-CALL skip 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; later versions will fill in indirect and tail-JMP cases.

v0.19.32 — 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 a later version) and supports only frame_idx=0.

v0.19.33 — DelphiLSP integration + bridge ergonomics polish Shipped

DelphiLSP integration. 12 new MCP tools backed by Embarcadero's DelphiLSP.exe (definition / declaration / implementation / hover / references / completion / signature help / document & workspace symbols / diagnostics / project open / aggregators), capability-gated per Delphi version. .delphilsp.json autogen from any .dproj — first open-source tool to do this. Multi-project registry with idle reap. rdbg-mcp.exe --lsp mode for VS Code / Neovim / Zed clients.

Bridge ergonomics polish. Cross-bitness hello; new bridge_config tool; accurate daemon CLI errors; stale paused events scrubbed before resume calls; set_exception_filter actually enforced; get_main_form HWND-scan fallback; inspect_object method-noise controls; classified exception frames; new read_param tool. Plus a translator destructor-race fix surfaced by cross-bitness probe testing.

v0.19.34 — Stack unwinder + LSP plumbing + step-over completion Shipped

.pdata / .xdata stack unwinder — primary walker on Win64; FPO_DATA on Win32. Microsoft's canonical x64 unwind format is now the default walker. Reads the loaded image's IMAGE_DIRECTORY_ENTRY_EXCEPTION via ReadProcessMemory, binary-searches the RUNTIME_FUNCTION array, follows UnwindInfoAddress into .xdata, and interprets the full UNWIND_CODE opcode set with full nonvolatile-register restore — not just SP/BP/RIP. UWOP_PUSH_NONVOL, UWOP_ALLOC_LARGE/SMALL, UWOP_SET_FPREG, UWOP_SAVE_NONVOL[_FAR], UWOP_SAVE_XMM128[_FAR], UWOP_PUSH_MACHFRAME, chained-info recursion. Win32 reaches parity in the same release via FPO_DATA records (PE Debug Directory IMAGE_DEBUG_TYPE_FPO) with PDB FrameData as the modern fallback for stripped binaries — per the project's arch-inclusivity rule, rdbgd32 ships parity with rdbgd64 in the same release. FP-chain demoted to last-resort fallback. Pure Delphi — no dbghelp.dll.

Caller-frame get_registers. get_registers now accepts frame_idx — pass 1, 2, ... to read up-stack frame state, not just the leaf. Walks via the new unwinder; returns the chosen frame's reconstructed CONTEXT plus a valid_registers array (RBX/RBP/RSI/RDI/R12–R15/XMM6–15 when an UWOP_SAVE_* covered them; never volatiles), and frame_source: "current"|"unwind"|"fpo_data"|"fp_chain" so callers know which path produced the answer. Sets up the next lift of inspect_locals's frame_idx=0-only restriction.

Step Over — indirect / tail-call decoding. v1 (v0.19.29) skipped only the 5-byte direct E8 rel32 CALL. v0.19.34 extends to indirect calls (FF /2, including the FF 50 xx virtual-method-dispatch shape everywhere in Delphi RTTI) and to both tail-JMP shapes (E9 rel32 direct, FF /4 indirect). Tail-jumps are the subtle case — they don't push a return address, so the v1 reactive return-addr-BP path can't see them. Trick: read [RSP] at the JMP — the original caller's return address is still sitting there because tail-jump preserves RSP — and plant the temp BP at that address. Wire response surfaces prearmed_kind: direct_call | indirect_call | tail_jump_direct | tail_jump_indirect.

LSP plumbing closes the discovery gap. All 13 lsp_* tools now appear in MCP tools/list from startup — previously hidden behind a registration-time probe that returned nil before any project opened, leaving agents stuck on a one-tool catalogue. Per-call dispatch already returned structured errors when no channel resolved; now the catalogue is static and gating moves to dispatch time. The bridge advertises capabilities.tools.listChanged: true too, future-proofing later dynamic-tool work. Plus a sibling lspServers: ./.lsp.json in plugin.json registers rdbg-mcp.exe --lsp as a Claude-Code-level LSP for .pas/.dpr/.dproj/.inc/.dpk, so editors get hover/completion/diagnostics natively for Delphi files — same code path that already powers VS Code / Neovim / Zed.

Per-file project routing in --lsp mode. v1 of --lsp bound a single DelphiLSP child at initialize from rootUri; files in sibling projects got results from the wrong symbol table, silently. v2 routes each textDocument/* message by its file URI through LspRegistry.Acquire — sibling channels are acquired lazily; out-of-tree files fall back to primary with a stderr warning. Server-initiated requests from any channel get their id swapped for a high-bit-tagged bridge id so multiple children's id spaces can't collide on the client side; the client's response is routed back to the originating channel with the native id restored. exit notifications broadcast to every live sibling.

Agent-DX polish. screenshot_desktop auto-downscales via StretchBlt + HALFTONE (default max_dimension 1024 → 800 to keep returns inside MCP's per-tool token budget on 4K displays); previously rejected oversized captures outright. events responses now carry idle_since_last_event_ms so agents can distinguish "target is genuinely idle" (e.g. waiting on a UI action you should drive) from "wire is silent because something broke" without having to send a probe RPC. get_main_form double-free that AV'd the session thread on too-early calls is fixed.

Unit-tested with canned UNWIND_INFO byte sequences (push-RBX-RDI-RSI prologue, alloc-stack-32, set-fpreg, mixed XMM saves, chained-info, machframe). 76/76 unit tests across the release. TLspStdioServer.WriteMessage is FWriteLock-protected so concurrent reader threads from sibling channels don't tear Content-Length frames in --lsp mode.

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.19.16 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.
  • 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 (v0.19.27) covers Pascal-style dot-paths 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.

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