Skip to main content

Debugging Spectrum Programs

Spectranext includes a powerful integrated debugger that allows you to debug ZX Spectrum programs using GDB or console commands. The debugger supports breakpoints, memory inspection, register access, and stack traces.

Overview

The debugger provides:

  • GDB Stub: Connect GDB for full debugging support
  • Hardware Breakpoints: Break on specific memory addresses
  • Software Breakpoints: Break on code locations
  • Memory Inspection: Read/write memory
  • Register Access: View and modify CPU registers
  • Stack Traces: Inspect call stack
  • Source-level debugging: Debug C programs with z88dk-gdb
  • All with real hardware!

Compiling with Debug Symbols

To enable source-level (C) debugging, compile your program with the -debug flag:

zcc +zx -debug main.c -create-app -m

This generates debugging symbols that allow GDB to map addresses to source code locations, enabling features like:

  • Setting breakpoints by function name or line number
  • Viewing variable values
  • Stepping through source code line by line
  • Viewing source code context

The -debug flag includes debugging information in the output files, which can be used with z88dk-gdb or standard GDB.

Assembly-Level Debugging

You can debug any program at the assembly level, even without debug symbols. This is useful for:

  • Assembly programs: Programs written directly in Z80 assembly
  • Low-level debugging: Understanding what the CPU is actually executing
  • Optimization: Analyzing generated code from compilers
  • Reverse engineering: Understanding existing programs

Debugging Without Debug Symbols

When debugging without debug symbols, you work directly with memory addresses and assembly instructions:

Set breakpoints by address:

(gdb) break *0x8000
(gdb) break *main

Disassemble code:

(gdb) disassemble 0x8000
(gdb) disassemble main
(gdb) disassemble $pc

View assembly at current location:

(gdb) x/10i $pc    # Show 10 instructions from PC
(gdb) x/10i $pc-10 # Show 10 instructions before PC

Step by instruction:

(gdb) stepi       # Step one instruction (step into)
(gdb) nexti # Step one instruction (step over)
(gdb) stepi 5 # Step 5 instructions

Inspect registers:

(gdb) info registers
(gdb) print/x $pc # Program counter in hex
(gdb) print/x $sp # Stack pointer in hex
(gdb) print/x $af # AF register pair
(gdb) print/x $bc # BC register pair
(gdb) print/x $de # DE register pair
(gdb) print/x $hl # HL register pair

View memory as assembly:

(gdb) x/20i 0x8000   # Disassemble 20 instructions from 0x8000
(gdb) x/20i $pc # Disassemble 20 instructions from PC

Assembly Debugging Workflow

  1. Load your program: Load the binary or TAP file
  2. Set breakpoint: Set breakpoint at entry point or specific address
    (gdb) break *0x8000
  3. Run to breakpoint: Start execution
    (gdb) continue
  4. Disassemble current code: See what's executing
    (gdb) x/20i $pc
  5. Step through instructions: Execute one instruction at a time
    (gdb) stepi
  6. Inspect registers and memory: Check CPU state
    (gdb) info registers
    (gdb) x/16x $sp # View stack
  7. Continue or modify: Resume execution or change values
    (gdb) set $hl = 0x1234
    (gdb) continue

Useful Assembly Debugging Commands

View memory in different formats:

(gdb) x/16x 0x8000    # Hex dump
(gdb) x/16i 0x8000 # Disassemble as instructions
(gdb) x/16c 0x8000 # Character dump
(gdb) x/16d 0x8000 # Decimal dump

View stack:

(gdb) stack           # View stack contents

Example: Debugging Assembly Code


# Set breakpoint at program start
(gdb) break $8000

# Run program
(gdb) continue

# When breakpoint hits, disassemble
(gdb) dis
0x8000: ld hl, 0x1234
0x8003: ld de, 0x5678
0x8006: add hl, de
...

# Step through instructions
(gdb) si
(gdb) print/x $hl # Check HL register
$1 = 0x1234

Assembly-level debugging gives you complete control over the CPU state and allows you to debug any program, regardless of whether it was compiled with debug symbols.

Debugger Architecture

When the debugger traps (breakpoint hit or stop command), Spectranext remaps memory:

0x0000-0x0FFF   - Debugger 0-page code
0x1000-0x3FFF - Unmapped
0x4000-0xFFFF - Spectrum RAM

The debugger code keeps the Z80 running in a loop until a command is received from the debugger client.

GDB Integration

Spectranext's debugger can be accessed via GDB in several ways:

Connection Options

For debugging on physical Spectranext hardware over USB-C:

The GDB debugger uses the third CDC interface (CDC interface 2). On macOS, the device appears as /dev/cu.usbmodem* (e.g., /dev/cu.usbmodem00015). On Linux, it's typically /dev/ttyACM2 (the third device, 0-indexed).

Using z88dk-gdb:

z88dk-gdb -d /dev/cu.usbmodem00015 -x your_program.sym

Note: Spectranext exposes three CDC interfaces:

  • Interface 0: Terminal/Console
  • Interface 1: USBFS (file transfer)
  • Interface 2: GDB Server (this one)

Network Connection (WiFi/TCP)

For debugging on physical Spectranext hardware over WiFi:

z88dk-gdb -h <spectranext_ip> -p 1337 -x your_program.sym

The debugger listens on port 1337 for GDB connections over the network.

Using z88dk-gdb

z88dk-gdb is a specialized GDB wrapper designed for Z80 debugging. Connect to the debugger:

# USB connection (recommended)
z88dk-gdb -d <serial_device> -x <debug_symbols>

# Network connection
z88dk-gdb -h <host> -p <port> -x <debug_symbols>

Parameters:

  • -d <device>: Serial device path for USB connections (e.g., /dev/cu.usbmodem00015 on macOS, /dev/ttyACM2 on Linux - the third CDC interface)
  • -h <host>: Hostname or IP address for network connections (e.g., 192.168.1.100)
  • -p <port>: Port number (only required for network connections)
  • -x <debug_symbols>: Path to debug symbols file generated during compilation

IDE Integration (CLion)

You can debug Spectrum programs directly from CLion for a full-featured debugging experience with breakpoints, variable inspection, and step-through debugging.

Setup:

  1. Install z88dk: Ensure you have the latest z88dk installed (part of Spectranext SDK)
  2. Configure CMake: Use z88dk's CMake toolchain (see example below)
  3. Create Debug Configuration: Configure CLion to use z88dk-gdb as the debugger

CLion Debug Configuration:

  1. Go to Run > Edit Configurations...

  2. Create a new GDB Remote Debug configuration

  3. Set the following:

    • Name: Spectranext USB (or Spectranext WiFi)
    • GDB: Path to z88dk-gdb executable
    • Target: Your compiled binary
    • Symbol file: Your .map file (generated with -debug flag)
    • 'target remote' args:
      • For USB: /dev/cu.usbmodem00015 (or your device path)
      • For WiFi: <spectranext_ip>:1337
    • GDB startup commands: Leave empty or add custom initialization
  4. Set breakpoints in your source code

  5. Run the debug configuration - z88dk-gdb will connect and upload the binary

  6. The debugger will stop at your breakpoints

Example Project:

See the z88dk-gdb IDE test repository for a complete working example with CLion and VSCode configurations.

Emulators

For debugging in emulators, use the emulator's GDB server:

MAME Emulator:

# Start MAME with GDB stub
mame spectrum -window -nomaximize -resolution0 768x576 -debug -debugger gdbstub -debugger_port 1337

# Connect z88dk-gdb
z88dk-gdb -h localhost -p 1337 -x your_program.sym

Fuse Emulator (Windows):

  1. Start Fuse
  2. Select "GDBServer..." from the menu
  3. Select "Enabled"
  4. Connect z88dk-gdb to the default port

Fuse Emulator (Mac):

  1. Start Fuse
  2. Go to "Preferences" > "Debugger"
  3. Check "Enable GDBServer"
  4. Connect z88dk-gdb to the default port

IDE Integration

z88dk-gdb can be integrated with modern IDEs for a better debugging experience. See the IDE Integration (CLion) section above for detailed setup instructions.

Visual Studio Code:

  • Configure launch.json to use z88dk-gdb
  • Set breakpoints directly in the editor
  • View variables and call stack in the IDE
  • See the z88dk-gdb IDE test repository for VSCode configuration examples

CLion:

  • Configure remote debugger to use z88dk-gdb
  • Full debugging support with breakpoints and variable inspection
  • See the IDE Integration (CLion) section above for detailed setup instructions

z88dk-gdb Commands

z88dk-gdb provides a rich set of debugging commands. Here are the most commonly used commands:

Execution Control

  • continue / cont / c - Continue execution
  • step / s - Step one source line (including into calls)
  • stepi / si - Step one instruction (including into calls)
  • next / n - Step one source line (over calls)
  • nexti / ni - Step one instruction (over calls)
  • finish - Exit current function and print result if any

Breakpoints

  • break / b [address/label] - Set breakpoint at address or label
  • break delete [index] - Delete breakpoint by index
  • break disable [index] - Disable breakpoint
  • break enable [index] - Enable breakpoint
  • break memory8 [address] [value] - Break when memory address equals value (8-bit)
  • break memory16 [address] [value] - Break when memory address equals value (16-bit)
  • break register [register] [value] - Break when register equals value
  • del_break / d [index] - Delete breakpoint

Memory and Register Inspection

  • examine / x [address] - Examine memory at address
  • print / p <expression> - Print expression value
  • registers / reg - Display all registers
  • info locals - Show local variables in current frame
  • info registers - Show register values

Stack and Frames

  • backtrace / bt - Show execution stack
  • stack [size] - Examine stack (optionally specify size)
  • frame [num] - Set or view current frame
  • up - Go one frame up
  • down - Go one frame down

Disassembly

  • disassemble / dis [address] [size] - Disassemble from address (or PC) for size bytes

Source Code

  • list [address] - List source code around address or current location

Advanced Features

  • restore <file> [address] - Restore memory from file
  • restore_pc <file> [address] - Restore memory and set PC
  • set <register/address> = <value> - Set register or memory value

Example z88dk-gdb Session

# Connect to debugger over USB (recommended - third CDC interface)
z88dk-gdb -d /dev/cu.usbmodem00015 -x program.sym

# Or connect over WiFi/TCP
# z88dk-gdb -h <spectranext_ip> -p 1337 -x program.sym

# Set breakpoint
(gdb) break main

# Continue to breakpoint
(gdb) continue

# When stopped, inspect state
(gdb) info registers
(gdb) print variable_name
(gdb) x/16x 0x8000

# Step through code
(gdb) step
(gdb) next

# View stack
(gdb) backtrace
(gdb) stack 32

# Disassemble
(gdb) disassemble $pc
(gdb) disassemble 0x8000 64

# Set watchpoint
(gdb) watch write 0x8000

# Continue
(gdb) continue

IDE Integration

You can debug Spectrum programs directly from modern IDEs like Visual Studio Code or CLion, providing a full-featured debugging experience with breakpoints, variable inspection, and step-through debugging.

See https://github.com/speccytools/z88dk-gdb-ide-test

Breakpoints

Hardware Breakpoints

Hardware breakpoints are set in hardware and trigger immediately:

  • Limited count: Only 1 hardware breakpoint available
  • Fast: No performance impact
  • Reliable: Always triggers when address matches

Software Breakpoints

Software breakpoints modify code to insert trap instructions:

  • More available: 4 software breakpoints
  • Code modification: Replaces instruction with RST 28h
  • Restorable: Original instruction is saved

Example Debugging Session

Complete Workflow

  1. Compile with debug symbols:

    zcc +zx -debug main.c -create-app -m
  2. Start debugger target:

    • Physical hardware: Ensure Spectranext is running and accessible
    • Connect Spectranext to your computer over USB-C or have an active Wi-Fi
  3. Connect GDB:

    # USB connection (recommended - third CDC interface)
    z88dk-gdb -d /dev/cu.usbmodem00015 -x main.sym

    For WiFi/TCP connection:

    z88dk-gdb -h <spectranext_ip> -p 1337 -x main.sym
  4. Set breakpoints:

    (gdb) b main
    (gdb) b main.c:20
  5. Start/continue execution:

    (gdb) continue
    (gdb) c
  6. Inspect state when breakpoint hits:

    (gdb) reg
  7. Step through code:

    (gdb) step      # Step into function calls
    (gdb) next # Step over function calls
    (gdb) finish # Finish current function
  8. Continue debugging or exit:

    (gdb) continue
    (gdb) quit

Limitations

  • Limited breakpoints: Only 1 hardware, 4 software breakpoints
  • Debug symbols: Source-level debugging requires compiling with -debug flag, which disables certain program size optimizations.

Additional Resources