3. 编写链接脚本控制内存布局
任务目标
编写一个GNU ld链接脚本,将编译生成的ELF可执行文件中的代码段(.text)、数据段(.data)和BSS段(.bss)分别放置到指定的内存区域,并验证各段实际加载地址符合预期。
环境与依赖
- 交叉编译器:arm-none-eabi-gcc(或gcc + ld,本案例以ARM裸机为例)
- 目标平台:任意ARM Cortex-M系列(无需真实硬件,用readelf验证即可)
实现步骤
创建三个文件:链接脚本 memory.ld、启动文件 startup.s、主程序 main.c。链接脚本定义两块内存区域:FLASH(起始0x08000000,大小1MB)和RAM(起始0x20000000,大小128KB)。代码段放入FLASH,数据段和BSS段放入RAM。
关键完整代码
/* memory.ld - 链接脚本 */
ENTRY(Reset_Handler)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text :
{
*(.isr_vector) /* 中断向量表 */
*(.text*) /* 代码段 */
*(.rodata*) /* 只读数据 */
_etext = .; /* 代码段结束地址 */
} > FLASH
.data :
{
_sdata = .; /* 数据段起始地址 */
*(.data*) /* 初始化数据 */
_edata = .; /* 数据段结束地址 */
} > RAM AT> FLASH /* 加载域在FLASH,运行域在RAM */
.bss :
{
_sbss = .; /* BSS起始地址 */
*(.bss*)
*(COMMON)
_ebss = .; /* BSS结束地址 */
} > RAM
/DISCARD/ :
{
*(.ARM.exidx*)
*(.comment*)
}
}
/* startup.s - 最小启动代码 */
.syntax unified
.cpu cortex-m3
.thumb
.section .isr_vector
.word 0x20002000 /* 初始SP */
.word Reset_Handler /* 初始PC */
.section .text
.thumb_func
.global Reset_Handler
Reset_Handler:
/* 复制.data段从FLASH到RAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_etext
copy_loop:
cmp r0, r1
beq copy_done
ldr r3, [r2], #4
str r3, [r0], #4
b copy_loop
copy_done:
/* 清零.bss段 */
ldr r0, =_sbss
ldr r1, =_ebss
movs r2, #0
zero_loop:
cmp r0, r1
beq zero_done
str r2, [r0], #4
b zero_loop
zero_done:
bl main
dead_loop:
b dead_loop
/* main.c */
int global_var = 42; /* 放入.data段 */
int uninit_var; /* 放入.bss段 */
int main(void)
{
volatile int local = global_var;
uninit_var = local + 1;
return 0;
}
编译与链接命令
arm-none-eabi-as -mcpu=cortex-m3 -mthumb startup.s -o startup.o
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c main.c -o main.o
arm-none-eabi-ld -T memory.ld startup.o main.o -o program.elf
arm-none-eabi-objdump -h program.elf
验证方法与判定标准
执行 arm-none-eabi-objdump -h program.elf,观察输出中的Section Headers表格。判定通过的三个条件:
- .text段的VMA(虚拟地址)和LMA(加载地址)均为
08000000起始的FLASH区域; - .data段的VMA为
20000000起始的RAM区域,而LMA为FLASH区域(紧跟在.text段之后),证明加载域与运行域分离; - .bss段的VMA在RAM区域且紧跟在.data段之后,LMA与VMA相同(BSS无需加载数据)。
预期输出关键行示例(地址数值可能因编译细节略有差异):
Idx Name Size VMA LMA File off Algn
0 .isr_vector 00000008 08000000 08000000 00010000 2**2
1 .text 000000xx 08000008 08000008 00010008 2**2
2 .data 00000004 20000000 080000xx 00020000 2**2
3 .bss 00000004 20000004 20000004 00020004 2**2
若输出中.data段的VMA与LMA不同(VMA在RAM、LMA在FLASH),且.bss段VMA在RAM中,则链接脚本的内存布局控制生效,任务完成。