Bloodhound logo

Rust · reverse debugging

Step backward
through a bug.

Bloodhound is a portable time-travel debugger built on a small self-contained stack VM. Most debuggers only move forward, so overshooting the moment a bug appears means restarting. Bloodhound records every instruction it runs, so you can step back, jump to any earlier step, and reconstruct the exact machine state forward execution had there. The stack, memory, locals, call frames, and program counter all rewind together. Pick a sample, set a breakpoint, and scrub the timeline in both directions.

Open the debugger View on GitHub
step 0

Source (click a line to toggle a breakpoint)

Call stack

Value stack

Locals (current frame)

Globals

watch global #

Linear memory

Output

How to use this playground
Guided tour: loading...

The controls

Reading the views

A short guided tour

  1. Leave sum_loop selected. Click the source line sum += i to set a breakpoint (its gutter turns red).
  2. Press Continue a few times and watch global[0] climb 1, 3, 6, 10, 15 as the loop runs.
  3. Set the watch index to 0 and press Toggle watch. Continue once more and read the reported old to new change.
  4. Now press ◀ Back several times. The sum counts back down. The print in the output un-happens. That is reverse execution.
  5. Drag the timeline to the far left, then to the far right, to leap across the whole run.
  6. Switch to factorial and use Continue to a breakpoint inside recurse, then read the deep call stack.

Reverse execution that is a true inverse

The debugger's claims are backed by a machine-checkable oracle in tests/gate.rs. It builds a ground-truth forward trace that snapshots the full VM state at every step, then checks each feature against it. Reverse execution is proven to be an exact inverse, not an approximation.

# tests/gate.rs, checked against the ground-truth forward trace
time-travel reversibility   forward K then back K is byte-identical, goto(N) rebuilds step N
breakpoint correctness      stops at exactly the breakpoint addresses, nowhere else
watchpoint correctness      fires on exactly the steps the watched cell changes
step-over / step-out        same-frame next line, and return of the current frame
conditional breaks + watchescompared to an independent Rust predicate, both directions

# more random programs, longer run:
BLOODHOUND_FUZZ_OPS=400 cargo test

This page reaches the same result the Rust engine does. It runs the program once and deep-clones the state after every step, so every motion command is index math over that history; the Rust engine reaches the identical state with a per-instruction undo journal. Conditional-breakpoint expressions are total and strictly read-only, so evaluating a condition can never disturb the machine.

The primitives that matter

Six primitives of a time-travel debugger.

Every one is a real command you can drive above or from the REPL, each small enough to read in the source.

time travel Reverse step

back moves one instruction backward in time and restores the exact earlier state. The Rust engine does it with a per-instruction undo journal.

scrub goto any step

goto <step> jumps to an absolute step index, forward or back, reconstructing the state forward execution had at that point.

breakpoints Line and address, conditional

break <line> and breaki <addr>, each optionally if <expr>. Reverse-continue with rc stops at the previous breakpoint.

watch Data watchpoints

watch a global, memory cell, or local. It fires on exactly the steps the watched location changes, matching a reference diff of the trace.

stepping Into, over, out

s, n, and out work over source lines: descend into calls, run a call to completion, or return from the current frame.

expr Safe condition language

Conditions read pc, depth, top, globals[e], and memory[e]. Evaluation is total and strictly read-only, so a condition can never disturb the machine.

The portable, from-scratch reverse debugger

Reverse debuggers exist. Bloodhound trades their reach over native processes for something small, deterministic, and identical on every platform.

rr

Records and replays a native process, powerful for real programs but bound to Linux and specific hardware and heavyweight to set up.

gdb record and replay

Adds reverse execution to native targets, tied to gdb and platform-specific process control.

WinDbg time travel debugging

Captures full traces of Windows processes, a large tool built for a single operating system.

Bloodhound

Debugs its own tiny stack VM instead of a native process, so it is one dependency-free Rust crate that behaves identically everywhere and whose reverse execution is checked by a machine oracle. Small enough to read in one sitting, safe enough for an agent to drive.

Use it

A REPL over a handful of built-in samples, an assembly loader, and the correctness gate.

REPL

cargo run loads a sample and drops into the debugger. Type help for every command and src to see the program.

samples and files

cargo run -- list shows the built-in programs; cargo run -- file prog.asm loads your own assembly.

scripted demo

cargo run -- demo runs the scripted demonstration and exits.

the gate and stress suite

cargo test runs the oracle in tests/gate.rs and the stress suite; the fuzz and stress scale knobs are environment variables.

# load the factorial sample into the REPL
cargo run

# run the scripted demonstration and exit
cargo run -- demo

# load a built-in sample by name, or list them
cargo run -- sum_loop
cargo run -- list

# load an assembly file
cargo run -- file prog.asm

# the unit tests and the correctness gate
cargo test

# inside the REPL: c / rc / s / n / back / goto <step> / break / watch