链接脚本(.ld)与启动文件(.s)的关系
概述
在嵌入式 ARM 项目中,链接脚本(.ld)和启动汇编文件(.s)通过符号名在链接阶段建立联系。启动文件引用符号,链接脚本定义符号地址。
1. 启动文件定义符号引用
启动文件 startup_ac7840x.s 中引用了链接脚本定义的符号:
asm
; 第 46-54 行,定义数据段和 BSS 段的地址引用
.word _sidata ; Flash 中 .data 初始值地址
.word _sdata ; RAM 中 .data 段起始地址
.word _edata ; RAM 中 .data 段结束地址
.word _sbss ; .bss 段起始地址
.word _ebss ; .bss 段结束地址
; 第 71 行,设置栈指针
ldr sp, =_estack ; 栈顶地址
2. 链接脚本定义这些符号
链接脚本 ac7840x_flash.ld 中定义了上述符号的具体地址:
ld
_sidata = .; /* 第 83 行,Flash 中 .data 初始值地址 */
_sdata = .; /* 第 94 行,RAM 中 .data 段起始 */
_edata = .; /* 第 99 行,RAM 中 .data 段结束 */
_sbss = .; /* 第 124 行,.bss 段起始 */
_ebss = .; /* 第 131 行,.bss 段结束 */
_estack = .; /* 第 159 行,栈顶地址 */
3. 链接阶段整合
编译和链接的流程:
汇编编译: startup_ac7840x.s → startup_ac7840x.o (符号未解析)
C 编译: main.c → main.o
链接: gcc *.o -T ac7840x_flash.ld → Demo.elf
↓
链接器用 .ld 中定义的地址
替换 .o 中的符号引用
在链接阶段,链接器读取链接脚本,确定各符号的最终地址,然后替换目标文件(.o)中所有对这些符号的引用。
4. 实际作用
启动文件 Reset_Handler 使用链接脚本定义的符号完成系统初始化:
asm
ldr r3, =_sidata ; 从 Flash 读取 .data 初始值
ldr r0, =_sdata ; .data 在 RAM 中的起始地址
ldr r3, =_edata ; .data 在 RAM 中的结束地址
ldr r2, =_sbss ; .bss 起始地址
ldr r3, =_ebss ; .bss 结束地址
初始化过程:
- 将
.data段初始值从 Flash 拷贝到 RAM - 将
.bss段清零 - 设置栈指针
sp = _estack - 调用
SystemInit进行时钟初始化 - 调用
main进入应用程序
5. 内存布局示例
Flash (0x00000000):
┌─────────────────┐
│ .isr_vector │ ← 中断向量表
├─────────────────┤
│ .text │ ← 程序代码
├─────────────────┤
│ .data 初始值 │ ← _sidata 指向此处
└─────────────────┘
RAM (0x1FFF0000):
┌─────────────────┐
│ .data │ ← _sdata ~ _edata
├─────────────────┤
│ .bss │ ← _sbss ~ _ebss
├─────────────────┤
│ .heap │ ← 堆区
├─────────────────┤
│ .stack │ ← _estack 指向栈顶
└─────────────────┘
总结
- 启动文件(.s):定义处理器复位后执行的初始化代码,引用链接脚本中的符号
- 链接脚本(.ld):定义各段在内存中的位置,提供符号的具体地址
- 联系 :两者通过符号名在链接阶段建立联系,完成系统的内存初始化