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表格。判定通过的三个条件:

  1. .text段的VMA(虚拟地址)和LMA(加载地址)均为 08000000 起始的FLASH区域;
  2. .data段的VMA为 20000000 起始的RAM区域,而LMA为FLASH区域(紧跟在.text段之后),证明加载域与运行域分离;
  3. .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中,则链接脚本的内存布局控制生效,任务完成。