单片机链接器(arm-none-eabi-ld / GCC 封装链接流程)

一、什么是单片机链接器

工具链里两个链接相关程序:

  1. arm-none-eabi-ld :底层原生链接器,专门处理目标文件 .o、库、内存分配;
  2. arm-none-eabi-gcc :上层封装,会自动调用 ld,同时传递内核、浮点、ABI 参数,单片机开发优先用 gcc 做链接,不直接裸用 ld。

链接器核心作用

  1. 合并所有 .o 目标文件、启动文件、库;
  2. 根据链接脚本 .ld 划分 Flash(只读程序)、RAM(全局 / 栈 / 堆)内存地址;
  3. 解析符号:Reset_Handlermain、中断函数、全局变量;
  4. 生成可烧录的 .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 后,用配套工具导出烧录文件:

  1. 查看内存占用(代码 /ram 大小)

cmd

复制代码
arm-none-eabi-size main.elf
  1. 生成烧录 BIN 文件(串口 / 下载器常用)

cmd

复制代码
arm-none-eabi-objcopy -O binary main.elf main.bin
  1. 生成 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

总结

  1. 单片机必须自定义 .ld 链接脚本,PC 程序不需要,这是嵌入式最大区别;
  2. 日常开发只用 arm-none-eabi-gcc 完成链接,不要直接调用 arm-none-eabi-ld
  3. 链接前所有 .c .s 必须先编译成 .o,且汇编文件要修复编码避免汇编报错;
  4. 链接器核心工作:内存分配、符号解析、多文件合并。