JNEXT
Skip to content

7.4.1 sjasmplus SLD

SLD, "Source Level Debugging data", is the only format here that carries a machine's memory model, one record per instruction and the labels in one file. It is the format JNEXT likes best, because it names the page of every instruction.

1. The producer

sjasmplus, option --sld[=FILE]. The fixtures were made with sjasmplus v1.23.1 (z00m128/sjasmplus commit 46aae3fa784cae903345b87bd6b9db16a77c4bea):

sjasmplus --sld=equlabels.sld equlabels.asm

Without a file name the default is the first source's name with the extension sld.txt (OpenSld_buildDefaultNameIfNeeded(), sjasmplus sjasm/sjio.cpp:1605), which is why the sidecar search tries both <stem>.sld and <stem>.sld.txt (7.3). Records are written only for code assembled inside a DEVICE (IsSldExportActive() needs a device id; the sjasmplus manual, docs/documentation.xml chapter c_sld_data, line 5479).

2. The writer

All in sjasmplus:

  • sjasm/sjio.cpp:1587 writes the first line, |SLD.data.version|1. An optional ||K|KEYWORDS|... line follows (sjasm/sjio.cpp:1588-1597).
  • sjasm/sjio.cpp:1629-1687, WriteToSldFile(), writes every record. Its own comment (sjasm/sjio.cpp:1630-1666) documents the layout; the snprintf is at sjasm/sjio.cpp:1683: "%s|%s|%s|%s|%d|%d|%c|%s\n". The position is line[:colBegin[:colEnd]] (WriteToSldFile_TextFilePos(), sjasm/sjio.cpp:1573-1578).
  • The device record: sjasm/devices.cpp:275-291 builds pages.size:%d,pages.count:%d,slots.count:%d,slots.adr:A,B,... and writes it as a Z record with page and value -1.
  • The L record's data, module,main,local[,+trait...], is built by ExportLabelToSld() (sjasm/tables.cpp:135-185); the module markers module,,,+module and module,,,+endmod by ExportModuleToSld() (sjasm/tables.cpp:187). The traits written are +local, +equ, +macro, +smc, +reloc, +reloc_high, +used, +struct_def, +struct_data and +sizeof (sjasm/tables.cpp:174-183).

3. The grammar

A line is either the version line, a comment, or a record of eight |-separated fields:

|SLD.data.version|1
||<anything>
<file>|<line>[:<col>[:<endcol>]]|<deffile>|<defline>|<page>|<value>|<type>|<data>
Field Meaning
file Source file, never empty in a record
line The source position: the line, then optionally the start and end column
deffile, defline Where the instruction was defined, when that differs (inside a macro); 0 otherwise
page The device page of value; -1 where the value is not an address in a page
value A 16-bit address, or a full 32-bit value for an EQU
type T instruction trace (data empty), D an EQU symbol, F a function label, L a label, Z a device (memory model) change
data By type: empty, the name, the name, module,main,local[,+traits], pages.size:...

4. The parser's rules

load_sld() in src/debug/sld_loader.cpp applies these, in file order.

  1. Header. Line 1 must equal |SLD.data.version|1 (a trailing CR is dropped), else "missing SLD.data.version 1 header" and the whole file is refused.
  2. || lines are comments, except ||program.name, ||program.sha256, ||program.org and ||program.size, which are the identity (below). A || line without a : is ignored.
  3. Eight fields. Any other line is split on | and must have exactly 8 fields, else "malformed SLD record at line N".
  4. Z, the device. Page and value must be -1. parse_device() requires pages.size:, ,pages.count:, ,slots.count:, ,slots.adr: in that order; a page size of exactly 8192; 1 to 224 pages (the Next's 224 8 KB RAM pages) and at least one slot; as many slot addresses as slots.count, each in 0..0xFFFF, strictly ascending, none that would run past 0x10000, none overlapping. Exactly one device record is allowed ("SLD contains multiple device records", "SLD must contain one device record"). Every device error refuses the whole file.
  5. T, a trace (source half). A trace before the device record is an error; so is an empty file field, a non-empty data field, a position that is not line[:col[:endcol]] with each number at least 1 (parse_position()), a page outside [0, pages.count) or above 0xFF, or an address outside 0..0xFFFF. The line and the start column are kept; the end column and the definition fields (deffile, defline) are ignored, so a macro's instructions map to the line that invoked it. The range is {Page{page}, address, hi, {file, line, column}}.
  6. L, a label (symbol half, only if a label table was given). parse_label(): the data is split on ,; at least three parts and a non-empty main are needed (so module markers name nothing and are skipped). The name is module.main.local with the empty parts and their dots left out, as sjasmplus --sym spells it. The address must be 0..0xFFFF. A record whose traits include +struct_def is a struct's size or a field offset, not an address: it goes to the name-only values (add_value()). +sizeof only says that SIZEOF(label) was used somewhere, and the record is an ordinary label (sjasmplus sjasm/tables.h LABEL_HAS_SIZE). A page of -1 makes an unpaged label; any other page must be in [0, pages.count) (and at most 0xFF), else the record is skipped.
  7. Failures of the source half. Without a label table the first bad record returns an error at once. With one, the scan goes on and the first error is kept (source_error) so the labels still load: the source half is refused (count == -1, labels >= 0), the label half is not. The device and identity errors are never recoverable this way.
  8. No traces. With no trace record and no earlier error: "SLD contains no source traces".

5. Space and extent

Space. A trace in page p belongs to MemSpace::page(p): the SLD page number is the device's 8 KB page number, which for DEVICE ZXSPECTRUMNEXT is the NR 0x50-0x57 number, so a record is exactly the memory it names (7.1). value is the CPU's logical address and is kept as the range's lo.

Extent. A trace has a start and no end, so the adapter gives it one (sld_loader.cpp, near the end of load_sld()): the range covers its instruction up to the next distinct start in the same page, and never past the end of the 8 KB window it starts in, (lo & 0xE000) + 0x2000. Records that share a start share the extent. A zero-width range cannot result.

6. Identity

sjasmplus writes no program identity. ||program.name, ||program.sha256, ||program.org and ||program.size are a jnext convention that a build script appends (introduced with the SLD identity check); grep -rn program.sha256 ~/src/spectrum/sjasmplus finds nothing. They are all or none:

  • the SHA-256 must be exactly 64 lowercase hex digits (valid_sha256()), else "invalid SHA-256 in SLD program identity";
  • all four must be present, program.name non-empty, org at most 0xFFFF, size at most 0x10000 and org + size at most 0x10000, else "invalid or incomplete SLD program identity".

The identity then is SourceProgramIdentity { sha256, org, size }; the name is only checked. It is the hash form of 7.2: the SHA-256 of size bytes of logical memory from org, as they were right after the load. A bad identity refuses the whole file, labels included.

7. What is skipped

D (EQU symbols) and F (function labels) records are well-formed records the loader does not use (the same names arrive as L records). The ||K|KEYWORDS| line is a || line. A +struct_def record, a module marker, a label whose value is past 0xFFFF or negative (an EQU of 0x10000 or -1), and a label in a page the device does not have are skipped as labels. The definition position is ignored (rule 5).

8. Excerpt

From test/fixtures/sjasmequ/equlabels.sld, produced by sjasmplus v1.23.1 from equlabels.asm (unmodified, 50 lines; lines 1-4 and 13-22 shown):

|SLD.data.version|1
equlabels.asm|3||0|-1|-1|Z|pages.size:8192,pages.count:224,slots.count:8,slots.adr:0,8192,16384,24576,32768,40960,49152,57344
equlabels.asm|5||0|-1|16384|D|SCREEN
equlabels.asm|5||0|-1|16384|L|,SCREEN,,+equ,+used
equlabels.asm|18||0|-1|2|L|,point,,+struct_def
equlabels.asm|18||0|-1|0|L|,point.x,,+struct_def
equlabels.asm|18||0|-1|1|L|,point.y,,+struct_def
equlabels.asm|21||0|4|32768|F|main
equlabels.asm|21||0|4|32768|L|,main,
equlabels.asm|21||0|4|32768|T|
equlabels.asm|22||0|4|32771|T|
equlabels.asm|23||0|4|32774|F|main.loop
equlabels.asm|23||0|4|32774|L|,main,loop,+local,+used
equlabels.asm|23||0|4|32774|T|

main at 32768 ($8000) is page 4. The two T records at 32768 and 32771 become the ranges Page{4}:[$8000, $8003) for line 21 and [$8003, $8006) for line 22 (the next distinct start in the page is 32774, which is line 23's). point, point.x and point.y are name-only values (sizes and offsets), and SCREEN is unpaged (page -1).

The identity records are synthetic: no public producer writes them, and there is no committed fixture. This is the SLD that write_text builds in code in test/debug/source_map_test.cpp:144-154, to exercise the identity check; kNextDevice is the Z record's tail (kNextDevice, source_map_test.cpp:85-87):

|SLD.data.version|1
||program.name:game.bin
||program.sha256:9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a
||program.org:32768
||program.size:4
main.bas|6||0|-1|-1|Z|pages.size:8192,pages.count:224,slots.count:8,slots.adr:0,8192,16384,24576,32768,40960,49152,57344
main.bas|6||0|4|32790|T|
...

The hash is of the four bytes 01 02 03 04 at $8000.

9. The rows

  • source_map_test: SLD-* (the parser's refusals and acceptances), SLDL-* (labels, with equlabels.sym as the oracle for names and values), SRCRNG-* (extents and resolution), SRCMAP-*, SRCPATH-*, SYMPG-*.
  • source_debug_test: SRCDBG-SIDE-* (the sidecar), SRCDBG-SYM-*, SRCLIFE-* (7.6).
  • Functional: sld-labels-func and source-map-func.
  • test/fixtures/sjasmequ/README.md records how equlabels.sld and sizeof.sld were made.