2. 链接脚本定制
任务目标
编写GNU LD链接脚本,将Bootloader与应用程序分别映射到独立Flash区域,并确保Bootloader入口地址与向量表位置一致。本案例面向STM32F103系列(Flash起始0x08000000,容量512KB),Bootloader占用前32KB,应用程序紧随其后。
环境依赖
- ARM交叉编译器:
arm-none-eabi-gcc(含GNU LD) - 目标芯片:STM32F103C8T6(Flash 64KB)或兼容型号
- 工程文件:
bootloader.ld、main.c、startup.s(最小启动文件)
链接脚本实现
以下链接脚本将Flash划分为两个区域:FLASH_BL(0x08000000,长度32KB)与FLASH_APP(0x08008000,长度32KB)。Bootloader的入口地址通过ENTRY(Reset_Handler)指定,向量表首地址与_bl_start对齐。
/* bootloader.ld */
ENTRY(Reset_Handler)
MEMORY
{
FLASH_BL (rx) : ORIGIN = 0x08000000, LENGTH = 32K
FLASH_APP (rx) : ORIGIN = 0x08008000, LENGTH = 32K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
_estack = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH_BL
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
. = ALIGN(4);
} > FLASH_BL
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} > FLASH_BL
_bl_end = .;
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
_edata = .;
} > RAM AT> FLASH_BL
.bss :
{
. = ALIGN(4);
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
_ebss = .;
__bss_end__ = _ebss;
} > RAM
. = ALIGN(4);
_end = .;
}
最小启动与主程序
/* 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:
ldr r0, =_sbss
ldr r1, =_ebss
movs r2, #0
bss_loop:
cmp r0, r1
bge data_init
str r2, [r0], #4
b bss_loop
data_init:
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_bl_end
data_loop:
cmp r0, r1
bge main_call
ldr r3, [r2], #4
str r3, [r0], #4
b data_loop
main_call:
bl main
b .
/* main.c */
#include <stdint.h>
void main(void)
{
volatile uint32_t *bl_flag = (uint32_t *)0x20000000;
*bl_flag = 0xDEADBEEF; /* Bootloader运行标志 */
while (1) { }
}
编译与链接命令
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-objcopy -O binary bootloader.elf bootloader.bin
arm-none-eabi-objdump -h bootloader.elf
验证方法与判定标准
- 区域隔离验证:执行
arm-none-eabi-objdump -h bootloader.elf,确认.isr_vector与.text的VMA(虚拟地址)均在0x08000000~0x08007FFF范围内,且.data的LMA(加载地址)位于FLASH_BL区域,VMA位于RAM。 - 入口地址验证:执行
arm-none-eabi-nm bootloader.elf | grep Reset_Handler,预期输出地址为08000000或08000004(取决于向量表对齐),与链接脚本中ENTRY声明一致。 - 边界检查:
_bl_end符号值应小于0x08008000(FLASH_APP起始地址),可通过arm-none-eabi-nm bootloader.elf | grep _bl_end确认。 - 烧录验证:将
bootloader.bin烧录至芯片0x08000000地址,复位后观察RAM地址0x20000000处数值为0xDEADBEEF(通过调试器读取),证明Bootloader正常启动。
常见问题
- 链接错误“region FLASH_BL overflowed”:Bootloader代码超过32KB,需减小
LENGTH或优化代码体积。 - 应用程序无法跳转:若后续实现跳转功能,需在应用程序链接脚本中将
FLASH_APP的ORIGIN设为0x08008000,并确保应用程序向量表偏移(通过SCB->VTOR设置)。 - 启动文件缺失:本案例使用最小启动文件,实际工程中需补充系统时钟初始化与C库启动代码(如
__libc_init_array)。