Building programs with z88dk¶
JNEXT reads the map z88dk writes with -debug -m. This page is the reference
for building a program so that the Source tab works: which options, which
files must end up where, which compilers and libraries work on which z88dk,
and what changes for tape builds and banked code. A first session
uses the first line below.
Every command line here was run with z88dk 2.4 (commit 4d530b6eb7, sdcc 4.5.0) and with a master build (commit 0f0a1cf70e, v2.4-2954, sdcc 4.6.0), unless a table or a section says one of them is different.
For everything that z88dk master has fixed in its debug records, install a nightly named z88dk-20261106-* or later, or build z88dk master from source; z88dk 2.4 works with the limits listed in C variables.
The recommended build¶
zcc +zxn -clib=new -debug -m --list -subtype=nex -create-app -o prog main.c util.c asmfn.asm
| Option | What it gives you |
|---|---|
-debug |
the C line records in the map (__C_LINE_ symbols). With sccz80 and 80cc it also changes the program: see below |
-m |
writes prog.map, the file JNEXT reads |
--list |
writes prog.lis and one <source>.lis beside each source: every assembly line, every address of a C line, exact extents, and the text of the start-up code. With 80cc it is also what lets JNEXT tell which block locals are in scope (the map keeps one scope per line, the listing every occurrence) |
-subtype=nex and -create-app |
write prog.nex, the loadable file (see the targets below) |
-clib=new |
the sccz80 compiler with the new library. A bare zcc +zxn uses sdcc: zxn.cfg sets -clib=sdcc_iy -O2 -SO2 |
The minimum is -debug -m. With it JNEXT gets the C lines and the symbols; for
the example program that is 46 source lines instead of 281 with --list.
Loading prog.nex gives (z88dk 2.4):
Loaded 332 z88dk symbols from '/home/you/prog/prog.map'
Loaded 281 z88dk source lines from '/home/you/prog/prog.map' and 4 listings (program identity verified)
and 330 symbols and 286 lines on the master build (0f0a1cf70e).
What must be beside what¶
| File | Where | Why |
|---|---|---|
prog.map |
beside the program, with the same stem | found by itself when the program is loaded |
prog_CODE.bin (and prog_<SECTION>.bin for banked sections) |
beside the map | the binary identity: JNEXT compares the bytes of every code range with the program in memory. z88dk's linker writes them with or without -create-app; -create-app makes the loadable file |
prog.lis |
beside the map | the start-up code's text (labelled m4 output, from prog.lis) |
main.c.lis, util.c.lis, asmfn.asm.lis |
where zcc wrote them: beside each source | found there, whatever the map's directory |
| the sources | beside the map, in a parent directory, or on the search path | where the Source tab finds the files |
If the map is not beside the program, load it by hand with Load Source
Map... in the Source tab, or with --source-map FILE after --load.
A build in another directory works too. This was run from build/ with the
sources in ../src/:
cd build
zcc +zxn -clib=new -debug -m --list -subtype=nex -create-app -o prog ../src/main.c ../src/util.c ../src/asmfn.asm
prog.nex, prog.map, prog.lis and prog_CODE.bin land in build/, the
.lis files land beside the sources (src/main.c.lis), and JNEXT finds them
there. The map records ../src/main.c, and the Source tab finds the file.
Which compiler and library work¶
| Build | z88dk 2.4 (4d530b6eb7) | z88dk master (0f0a1cf70e, v2.4-2954) | C variables (the Variables tab) |
|---|---|---|---|
sccz80, newlib: +zxn -clib=new -debug -m --list |
works: 281 lines. Part of a statement can show on the next line | works: 286 lines | yes, with the limits listed in C variables |
sdcc, newlib: +zxn -clib=sdcc_iy -debug -m --list |
does not build: main.c:224: error: syntax error ^---- defc Gcounter0_00==.. Use --c-code-in-asm instead (next row) |
works: 222 lines. Code from a header can show under the wrong line | 2.4: no. Master: yes |
sdcc on 2.4: +zxn -clib=sdcc_iy --c-code-in-asm -m --list (no -debug) |
works: 230 lines, from the listings alone | not needed | no (no records without -debug) |
80cc: +zxn -clib=new -compiler=80cc -debug -m --list |
not available | works: 240 lines. Some lines are shared | yes, with the 80cc note |
classic on the Next: +zxn -clib=classic -debug |
fails: Unknown option2 --hardware-keyboard |
works: 187 lines | 2.4: no. Master: yes |
classic on the Next, no -debug: +zxn -clib=classic -m --list |
builds: 171 lines, from the listings. No prog_CODE.bin, so the identity is not verified |
builds: 173 lines, same | no |
classic on the 48K Spectrum: +zx -debug -m --list (no -clib: classic is the default) |
works: 251 lines. No prog_CODE.bin: (program identity not verified: no section binaries beside it) |
works: 254 lines, same | yes |
A line count is the number JNEXT logs when it loads the map (Loaded N z88dk source lines from ... and 4 listings), for the example program of this page (main.c, util.c,
asmfn.asm) built with the commands in the table, with each of the two z88dk versions named in the header. A newer z88dk gives other numbers.
Notes:
- On
+zxclassic is the default library; do not pass-clib=classicthere (z88dk answersCannot find definition for -clib=classic). On+zxnit is the other way round: sdcc is the default, and-clib=newselects sccz80 with the new library. - sdcc writes its C lines into the listings only with
--c-code-in-asm. With--listbut without it, JNEXT says so and uses the map alone:z88dk listings for 'prog.map': main.c.lis: it has no line information (sdcc writes it with --c-code-in-asm). - The
--hardware-keyboardfailure is a z88dk bug that is fixed upstream (in z88dk master from 2026-10-23); master builds from v2.4-2883 have it fixed, a release may not. - A
.lisfile from a different build is detected and dropped with a warning; see Limits and troubleshooting.
Other targets¶
-subtype and -create-app choose the file you load. All of these were built
with -clib=new -debug -m --list and load with the map attached:
| For | Command (after zcc) |
Writes | JNEXT reports |
|---|---|---|---|
| a NEX | +zxn -subtype=nex -create-app -o prog |
prog.nex |
identity verified |
| a 48K tape | +zx -create-app -o prog |
prog.tap |
identity pending, then verified (below) |
| a tape for the Next | +zxn -subtype=tap -create-app -o prog |
prog.tap |
identity pending, then verified |
| a snapshot | +zx -subtype=sna -create-app -o prog or +zxn -subtype=sna -create-app -o prog |
prog.sna |
identity verified |
Without -create-app the map and prog_CODE.bin are written, but no loadable
file. -o prog.bin instead of -o prog also works and names the map prog.map
as before.
A program loaded from tape is not in memory when the load call returns, so its binary identity is checked later, the first time the CPU runs the code the map describes. This is the log of the 48K tape build (z88dk 2.4 and master alike):
Loaded 337 z88dk symbols from '/home/you/prog/prog.map'
Loaded 281 z88dk source lines from '/home/you/prog/prog.map' and 4 listings (identity check pending: the program loads from tape)
Verified z88dk source map '/home/you/prog/prog.map': the program loaded from tape matches its binary identity
(338 symbols on the master build.) More about tapes is in loading a source map.
Banked code¶
A function that lives in a bank, built with z88dk's __banked on the Next, is
matched to its source only while its 8K page is mapped. The two steps
(-DUSE_BANKED_KW makes banked.h use __banked; the sources are
bmain.c, banked.c, banked.h and bankin.asm in test/fixtures/z88dk/src/):
zcc +zxn -clib=new -debug -DUSE_BANKED_KW --codesegPAGE_20 --list -c banked.c -o banked.o
zcc +zxn -clib=new -debug -DUSE_BANKED_KW -pragma-define:CRT_ORG_PAGE_20=0x14C000 -pragma-define:REGISTER_SP=0xC000 -m --list -subtype=nex -create-app -o prog bmain.c bankin.asm banked.o
Give both pragmas. CRT_ORG_PAGE_20 carries the page in its top byte
(0x14C000 is page 20 at $C000) and REGISTER_SP=0xC000 keeps the stack below the
banking window. Without them the program itself calls page 0 or loses its stack,
so it cannot run. A newer z88dk (master from 2026-10-31) makes the page the
default and documents the stack; the change's description says the pragma gives identical
output, so it is harmless there. This example was run with 2.4 and with the
2026-11-03 master (commit 97731e4e29), both pragmas given.
The build writes prog_CODE.bin and prog_PAGE_20.bin, and JNEXT checks both:
Loaded 193 z88dk source lines from '/home/you/prog/prog.map' and 4 listings (program identity verified)
The banking driver adds its own code to the start-up section, after the start-up
code's. The start-up code's part of that section has no label in the map, so JNEXT
places it by its bytes. It goes where prog_CODE.bin holds its instructions, and only when exactly
one place in the section does. How banked code, pages and addresses show is in
banked code.
Things that change the result¶
- With sccz80 and 80cc,
-debugchanges the program. It addsl_debug_push_frameandl_debug_pop_framearound functions, so the build you debug is not the build you ship: debug the-debugbuild, and do not compare its addresses or timing with the release build. sdcc's-debugleaves the code unchanged (the sameprog_CODE.binwith and without it). - Optimisation moves code across lines. The default is
-O2 -SO2(zxn.cfgandzx.cfg). For sdcc, the-SOlevel decides which line a piece of code sits under;-SO0stays closest to the source. For sccz80,-O3made no difference to the example (the binary was identical to the default). - The program stops at
__Start, notmain, when loaded into a paused machine: press Continue or set a breakpoint onmain. - Code your program copies at run time, or places in a
PHASEblock, has no source line where it runs.