cc65链接器ld65配置指南:定制你的复古程序
cc65是一款免费的C编译器,专为基于6502架构的复古系统设计。ld65作为cc65工具链的重要组成部分,是一个高度可配置的链接器,通过配置文件可以灵活控制程序的内存布局和输出格式,让你轻松定制复古程序。
一、ld65链接器简介
ld65链接器的主要功能是将ca65汇编器生成的目标文件组合成可执行文件,它支持多种6502平台,如C64、Apple II、Atari等。其核心优势在于通过配置文件实现高度定制化,满足不同复古系统的内存架构需求。
ld65的核心特性
- 支持任意数量的段组合,灵活构建可执行模块
- 解析目标文件中存储的任意表达式
- 提供详细的错误信息,包括未定义符号、表达式范围错误等
- 通过配置文件实现高度可配置的输出,支持多种目标系统
二、快速上手:使用默认配置文件
cc65为常见的6502平台提供了预定义的配置文件,位于项目的cfg/目录下。例如C64的配置文件为cfg/c64.cfg,Atari的配置文件为cfg/atari.cfg等。
基本使用命令
使用默认配置链接程序的基本命令如下:
ld65 -t c64 -o program.prg main.o libs/libc.lib
其中-t c64指定了目标系统为C64,ld65会自动使用cfg/c64.cfg配置文件。如果需要显式指定配置文件,可以使用-C选项:
ld65 -C cfg/c64.cfg -o program.prg main.o libs/libc.lib
三、配置文件结构解析
ld65的配置文件采用简洁的语法,主要包含以下几个部分:
MEMORY部分:定义内存区域
MEMORY部分用于描述目标系统的内存布局,定义各个内存区域的起始地址、大小和属性。以下是C64配置文件中的MEMORY定义:
MEMORY {
ZP: file = "", define = yes, start = $0002, size = $001A;
LOADADDR: file = %O, start = %S - 2, size = $0002;
HEADER: file = %O, define = yes, start = %S, size = $000D;
MAIN: file = %O, define = yes, start = __HEADER_LAST__, size = __HIMEM__ - __HEADER_LAST__;
BSS: file = "", start = __ONCE_RUN__, size = __HIMEM__ - __STACKSIZE__ - __ONCE_RUN__;
}
每个内存区域可以设置以下属性:
start:内存区域的起始地址size:内存区域的大小file:输出文件名称,%O表示使用命令行指定的输出文件名define:是否为该内存区域定义符号(如__ZP_START__、__ZP_SIZE__等)
SEGMENTS部分:段分配
SEGMENTS部分定义如何将目标文件中的段分配到MEMORY部分定义的内存区域。例如:
SEGMENTS {
ZEROPAGE: load = ZP, type = zp;
LOADADDR: load = LOADADDR, type = ro;
EXEHDR: load = HEADER, type = ro;
STARTUP: load = MAIN, type = ro;
LOWCODE: load = MAIN, type = ro, optional = yes;
CODE: load = MAIN, type = ro;
RODATA: load = MAIN, type = ro;
DATA: load = MAIN, type = rw;
INIT: load = MAIN, type = rw; # uninitialized, but reserves output space
ONCE: load = MAIN, type = ro, define = yes;
BSS: load = BSS, type = bss, define = yes;
}
段的类型(type)主要有:
zp:零页段ro:只读段rw:读写段bss:未初始化数据段
SYMBOLS部分:符号定义
SYMBOLS部分用于定义链接器符号,这些符号可以在程序中引用。例如:
SYMBOLS {
__LOADADDR__: type = import;
__EXEHDR__: type = import;
__STACKSIZE__: type = weak, value = $0800; # 2k stack
__HIMEM__: type = weak, value = $D000;
}
weak类型的符号可以在链接时被覆盖,value指定了符号的默认值。
四、定制你的配置文件
修改内存布局
根据目标系统的硬件特性,你可能需要调整内存布局。例如,如果你需要为C64程序分配更大的栈空间,可以修改__STACKSIZE__的值:
SYMBOLS {
__STACKSIZE__: type = weak, value = $1000; # 4k stack
}
添加自定义段
如果你需要将特定代码放在固定地址,可以添加自定义段。例如,为中断处理程序创建一个单独的段:
SEGMENTS {
INTERRUPT: load = MAIN, type = ro, start = $FF00;
}
然后在汇编代码中使用.segment "INTERRUPT"指令将代码放入该段。
多文件输出配置
ld65支持将不同的内存区域输出到不同的文件。例如,将ROM和RAM分别输出到不同文件:
MEMORY {
ROM: start = $8000, size = $8000, file = "rom.bin";
RAM: start = $0800, size = $7800, file = "ram.bin";
}
五、高级技巧
使用条件编译
配置文件支持条件编译,可以根据不同的目标系统或编译选项启用不同的配置:
FEATURES {
# 定义不同目标系统的特性
TARGET_C64: value = yes;
TARGET_VIC20: value = no;
}
MEMORY {
# 条件定义内存区域
#if TARGET_C64
MAIN: start = $0801, size = $A000;
#else
MAIN: start = $1001, size = $3000;
#endif
}
生成映射文件
使用-m选项可以生成映射文件,帮助你分析内存使用情况:
ld65 -t c64 -m program.map -o program.prg main.o
映射文件包含了段的地址分布、符号表等信息,是调试内存问题的重要工具。
处理 banked 内存
对于具有banked内存的系统(如C128),可以使用bank属性定义内存bank:
MEMORY {
BANK0: start = $8000, size = $4000, bank = 0;
BANK1: start = $8000, size = $4000, bank = 1;
}
SEGMENTS {
CODE_B0: load = BANK0, type = ro;
CODE_B1: load = BANK1, type = ro;
}
六、常用命令行选项
ld65提供了丰富的命令行选项,以下是一些常用选项:
-t sys:指定目标系统(如c64、apple2等)-C file:指定配置文件-o file:指定输出文件-m file:生成映射文件-D sym=val:定义符号-v:详细输出模式-u sym:强制导入符号
完整的命令行选项说明可以参考官方文档doc/ld65.sgml。
七、故障排除
常见错误及解决方法
-
未定义符号:检查是否遗漏了库文件或目标文件,使用
-v选项获取详细的引用信息。 -
内存区域溢出:调整内存区域大小或优化代码,使用映射文件分析内存使用情况。
-
段类型不匹配:确保将rw类型的段分配到rw类型的内存区域。
-
配置文件错误:检查配置文件语法,确保所有括号和分号正确匹配。
获取帮助
如果遇到问题,可以查阅以下资源:
- 官方文档:
doc/ld65.sgml - 配置文件示例:
cfg/目录下的各种.cfg文件 - 示例程序:
samples/目录下的示例代码
通过灵活使用ld65的配置文件,你可以充分发挥6502系统的硬件特性,为各种复古计算机编写高效的程序。无论是简单的Hello World还是复杂的游戏,ld65都能帮助你实现精确的内存控制和优化的代码布局。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



