一、什么是单片机链接器
工具链里两个链接相关程序:
arm-none-eabi-ld:底层原生链接器,专门处理目标文件.o、库、内存分配;arm-none-eabi-gcc:上层封装,会自动调用 ld,同时传递内核、浮点、ABI 参数,单片机开发优先用 gcc 做链接,不直接裸用 ld。
链接器核心作用
- 合并所有
.o目标文件、启动文件、库; - 根据链接脚本
.ld划分 Flash(只读程序)、RAM(全局 / 栈 / 堆)内存地址; - 解析符号:
Reset_Handler、main、中断函数、全局变量; - 生成可烧录的
.elf镜像,记录完整内存布局。
二、链接脚本 .ld
1. 核心功能
定义芯片硬件内存分区,以 STM32F103(128K Flash + 20K RAM)举例:
ld
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 128K /* 程序存放Flash起始地址 */
RAM : ORIGIN = 0x20000000, LENGTH = 20K /* 运行时RAM */
}
SECTIONS
{
.text : /* 代码段,存Flash */
{
*(.vectors) /* startup.s 中断向量表,放最开头 */
*(.text*) /* C/汇编代码 */
} > FLASH
.rodata : /* 常量字符串、const只读数据 */
{ *(.rodata*) } > FLASH
.data : /* 初始化全局变量,Flash存副本,启动时拷贝到RAM */
{ *(.data*) } > RAM
.bss : /* 未初始化全局变量,清零区 */
{ *(COMMON) *(.bss*) } > RAM
}
2. 关键关键字
MEMORY:划定硬件内存地址区间,必须和芯片手册一致;SECTIONS:把编译出的代码 / 数据段分配到 Flash/RAM;> FLASH/> RAM:指定段存放位置;*(.vectors):中断向量表,必须放在 Flash 最开头,否则芯片上电找不到复位入口。
三、两种链接命令写法
方式 1:gcc 封装链接(推荐,单片机通用)
自动传递 -mcpu -mthumb 内核参数,兼容 Thumb 指令集:
cmd
# 输入:main.o + startup.o 目标文件,指定ld脚本,输出elf
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb main.o startup.o -T chip.ld -o main.elf
参数说明:
-T chip.ld:指定自定义链接脚本(必加,单片机没有操作系统默认内存布局)-o main.elf:输出链接产物 elf 文件
方式 2:直接调用原生 ld
需要手动补全架构、库、标准启动参数,极易出错:
cmd
arm-none-eabi-ld main.o startup.o -T chip.ld -o main.elf
缺点:不会自动匹配 Cortex-M 内核 ABI,缺少系统库、浮点支持,容易报符号未定义。
四、链接阶段常见报错
1. No such file or directory: startup.s / chip.ld
当前 CMD 目录缺少文件,文件名拼写错误,文件不在执行目录。 解决:dir 命令查看当前目录文件,保证脚本、启动文件、.o 都在同目录。
2. 汇编文件 junk at end of line '*'
不是链接器报错,是汇编阶段报错,.s 文件编码带 BOM、含中文、全角符号,汇编器无法解析,链接还没走到。 修复:VSCode 改为 UTF-8 无 BOM、LF 换行,删除所有中文注释。
3. undefined reference to 'main'
链接器找不到 main 函数,两种情况:
- 没把 main.c 编译进
.o; - startup.s 里复位函数没有跳转到
main。
4. region 'FLASH' overflowed
代码总量超过 ld 脚本里定义的 Flash 长度,精简代码或修改 LENGTH 扩容。
5. undefined reference to Reset_Handler
startup.s 丢失复位 / 中断向量,或 ld 脚本没写 *(.vectors)。
五、链接后配套工具(链接器产物处理)
链接输出 .elf 后,用配套工具导出烧录文件:
- 查看内存占用(代码 /ram 大小)
cmd
arm-none-eabi-size main.elf
- 生成烧录 BIN 文件(串口 / 下载器常用)
cmd
arm-none-eabi-objcopy -O binary main.elf main.bin
- 生成 HEX 文件(Keil、Jlink 通用)
cmd
arm-none-eabi-objcopy -O ihex main.elf main.hex
六、完整编译 + 链接标准流程(无报错模板)
cmd
:: 1. 编译C文件生成目标文件
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb main.c -o main.o
:: 2. 编译汇编启动文件生成目标文件
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb startup_stm32f103xb.s -o startup.o
:: 3. 链接,使用ld脚本生成elf
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb main.o startup.o -T chip.ld -o main.elf
:: 4. 导出烧录文件
arm-none-eabi-objcopy -O binary main.elf main.bin
总结
- 单片机必须自定义
.ld链接脚本,PC 程序不需要,这是嵌入式最大区别; - 日常开发只用
arm-none-eabi-gcc完成链接,不要直接调用arm-none-eabi-ld; - 链接前所有
.c.s必须先编译成.o,且汇编文件要修复编码避免汇编报错; - 链接器核心工作:内存分配、符号解析、多文件合并。