Copperline can run as a headless GDB remote target:
./target/release/copperline --config copperline.example.toml --noaudio --gdb :2345Port-only forms (2345 or :2345) bind to 127.0.0.1. Use an explicit
address such as 0.0.0.0:2345 only on a trusted network: the remote
protocol can read and write guest RAM and can resume the emulated machine.
Connect from GDB with the 68k architecture selected:
(gdb) set architecture m68k
(gdb) target remote :2345The target starts paused at reset. The stub implements the normal all-stop
remote packets for register access, RAM reads/writes, hardware-style PC
breakpoints, memory watchpoints, single-step, continue, Ctrl-C interrupt,
program load events (qXfer:libraries:read), and GDB reverse execution
(reverse-step / reverse-continue). Detaching leaves the machine
paused and the stub listening, so a later target remote picks up the
same session (and re-runs qOffsets); GDB's kill ends the emulator.
If monitor (or anything else) answers "monitor" command not supported by this target., GDB is not actually connected: that is GDB's built-in
dummy target speaking after a failed target remote, and -batch
scripts carry on past the connection error. A common cause is running
GDB inside a container (e.g. a cross-toolchain Docker image): the
container's localhost is not the host's, so it never reaches
Copperline's 127.0.0.1 listener. Run GDB on the host, or bind an
address the container can reach (--gdb 0.0.0.0:2345, trusted networks
only per the warning above) and connect to the host's address.
GDB sees the core 68000 register set as d0-d7, a0-a6, sp, ps,
and pc. Register writes go through Copperline's CPU wrapper so SR stack
banking and interrupt state stay coherent.
Generic GDB memory packets are intentionally conservative:
- Reads use a side-effect-free CPU-visible RAM/ROM view, including the boot ROM overlay.
- Writes modify RAM-backed regions only: chip RAM, trapdoor slow RAM, and configured RAM expansion boards.
- Writes to ROM, overlay ROM, custom chips, CIA, RTC, IDE, SCSI, CD, and
other device windows are ignored by
Mpackets.
Use monitor write-reg for deliberate custom-chip writes.
The stub is exercised against real clients: m68k-amigaos-gdb (the
bebbo amiga-gcc toolchain) connects, disassembles live ROM, steps,
breaks, and reverse-steps as expected. A stock multi-arch host gdb
also accepts the target description, but without an executable loaded
it guesses endianness from the host -- issue set endian big before
target remote (or just load your program's ELF with file, which the
Amiga toolchains' gdb does implicitly).
Use GDB's monitor command for Amiga-specific state:
(gdb) monitor status
(gdb) monitor beam
(gdb) monitor custom
(gdb) monitor reg DMACON
(gdb) monitor reg DFF100
(gdb) monitor write-reg COLOR00 00fCustom-register inspection is side-effect-free. It reads Copperline's
internal Agnus/Denise/Paula/blitter latches rather than executing a real CPU
read from $DFFxxx, so it will not acknowledge interrupts, clear latches, or
advance collision/audio state. write-reg is different: it routes a word
write through the normal custom-register write path and therefore has real
hardware effects.
Register names match the debugger window (DMACON, BPLCON0, COLOR00,
AUD0VOL, and so on). Numeric offsets (96) and full custom addresses
(DFF096) are also accepted.
monitor beam-trap VPOS [HPOS] (decimal, like monitor beam's output)
toggles a beam trap: continue then stops when the Agnus beam reaches that
position, at exact colour-clock granularity, even while the CPU sits in
STOP. HPOS omitted means the start of the line, and a trap re-fires
every frame until removed. monitor clear-beam-traps removes them all.
(gdb) monitor beam-trap 100
(gdb) monitor beam-trap 100 40
(gdb) monitor clear-beam-trapsmonitor copper-break ADDR (hex) toggles a Copper breakpoint: continue
stops when the Copper's PC arrives at that list address, before the
instruction there executes. monitor clear-copper-breaks removes them all.
(gdb) monitor copper-break c01000
(gdb) monitor clear-copper-breaksThe Copper list can be dumped from the live list pointer, the current Copper PC, or an explicit chip-RAM address:
(gdb) monitor copper
(gdb) monitor copper pc 20
(gdb) monitor copper 00c01000 80Counts are hexadecimal, matching GDB's packet syntax and Copperline's other debugger address inputs.
--gdb arms the same snapshot-ring reverse debugger used by the window and
headless reverse watchpoint. GDB commands map as follows:
| GDB command | Copperline operation |
|---|---|
reverse-step |
reconstruct the previous instruction boundary |
reverse-continue |
run backward to the previous GDB PC breakpoint |
monitor last-writer ADDR |
find the last instruction that changed the watched word |
Reverse history uses COPPERLINE_DBG_RR_BUDGET_MB and
COPPERLINE_DBG_RR_INTERVAL, with the same tradeoff as the other frontends:
more memory and more frequent snapshots make reverse operations faster.
For byte-identical replay, keep the usual determinism requirements from
: set COPPERLINE_RTC_FIXED_SECS when guest RTC reads matter, and
avoid externally mutating hard-drive/CD images during a debug session.
What symbol detail you get depends on the file you feed GDB, not on the stub:
- An AmigaOS hunk executable built with
-g(bebbo's amiga-gcc) carries a hunk symbol table:file proggives function-level symbols --break main, named disassembly -- but no DWARF, so source lines,next, andprinton variables are unavailable. Reading hunk files at all requires a GDB whose BFD includes theamigatarget (check withm68k-amigaos-objdump -i); some builds of the same toolchain lack it and report "file format not recognized". - Full source-level debugging needs an ELF file with DWARF kept
alongside the hunk binary you actually run: build with an m68k ELF
toolchain and convert the executable with
elf2hunk(the flow the amiga-debug VS Code extension uses), then load the ELF's symbols withadd-symbol-file prog.elf ADDRor through the library list below.
Either way the symbols must land at the addresses LoadSeg() chose.
Copperline answers GDB's qOffsets query with the load addresses of the
current process's segment list: TextSeg= is the first hunk and
DataSeg= the second, when present. GDB asks qOffsets once at attach,
so this relocates a program that is already running: start prog from a
CLI inside the emulator (so the process has a cli_Module seglist),
then m68k-amigaos-gdb prog + target remote localhost:2345 places
prog's symbols at the loaded hunk addresses. When no process seglist
is walkable (ROM code, task rather than process, OS not up yet), the
qOffsets reply is empty and GDB falls back to link-time addresses --
harmless for ROM-level sessions.
For a program AmigaDOS loads after GDB is connected, the stub tracks
the scheduled process's segment list and reports the load at the moment
LoadSeg()'s result is installed -- after relocation, before the
program's first instruction.
The workflow that works with every client, -batch scripts included:
(gdb) target remote :2345
(gdb) monitor loadseg-break
(gdb) continue
loadseg: hello first hunk $018FE8 (monitor segments / add-symbol-file FILE 0x18FE8)
Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb) disconnect
(gdb) file hello
(gdb) target remote :2345
(gdb) break main
(gdb) continuemonitor loadseg-break makes continue stop, with a console hint
naming the program and its first hunk, whenever a new program is
loaded. Detaching does not end the emulator: the machine stays paused
and the stub listens for the next connection, so reattaching with the
program's file loaded re-runs qOffsets against the now-current
process and relocates the symbols with no manual addresses (GDB's
kill command ends the server). Alternatively stay attached and use
add-symbol-file FILE ADDR with the printed hunk address; monitor segments prints the full hunk map, and the SEGMENTS console command
prints the same map pre-formatted with that hint.
Clients that request GDB's target library lists get automatic load
events instead: fetching qXfer:libraries:read once arms them, and
each new program then reports a library: stop event -- GDB re-reads
the stub's library list (one <library> per tracked program, a
<segment> per hunk, named by the command's basename), relocates a
matching file found via set solib-search-path, binds pending
breakpoints, and resumes on its own. Be aware that stock bare-metal
m68k GDB builds -- m68k-amigaos-gdb 13.x and a multi-arch host gdb
alike -- never request library lists, so they never see these events;
use the monitor loadseg-break flow with them. Programs already
running when the list is first fetched are reported in it without a
stop; monitor loadseg-list prints the same table.
| Command | Effect |
|---|---|
help |
list monitor commands |
status |
CPU PC/SR, frame, beam, instruction position, reverse status |
beam |
beam/frame/colour-clock position |
custom |
compact custom-chip state dump |
stepover |
step over a BSR/JSR/TRAP call (single step otherwise) |
finish |
run until the current subroutine returns to its caller |
reg NAME|OFFSET |
side-effect-free custom-register latch read |
write-reg NAME|OFFSET VALUE |
real custom-register word write |
watch-reg NAME|OFFSET |
stop on CPU or Copper writes to the custom register |
unwatch-reg NAME|OFFSET |
remove one custom-register watch |
clear-reg-watches |
remove all custom-register watches |
copper [auto|pc|ADDR] [COUNT] |
disassemble Copper instructions |
beam-trap VPOS [HPOS] |
toggle a stop when the beam reaches that position |
clear-beam-traps |
remove all beam traps |
copper-break ADDR |
toggle a stop when the Copper PC reaches ADDR |
clear-copper-breaks |
remove all Copper breakpoints |
last-writer ADDR |
reverse-search the last write to a word |
segments |
the current process's loaded hunks (LoadSeg addresses) |
loadseg-break |
toggle a visible stop whenever a new program is loaded |
loadseg-list |
tracked program loads with their hunk addresses |
execbase |
ExecBase scheduler state (dispatch counters, Disable()/Forbid() nesting, AttnFlags) and the machine facts exec recorded at boot |
tasks |
the scheduled task plus the ready and waiting lists |
task [ADDR|NAME] |
one struct Task in full -- flags, signals, trap/exception vectors, stack use, and the process/CLI half when it has one (no argument: ThisTask) |
memlist |
exec's memory list with free, largest chunk, and attributes per region |
These four are the same dumps the debugger console prints for EXECBASE,
TASKS, TASK, and MEMLIST (see ), so a headless
--gdb session sees exactly what the window would show. When the OS is
not up yet, they say so rather than printing garbage.