Atari XEX Debugger (llvm-mos / cc65)
Source-level debugging of Atari 8-bit programs from inside VS Code, driving the
Fujisan emulator over its TCP debug API. Supports both toolchains:
- llvm-mos — C/C++ with ELF + DWARF debug info
- cc65 / ca65 — C and assembly, using ld65's
.dbg debug-info file
Features
- Source-line breakpoints (C/C++), driven by the program's DWARF line table.
- Registers + processor flags — A/X/Y/PC/SP/P with a decoded
NV-BDIZC view.
- Variables — inspect and edit globals and locals/parameters:
- globals & file/function statics (typed; arrays and structs expand),
- locals in zero-page imaginary registers (
RCn/RSn), CPU registers
(A/X/Y), and frame-relative (DW_OP_fbreg) storage,
<optimized out> reported honestly when a value's live range has ended.
- Watches & hovers (
evaluate) over in-scope locals and globals.
- Call stack — multi-frame heuristic unwind of the 6502 hardware stack.
- Stepping — source-level step in / over / out, plus instruction stepping.
- Memory — read/write via the memory view.
Requirements
Fujisan with its TCP server enabled
(Tools → TCP Server, default localhost:6502). You do not need to start it
yourself — if nothing is listening, the extension launches it and shuts it down
again when the session ends. An emulator you already have open is reused and
left running.
The executable is found via the fujisanPath launch attribute, then the
xexDebug.fujisanPath setting, then well-known locations, then PATH.
Your Fujisan must include the per-instruction PC-breakpoint fix
([PR #41](https://github.com/pedgarcia/fujisan/pull/41), merged 2026-07-13).
Without it, breakpoints on addresses that execute only once — a program's
entry point, or any ordinary line of code — are silently missed; only
breakpoints inside hot loops appear to work. The fix is on main but is not
yet in a tagged release (latest is v2.0.4), so build Fujisan from main
for now. The extension warns in the debug console when the build it started
appears to lack the fix.
Tested on Linux. macOS and Windows emulator discovery is implemented but
untested — reports welcome.
llvm-mos or cc65 on your PATH. The DWARF path shells out to
llvm-dwarfdump and llvm-nm, both of which ship with llvm-mos, so make sure
its bin/ directory is on PATH.
A program built with debug info, so the companion file sits next to the .xex:
llvm-mos — emits <name>.xex alongside <name>.xex.elf (symbols + DWARF).
Compile with -g and without aggressive inlining if you want readable locals:
mos-atari8-clang -g -Og -o build/main.xex main.c
cc65 / ca65 — build with -g and ask ld65 for a .dbg file (the .lbl
is used to locate the C-stack pointer, so pass -Ln too):
cl65 -t atari -g -Ln prog.lbl -Wl --dbgfile,prog.dbg -o prog.xex prog.c
The extension picks the provider automatically: a .dbg next to the program
selects the cc65 path, otherwise it looks for the ELF. Both can be overridden
with the dbg / elf launch attributes.
Usage
Add a launch configuration of type xex:
{
"type": "xex",
"request": "launch",
"name": "Debug Atari XEX (Fujisan)",
"program": "${workspaceFolder}/build/main.xex",
"stopOnEntry": true
}
Press F5. Fujisan starts automatically if it isn't already running, and the
.xex.elf (or .dbg) beside program is discovered automatically for symbols
and source mapping.
Launch attributes
| attribute |
default |
description |
program |
(required) |
path to the .xex to debug |
elf |
<program>.elf |
override the ELF used for symbols/DWARF (llvm-mos) |
dbg |
<program>.dbg |
override the cc65 debug-info file (ld65 --dbgfile) |
stopOnEntry |
true |
break at the program entry point |
tcpHost |
127.0.0.1 |
Fujisan TCP host |
tcpPort |
6502 |
Fujisan TCP debug port |
fujisanPath |
(auto) |
path to the Fujisan launcher/executable |
autoLaunch |
true |
start Fujisan when nothing is listening on tcpPort |
sourceRoot |
(auto) |
root for resolving source files |
Architecture
The debug adapter runs in-process. Emulator backends sit behind a single
ITransport interface (src/transport/), so additional backends (mos-sim,
Altirra) can be added without touching the DAP core. Source/symbol mapping is
owned by the extension (DWARF via llvm-dwarfdump + llvm-nm); the emulator is
driven at the address level.
Debug info sits behind a DebugInfo provider interface (src/debuginfo/), with
two implementations: DWARF via llvm-dwarfdump/llvm-nm for llvm-mos, and an
ld65 .dbg parser for cc65 (src/cc65/). cc65 auto-locals resolve through the
C-stack pointer (c_sp); note cc65 records no C type for locals, so they are
shown as single bytes, and module-level globals come from mangled symbol labels
with widths inferred from address gaps.