Debugging a Build

FantASM writes two files a debugger can use: --sld, which maps source lines to addresses, and -e, a plain list of exported symbols. Neither changes the image; both are written alongside it.

With the SLD file you get breakpoints on source lines and a call stack naming your labels. Without it, a debugger has addresses and nothing else.

The Flags

fantasm build main.asm game.nex -d zxnext --sld game.sld -e game.labels

or, so nobody has to remember the flags:

[artifacts]
sld = "dist/game.sld"
labels = "dist/game.labels"

Both paths in fantasm.toml resolve against the directory holding it. With several images, each [[target]] can name its own — see Building Several Images.

The --sld File

sjasmplus-style SLD v1, which is a text format one record per line:

|SLD.data.version|1
t.asm|1||0|-1|-1|Z|pages.size:8192,pages.count:224,slots.count:8,slots.adr:0,8192,…
t.asm|15||0|4|32768|T|
t.asm|14||0|4|32768|L|,main,
t.asm|3||0|20|32777|L|Lib,First,,+used
t.asm|6||0|-1|16384|L|,SCREEN,,+equ
  • Z describes the machine’s paging, so a debugger knows the slot layout.
  • T maps a source line to an address. Breakpoints and the stack are built from these.
  • L is a symbol: module, name, and traits — +equ for a constant, +module and +endmod for boundaries, +used for something referenced.
  • K is an annotation you wrote — see Debugger Annotations below.

Records are page-aware: each carries a physical page as well as the Z80 address, so a debugger can tell two banked routines at the same address apart.

Debugger Annotations

!assert and !debug put a claim about the running program into the SLD, at the address they sit at, for the debugger to act on. Neither emits anything — the image is byte-for-byte what it would be without them.

    ld hl, 4
    !assert hl==4,"HL survived the copy"
    !debug  "entered draw_row"

!assert is for something that should be true, and a debugger acts when it is not. !debug has nothing to test and acts every time the address is reached.

The ! is optional, as on every directive — assert and !assert are the same thing. The record always names the canonical spelling, so nothing reading the file has to strip one.

FantASM does not read the text. Everything after the directive crosses to the file as written, and what a condition means is the debugger’s to decide — so its vocabulary can grow without a FantASM release. The price is that a mistake in one is not caught at assembly time; whatever reads the file is what finds it.

A trailing ; comment is not part of the annotation and does not cross. A ; inside quotes is just a character.

They do nothing without --sld. There is nowhere to record them, and the build says so once (W1120). Naming an SLD file is the only switch they have.

Inside a MACRO you get one record per expansion, at each address it reached, every one naming the invocation rather than the body line — that is the position FantASM reports for anything inside a macro. The body line is legible from the text the record carries.

A condition can contain |, as hl==4 || de==0 does, and the SLD’s own fields are |-separated. The annotation is the last field, so split on the first seven separators and take the rest whole.

The -e File

main        = 0x8000
Lib.First   = 0x8009

Only GLOBAL symbols appear. The file is empty until something is exported, which is working correctly rather than failing. A constant holding text is written as NAME = "text". Module labels are written with their qualified name.

This is for a loader or a script that needs one address, not for source-level debugging — the SLD file is what a debugger reads. See Modules for what GLOBAL does and does not do.

Bizmuth

The 0x1DE extension (The Language Server) drives the whole cycle from the editor. Its Assemble task runs

fantasm build <source> <source>.nex -t <target> --sld <source>.sld

so the debug info is always beside the image and always current. ⚠ That -t is the old spelling, so every build the extension drives now says W1103 once until 0x1DE moves to -d. Run on Bizmuth then launches the emulator and attaches.

A launch configuration takes program, host, port and stopOnEntry; attaching to an already-running Bizmuth takes host and port. The defaults are 127.0.0.1 and port 11001.

The SLD file is found from the program namegame.nex looks for game.sld — or you can name one explicitly. Without it the session still runs, but at address level only: stack frames show $PC rather than a source line, and breakpoints do not verify.

Limitations

A breakpoint inside a MODULE used to be broken and is not. Trace records for a module body carried the address the body was read at rather than where it was placed, so a breakpoint on one resolved into ROM and never hit. The decide phase now settles every record against where the body landed, page included — so a breakpoint in a module works like any other. -vv reports the same placement if you want to see it from the build side.

A !test block is not debuggable. Tests run inside the assembler on its own simulator (Testing) and never reach the image, so there is nothing for an emulator to attach to. -v and assert_cycles are the tools there. !assert is the other way round: it never runs during a build, and only something reading the SLD can act on it.

Further Reading

  • The Language Server — the editor side, and the rest of what 0x1DE provides.
  • Testing — catching it at build time instead, which needs no debugger at all.
  • ModulesGLOBAL, and what ends up in the labels file.

Documentation