2. Bootloader中实现内存映射与链接脚本定制

任务目标

为Bootloader编写定制链接脚本,将启动代码固定在0x08000000(Flash起始地址),中断向量表紧随其后,并将可读写数据与堆栈放置在内部SRAM(起始0x20000000)。最终通过编译产物映射文件验证各段地址符合预期。

环境与依赖

  • ARM交叉编译器:arm-none-eabi-gcc(含ld、objdump)
  • 目标MCU:STM32F103系列(Cortex-M3,Flash 0x08000000,SRAM 0x20000000)
  • 文件清单:startup.s(启动代码)、main.c(主函数)、bootloader.ld(链接脚本)

关键实现

链接脚本是核心。通过MEMORY命令定义物理存储区域,SECTIONS命令将各段映射到指定地址。启动代码必须位于Flash起始地址,因此.isr_vector段排在最前;.text紧随其后;.data的LMA(加载地址)在Flash,VMA(运行地址)在SRAM,由启动代码完成拷贝;.bss和堆栈直接在SRAM。

完整代码

bootloader.ld

/* 内存区域定义 */
MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 32K
  SRAM  (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

/* 栈顶地址 = SRAM起始 + SRAM大小 */
_estack = ORIGIN(SRAM) + LENGTH(SRAM);

SECTIONS
{
  /* 中断向量表:必须从Flash起始地址开始 */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } > FLASH

  /* 代码段 */
  .text :
  {
    . = ALIGN(4);
    *(.text*)
    *(.rodata*)
    . = ALIGN(4);
    _etext = .;
  } > FLASH

  /* 初始化数据:LMA在Flash,VMA在SRAM */
  _sidata = LOADADDR(.data);
  .data :
  {
    . = ALIGN(4);
    _sdata = .;
    *(.data*)
    . = ALIGN(4);
    _edata = .;
  } > SRAM AT> FLASH

  /* 零初始化数据 */
  .bss :
  {
    . = ALIGN(4);
    _sbss = .;
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;
  } > SRAM

  /* 堆(可选) */
  .heap :
  {
    . = ALIGN(4);
    _sheap = .;
    . = . + 2K;
    _eheap = .;
  } > SRAM
}

startup.s(关键片段,演示向量表与段拷贝)

.syntax unified
.cpu cortex-m3
.thumb

.section .isr_vector
.word _estack          /* 栈顶 */
.word Reset_Handler    /* 复位向量 */

.section .text
.thumb_func
.global Reset_Handler
Reset_Handler:
    /* 拷贝.data段从Flash到SRAM */
    ldr r0, =_sdata
    ldr r1, =_edata
    ldr r2, =_sidata
copy_loop:
    cmp r0, r1
    bge 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
    bge zero_done
    str r2, [r0], #4
    b zero_loop
zero_done:
    bl main
    b .

main.c

void main(void) {
    volatile int counter = 0;
    while (1) {
        counter++;
    }
}

编译与链接命令

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 bootloader.ld startup.o main.o -o bootloader.elf
arm-none-eabi-objdump -h bootloader.elf

验证方法与判定标准

  1. 地址验证:执行objdump -h,检查输出中:
    - .isr_vector的VMA为0x08000000
    - .text紧随其后(VMA > 0x08000000且连续)
    - .data的VMA在0x20000000区域,LMA在Flash区域
    - .bss的VMA在SRAM区域
  2. 符号验证:arm-none-eabi-nm bootloader.elf | grep _estack,应输出20005000(0x20000000+20K)。
  3. 反汇编验证:arm-none-eabi-objdump -d bootloader.elf,确认Reset_Handler位于Flash地址(0x08000000+偏移),且第一条指令为栈指针初始化。

若以上地址均符合预期,则链接脚本定制成功,启动代码将在指定地址正确运行。