Appendices

Appendix G — D8 Debug Map Format

D8 is a JSON debug-map format for Z80 assemblers, debuggers, and conversion tools. Debug80 consumes D8 maps for source-level debugging. AZM emits D8 maps as one of its normal output artifacts.

The format is intentionally small. It records the relationship between generated address ranges, source files, listing rows, and symbols. A debugger can use that information to bind breakpoints, show stack frames, and correlate the program counter with source lines without reverse-engineering an assembler listing.

D8 is not tied to AZM syntax. AZM is one producer. Debug80 is one consumer. Other assemblers can emit the same shape directly or use a converter that turns their own listing or symbol format into D8 JSON.


File Name

A native D8 map should be written beside the listing file with the same base name and the suffix .d8.json:

build/main.lst
build/main.d8.json

Debug80 looks for that sidecar map before it falls back to listing-only source mapping or generated cache maps.


Version 1 Root Object

A D8 v1 file is a JSON object with these required root fields:

interface D8DebugMap {
  format: 'd8-debug-map';
  version: 1;
  arch: string;
  addressWidth: number;
  endianness: 'little' | 'big';
  files: Record<string, D8FileEntry>;
  lstText?: string[];
  segmentDefaults?: D8SegmentDefaults;
  symbolDefaults?: D8SymbolDefaults;
  memory?: D8MemoryLayout;
  generator?: D8Generator;
  diagnostics?: D8Diagnostics;
}
Field Required Meaning
format Yes Must be "d8-debug-map".
version Yes Must be 1 for this format version.
arch Yes Target architecture label, such as "z80", "6502", or "6809". Debug80 expects "z80" for Z80 maps.
addressWidth Yes Address size in bits, such as 16 or 24. Z80 maps normally use 16.
endianness Yes Byte order for multi-byte values. Z80 maps normally use "little".
files Yes Source-file table. Keys are source paths; values hold segments and symbols.
lstText No Optional listing-text table used by lstTextId references.
segmentDefaults No Optional defaults for segment fields. Consumers may ignore unknown default fields.
symbolDefaults No Optional defaults for symbol fields. Consumers may ignore unknown default fields.
memory No Optional memory-layout metadata.
generator No Optional producer metadata.
diagnostics No Optional producer diagnostics or quality notes.

Consumers should reject files with the wrong format or unsupported version. Consumers may ignore optional root objects and unknown root fields.


File Entries

The files object groups mapping data by source file:

interface D8FileEntry {
  meta?: {
    sha256?: string;
    lineCount?: number;
  };
  segments?: D8Segment[];
  symbols?: D8Symbol[];
}

Each key is a source path. Project-relative paths with / separators are the preferred portable form. Producers may write absolute paths or paths relative to a configured source root when that better matches their build environment. The empty string key represents unknown source.

Use portable relative paths when possible. AZM’s --source-root option is one way to produce paths that can move between machines.


Segments

A segment maps an address range back to a source or listing location:

interface D8Segment {
  start: number;
  end: number;
  line?: number | null;
  column?: number;
  kind?: 'code' | 'data' | 'directive' | 'label' | 'macro' | 'unknown';
  confidence?: 'high' | 'medium' | 'low';
  lstLine: number;
  lstText?: string;
  lstTextId?: number;
  includeChain?: string[];
  macro?: {
    name: string;
    callsite: {
      file: string;
      line: number;
      column?: number;
    };
  };
}
Field Required Meaning
start Yes Inclusive address where the segment begins.
end Yes Exclusive address where the segment ends.
lstLine Yes 1-based listing line associated with the segment.
line No 1-based source line. Use null when no source line is known.
column No 1-based source column when available.
kind No Producer’s classification of the source item.
confidence No Producer’s confidence in the source association.
lstText No Listing text for this segment.
lstTextId No Index into the root lstText table.
includeChain No Include stack, from outer source toward the included source.
macro No Macro expansion metadata.

start is inclusive and end is exclusive. A one-byte instruction at $0800 uses start: 2048 and end: 2049. A zero-width segment, where start === end, can preserve source context for labels or directives but should not be treated as executable code.

confidence is a quality hint:

Value Meaning
high Direct assembler knowledge, symbol anchor, or exact source attribution.
medium Reasonable derived mapping.
low Approximate mapping, often from weak listing information.

Symbols

Symbols describe named addresses or constants associated with a source file:

interface D8Symbol {
  name: string;
  address?: number;
  value?: number;
  line?: number;
  kind?: 'label' | 'constant' | 'data' | 'macro' | 'unknown';
  scope?: 'global' | 'local';
  size?: number;
}
Field Required Meaning
name Yes Symbol name as written or exported by the producer.
address Conditional Address for labels and addressable data symbols.
value Conditional Compile-time value for constants that do not have a source address.
line No 1-based source line where the symbol is defined.
kind No Producer’s symbol classification.
scope No Symbol visibility hint.
size No Size in bytes when known.

Each symbol must have either address or value. Constant symbols may use value without address. Debuggers should not treat value-only constants as source anchors or breakpoint locations.

Debug80 uses symbols with source lines and addresses as anchors during source-map import.


Defaults

segmentDefaults and symbolDefaults let a producer avoid repeating common fields:

interface D8SegmentDefaults {
  kind?: 'code' | 'data' | 'directive' | 'label' | 'macro' | 'unknown';
  confidence?: 'high' | 'medium' | 'low';
}

interface D8SymbolDefaults {
  kind?: 'label' | 'constant' | 'data' | 'macro' | 'unknown';
  scope?: 'global' | 'local';
}

Defaults apply only when an individual segment or symbol omits that field.


Memory Layout

memory can describe target memory regions:

interface D8MemoryLayout {
  segments: Array<{
    name: string;
    start: number;
    end: number;
    kind?: 'rom' | 'ram' | 'io' | 'banked' | 'unknown';
    bank?: number;
  }>;
}

Memory segment start is inclusive and end is exclusive. Consumers may use this for display, validation, or bank-aware lookup. Debuggers that do not need memory layout can ignore it.


Generator and Diagnostics

generator identifies the tool that produced the map:

interface D8Generator {
  name?: string;
  tool?: string;
  version?: string;
  args?: string[];
  createdAt?: string;
  inputs?: Record<string, string>;
  entrySymbol?: string;
  entryAddress?: number;
}

diagnostics records warnings or errors encountered while creating the map:

interface D8Diagnostics {
  warnings?: string[];
  errors?: string[];
}

Diagnostics are metadata about map generation. A consumer can display them, but should still validate the map structure itself.


Minimum Viable Map

This is enough for a consumer to map address $0800 through $0801 back to line 5 of src/main.asm:

{
  "format": "d8-debug-map",
  "version": 1,
  "arch": "z80",
  "addressWidth": 16,
  "endianness": "little",
  "generator": {
    "name": "example-assembler",
    "version": "1.0"
  },
  "files": {
    "src/main.asm": {
      "segments": [
        {
          "start": 2048,
          "end": 2050,
          "lstLine": 5,
          "line": 5,
          "confidence": "high",
          "kind": "code"
        }
      ],
      "symbols": [
        {
          "name": "start",
          "address": 2048,
          "line": 5,
          "kind": "label",
          "scope": "global"
        }
      ]
    }
  }
}

Producer Guidance

Prefer one precise segment for each emitted instruction or data range. Use broader segments only when the producer cannot attribute bytes more exactly.

Write source paths consistently. Mixing absolute paths, relative paths, and generated temporary paths makes debugger lookup weaker.

Use line: null or omit line when a range cannot be tied to source. Do not invent source lines just to make the file look complete.

Set confidence honestly. A debugger can use this to prefer exact native mappings over approximate listing-derived mappings.

Keep unknown future data additive. Producers may include extra fields, but required v1 fields should remain stable so existing consumers can continue to read the map.


Debug80 Consumer Behavior

Debug80 parses and validates D8 maps before importing them. Invalid JSON or schema-level failure makes Debug80 fall back to listing-derived mapping. Segment quality warnings do not necessarily abort launch.

Native sidecar maps are preferred over Debug80-generated cache maps. If no usable D8 map exists, Debug80 can parse the listing, build a generated D8-style cache map, and import that through the same mapping path.

For breakpoint binding, Debug80 uses executable segments. Labels, constants, and directive-only rows may still help stack display or symbol lookup, but zero-width or non-executable mappings should not become active breakpoint addresses.