Pyromaniac

Using GDB with RISC OS Pyromaniac

Introduction

RISC OS Pyromaniac includes a GDB Remote Serial Protocol (RSP) stub, so a real gdb (or arm-none-eabi-gdb/gdb-multiarch) can attach to a running Pyromaniac instance and drive it with standard GDB commands - break, continuestepinfo registersxwatch, and so on - instead of (or alongside) the sysrq-based tools (docs/PYRODEBUG.mddocs/PYROMCP.md).

The stub is a second, independent client of the same sysrq substrate those other tools use: everything it does is translated to and from ro.sysrq(...) calls (regs-list/regs-setmemory-read-bytes/memory-write-bytes, debug-resume/debug-stepwatchpoints-add/tracepoints-add, ...) rather than talking to the emulator directly. See docs/FEATURES.md's GDBStub section for the feature summary, and PLANGDB.md for the staged design this was built from, including notes on what could and couldn't be verified against a real gdb client while it was being built.

RSP is a documented, open wire protocol, not GDB's own source code, so implementing a server that speaks it carries no GPL obligation.

Requirements

Starting a session

There are two ways to get a Pyromaniac instance for GDB to talk to, built from independent existing configuration options rather than a single stub-specific mode:

Attach to an already-running instance

Start Pyromaniac with the stub enabled and something to run - a persistent instance (pyro_server.py) is usually more convenient than a one-shot pyro.py invocation, since the process needs to still be there when gdb connects:

$ scripts/pyro-server --config gdbstub.enable=true

Then, from another terminal:

$ gdb
(gdb) target remote :1234

You can debug-pause (via docs/PYRODEBUG.md's CLI, the raw sysrq socket, or MCP) before or after connecting - RSP assumes the target is already halted, so GDB's first ? query will report "no signal" until something has actually paused execution.

Launch already halted at boot

To have RISC OS start already halted - before any RISC OS code, including resident module initialisation, has run - add emulation.pause_at_boot=true:

$ scripts/pyro-server --config gdbstub.enable=true --config emulation.pause_at_boot=true

The stub's socket is already listening by the time the halt takes effect (resources are provisioned before that point is reached), so you can connect and set breakpoints before the first instruction executes:

$ gdb
(gdb) target remote :1234
(gdb) break *0x8000
(gdb) continue

Configuration options

Option Default Meaning
gdbstub.enable false Start the GDB RSP stub.
gdbstub.port 1234 TCP port the stub listens on (GDB's own conventional default).
emulation.pause_at_boot false Start already halted, before any RISC OS code runs - equivalent to an immediate debug-pause issued before boot.

Any of these can also go in a --config-file instead of individual --config switches - see docs/CONFIGURATION.md.

What the stub implements

Example session

$ scripts/pyro-server --config gdbstub.enable=true --config emulation.pause_at_boot=true
$ gdb
(gdb) set architecture arm
(gdb) target remote :1234
Remote debugging using :1234
(gdb) info registers
r0             0x0                 0
...
pc             0x8000              0x8000
(gdb) x/4i $pc
(gdb) break *0x80a0
(gdb) continue
(gdb) stepi
(gdb) watch *(int *) 0x8030
(gdb) continue
^C
Program received signal SIGINT, Interrupt.
(gdb) monitor debug-backtrace
(gdb) monitor where 8030
(gdb) monitor watchpoints-list
(gdb) monitor debug-trace-log
(gdb) detach

Notes on the commands above:

Using LLDB instead

LLDB's remote-debugging transport is the same GDB Remote Serial Protocol gdb uses (with its own additional packets on top), not a competing wire format, so lldb can attach to the same stub directly - there is nothing LLDB-specific to enable on the Pyromaniac side:

$ lldb
(lldb) gdb-remote localhost:1234

Register access works because the stub answers LLDB's qRegisterInfo queries (see above); memory (m/M), execution (c/s), and breakpoints/watchpoints (Z/z) are all plain RSP and work the same as they do for gdb. As with gdb, there is no symbol table, so set breakpoints by raw address (breakpoint set --address 0x80a0 or b *0x80a0) rather than by name.

Limitations

Relationship to other clients

The GDB stub, scripts/pyro-debug/pyro_debug.py (docs/PYRODEBUG.md), and the MCP server (docs/PYROMCP.md) are three independent clients of the same underlying sysrq substrate, useful in different contexts: GDB (or LLDB, see above) for register/memory/breakpoint work through a real debugger's UI, the MCP server for AI agent tool integration, pyro_debug.py for a quick interactive session from a terminal. Nothing one of them does affects whether the others are available - gdbstub.enablesysrqserver.enable, and the MCP server's own transport can all be turned on at once against the same instance.