aboutsummaryrefslogtreecommitdiffstats
ModeNameSize
-rw-r--r--.gitignore234logstatsplainblame
-rw-r--r--README.md6499logstatsplainblame
-rw-r--r--TUI_DRIVING_BLUEPRINT.md18782logstatsplainblame
-rwxr-xr-xdrive7898logstatsplainblame
-rw-r--r--driver.py20153logstatsplainblame
-rw-r--r--pane.py6504logstatsplainblame
d---------tests36logstatsplain

gdb-driver

Puppeteer a live gdb (plain CLI + GEF, not -tui) programmatically over a unix socket, while it renders normally in a tmux pane. An embedded Python plugin runs an RPC server on gdb's own thread, so debugger state is read and written directly — and every driven command is echoed at the gef> prompt exactly as if a human typed it.

It was built for agent-driven dynamic analysis on a livestream: the pane is a real, watchable gdb session (GEF's colored context is what a viewer sees), but a script is doing the driving.

  your script ──JSONL──▶ unix socket ──▶ driver.py (in gdb's Python)
                                              │  marshals onto gdb's thread
   tmux pane  ◀── gef> echo + GEF context ────┘  reads/writes real state

Why

gdb automation usually means GDB/MI or brittle expect scraping. Neither gives you both a clean structured API and a genuine, human-looking terminal session. gdb-driver does:

  • Three tiers of control — raw tmux keys, semantic verbs (run-control / breakpoints / eval), and structured introspection (regs / memory / backtrace as data).
  • Real stop-event settling. Mutating verbs return only after gdb's actual stop/exited event fires — no sleeps, no guessing. The returned state is final.
  • Visible by construction. Everything is echoed at the prompt and rendered by GEF, so the pane is legible to a human (or a stream) the whole time.
  • The escape hatch is first-class. drive cmd "<any gdb/GEF command>" runs anything — disassemble, vmmap, checksec, even in-gdb python … — so you are never blocked by a missing verb.

All verbs go through gdb's CLI path (GEF-compatible). GDB/MI is not used.

Requirements

  • Linux, running inside tmux (the driver spawns/handles panes).
  • gdb 17+ with GEF auto-loaded (~/.gdbinit~/.gef.py).
  • python3 (stdlib only) for the drive/pane client CLIs. The driver.py plugin runs inside gdb's own embedded Python.

No pip install, no dependencies — it's three files and the stdlib.

Quickstart

cd gdb-driver   # so ./drive resolves (it's a plain executable)

# 1. spawn a pane with gdb on a target (+ program args). Blocks until ready,
#    then prints:  ready pane=%N sock=/run/.../gdb-XXXX.sock
./drive spawn /abs/path/to/binary [prog-args...]
#    forward environment to the inferior (shim libs, LD_PRELOAD, …):
#    ./drive spawn --env LD_LIBRARY_PATH=/path/to/libs ./target arg1

# 2. drive it. ./drive auto-finds the socket when one pane is live (no --sock).
./drive where                 # main @ 0x555… at file:line (or func+0xoff) [stopped]
./drive break main            # b *main+42 | b <func> | tbreak …
./drive run                   # settles at the next real stop (breakpoint/exit)
./drive bt / regs / mem '$sp' 64
./drive print '(char*)$rdi'; ./drive cont; ./drive finish
./drive cmd "disassemble main"    # ANY gdb/GEF command (the escape hatch)
./drive set '$r14d = 5'           # patch a register live
./drive screen [color]            # tmux capture of the pane (GEF context)

# 3. tear down (graceful quit + kill pane + rm socket). Never leak panes.
./drive stop
./drive list [--prune]            # inventory; --prune drops dead panes

Socket resolution for ./drive: --sock <path> (anywhere on the line), else $GDB_DRIVER_SOCK, else the single live pane (with several, it lists them). Calls are sequential — single-driver, one connection at a time; a second concurrent request gets busy.

Command surface

  • Lifecycle / meta: ping · methods (self-documenting verb table) · quit · spawn · list [--prune] · stop
  • Read (never settle): state/where · regs · bt [n] · mem <addr-expr> [len] · breakpoints/bp · screen [color]
  • Run-control (settle on the real stop/exited event): run [args] · start · cont/c · next [n]/n · step [n]/s · stepi/si · nexti/ni · finish · until [loc]
  • Breakpoints: break <loc>/b · tbreak <loc> · delete [n] · watch <expr>
  • Eval / examine / patch: print <expr>/p · x <fmt> <addr> · set <expr> (vars and registers: set '$rax=1') · frame <n> · up/down
  • Escape hatch: cmd <any gdb/GEF command…>
  • Raw keys: keys <tmux-key…> (e.g. keys C-c to interrupt a running inferior) — client-side tmux send-keys, so it works even while a cont is blocking.

Mutating verbs settle, then return a fresh {cmd, state[, output, note]}. Reads return immediately; a program-dependent read before the inferior is stopped returns a clean not ready error rather than garbage.

Layout

file what
driver.py the embedded gdb-Python plugin: unix-socket JSONL server on a background thread, marshalling to gdb's thread via post_event, all verbs, run-control stop-event settling, and gef>-prompt echoing.
pane.py tmux spawn/stop/list, registry under $XDG_RUNTIME_DIR/gdb-driver/, readiness poll, socket resolution, send_keys.
drive the ergonomic CLI (terse text, auto socket). Use this all day.
tests/smoke.py end-to-end: compiles a fixture, spawns, drives the whole surface, ~28 asserts. A protocol-regression lock.
TUI_DRIVING_BLUEPRINT.md the design / generalization reference (this is a Level-2 embedded adapter).

Testing

python3 tests/smoke.py     # compiles a fixture, exercises the full surface

Gotchas worth knowing

A few that will bite (the blueprint and inline comments have the rest):

  • Break at the call, not the mov/lea before it — argument registers ($rdi/$rsi/…) are only set at the call.
  • PIE raw-offset breakpoints fail before load (break *0x1275Cannot access memory). Use symbol-relative: break *main+<off> so gdb relocates it.
  • Plugin changes need a respawn — editing driver.py won't affect a running gdb; ./drive stop && ./drive spawn … reloads it.
  • fork + ptrace anti-debug targets (a parent↔child PTRACE_SEIZE coroutine) do not reproduce native behavior under gdb — a process can have only one tracer. Observe those with eBPF/bpftrace (orthogonal to ptrace) instead of trusting gdb-extracted intermediate state.

License

No license yet — all rights reserved by the authors pending one.