BitMagic The Complete Development Environment for the Commander X16.

ca65 Projects

BitMagic can debug an assembly program built with ca65 and ld65 from the cc65 suite. It doesn’t build the project; you build it as normal and point a project file at the debug file ld65 writes. C programs built with the cc65 compiler aren’t supported.

There are two working examples: Ca65Application, a small program with globals and locals, and Ca65DreamTracker, which debugs Dream Tracker including its overlay modules.

How to build for debugging

Build with ca65 and ld65 the way you already do, with two additions:

  • Assemble with -g so the debug file has line information. With cl65, options only apply to the files after them, so -g must come before the source files.
  • Pass --dbgfile <name>.dbg to ld65 (with cl65, -Wl --dbgfile,<name>.dbg) so it writes the debug file.
ca65 -g --cpu 65c02 -t cx16 src/main.asm
ld65 -C cx16.cfg --dbgfile game.dbg -o GAME.PRG src/main.o cx16.lib

Or in one step with cl65:

cl65 -g -t cx16 -C cx16.cfg -o GAME.PRG -Wl --dbgfile,game.dbg src/main.asm

The debug file says where every source line ended up in each output file, so BitMagic doesn’t need the linker config. Each byte is mapped by its offset in its file, so a file can be loaded to any address or RAM bank and still map back to the source.

How to set up the project file

Add an entry to files with type cc65 (named after the toolchain):

{
    "files": [
        {
            "type": "cc65",
            "debugFile": "game.dbg",
            "objectFiles": [ "src/*.o" ],
            "outputs": [
                { "filename": "GAME.PRG", "hasHeader": true },
                { "filename": "DAT/*", "hasHeader": false }
            ]
        }
    ]
}
Name Type Optional Description
type "cc65" No  
debugFile string No The ld65 --dbgfile output. The source map and symbols come from here.
outputs output[] No The files ld65 wrote that you want to debug.
basepath string Yes Folder ca65 and ld65 were run from, relative to the project’s basePath. Paths in the debug file are relative to it.
objectFiles string[] Yes The .o files, checked against the outputs to catch an out of date build. Wildcards are accepted.
sourcePath string Yes Another folder to search for source files.
includes string[] Yes Extra source files (eg .mac) to use when a source file named in the debug file can’t be found. Matched by filename.
filemap { "path", "replace" }[] Yes Rewrites the start of source paths from the debug file, eg when it was built on another machine.

config and defaultOutputFile are no longer used and are ignored.

Outputs

Each output is matched against the file names in the debug file. A file name can use wildcards, so DAT/* covers every overlay module in that folder, and either / or \ works.

Name Type Optional Description
filename string No The file ld65 wrote, as named in the debug file.
hasHeader bool Yes The file starts with a two-byte load address. Defaults to true.
startAddress int Yes The address the file loads to. Taken from the debug file if not set.
referenceFile string Yes Read this file instead of filename.

What gets mapped to source

  • Files the program loads at runtime, such as overlay modules, are mapped when the program loads them, so their breakpoints only resolve once they’re loaded.
  • Code from a macro maps to the line that uses the macro.
  • Code from a library built without -g (eg cx16.lib) has no source lines and is shown as disassembly, labelled with the names from the debug file.
  • BitMagic warns if a source file has changed since it was assembled, as breakpoints may then be on the wrong lines.

How to view variables

Symbols in the debug file show in the Globals and Locals views and can be used in watches, the same as BitMagic variables. They use BitMagic’s naming rather than ca65’s, so scopes are separated with a single :.

.segment "BSS"
frame_count:    .res 2          ; watch 'frame_count', a word

.proc update
.segment "BSS"
step_index:     .res 1          ; watch 'update:step_index'
.segment "CODE"
    ldy step_index
@loop:                          ; a cheap local, which can't be watched
    dey
    bne @loop
    rts
.endproc
ca65 Watch as
frame_count frame_count
update::step_index update:step_index, or :step_index if the name is unique
@loop Can’t be watched

ca65 symbols have no type, so variables are shown by their size: one byte as a byte, two as a word, and anything larger as a byte array.

  • Variables are labels in writable segments (BSS, DATA, zero page, banked RAM), zero page equs, and labels on data such as a .byte table.
  • Code labels and constants aren’t listed, but you can still use them in a watch, where they give their address or value.
  • Variables in banked RAM read whichever bank is currently selected.

Each .proc or .scope is a scope. Locals shows the variables of the current .proc and the scopes around it, and Globals shows everything nested by scope. A large application often declares its shared variables in one place, such as a memory map include file, so where a scope has a lot of variables Globals groups them by segment (ZEROPAGE, BSS, BANKMISC and so on). The group is only for display and isn’t part of the name.

Mixing with BitMagic source

A project’s files array can hold both cc65 and bitmagic entries, so a ca65 program and a .bmasm module can be debugged together.

Reporting problems

An unusual .dbg or .o file can still trip up the parser. If a build doesn’t load, or debugging behaves oddly, check the issue tracker or open a new one with the files that triggered it, and I’ll take a look.