2. 应用程序入口点定义
任务目标
本案例为应用程序定义明确的起始地址与入口函数,使Bootloader在完成校验后能够精确跳转到应用程序代码段。核心工作是:在链接脚本中固定应用程序的加载地址,并在C代码中声明一个带__attribute__((section(".app_entry")))的入口函数,将该函数放置在链接脚本指定的起始位置。
实现方案
采用链接脚本定位 + 段属性声明的方式。应用程序的Flash起始地址设为0x08008000(偏移32KB,为Bootloader预留空间),入口函数app_main被强制放入.app_entry段,该段在链接脚本中被定位到Flash起始地址。Bootloader跳转时直接调用该地址处的函数指针即可。
关键完整代码
// app_entry.h
#ifndef APP_ENTRY_H
#define APP_ENTRY_H
// 应用程序入口函数声明,放置在.app_entry段
#define APP_ENTRY __attribute__((section(".app_entry")))
void APP_ENTRY app_main(void);
// 应用程序起始地址(与链接脚本保持一致)
#define APP_FLASH_BASE 0x08008000u
#endif // APP_ENTRY_H
// app_entry.c
#include "app_entry.h"
// 入口函数实现:初始化后进入主循环
void APP_ENTRY app_main(void)
{
// 应用程序主逻辑(示例:空循环占位)
volatile uint32_t counter = 0;
while (1) {
counter++;
}
}
// app.ld(链接脚本关键片段)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 384K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
. = ORIGIN(FLASH);
.app_entry :
{
KEEP(*(.app_entry)) /* 入口函数固定在起始地址 */
} > FLASH
.text :
{
*(.text*)
*(.rodata*)
} > FLASH
/* 其余段定义省略 */
}
// Bootloader跳转代码(调用方式)
typedef void (*pFunction)(void);
void jump_to_app(void)
{
// 检查栈顶值合法性(可选)
uint32_t app_stack = *(volatile uint32_t *)APP_FLASH_BASE;
if ((app_stack & 0x2FFE0000) != 0x20000000) {
return; // 栈顶非法,不跳转
}
// 跳转到入口函数(入口地址 = Flash起始地址 + 4字节偏移处存放的函数指针)
pFunction app_entry = (pFunction)(*(volatile uint32_t *)(APP_FLASH_BASE + 4));
app_entry();
}
编译与链接
# 编译应用程序(以ARM GCC为例)
arm-none-eabi-gcc -c app_entry.c -o app_entry.o -mcpu=cortex-m4 -mthumb
arm-none-eabi-gcc -T app.ld app_entry.o -o app.elf -mcpu=cortex-m4 -mthumb -nostdlib
# 查看入口函数是否位于正确地址
arm-none-eabi-nm app.elf | grep app_main
验证方法
- 地址验证:执行
arm-none-eabi-nm app.elf | grep app_main,预期输出地址为08008000(或08008001表示Thumb模式),确认入口函数被放置在Flash起始位置。 - 反汇编验证:执行
arm-none-eabi-objdump -d app.elf,查看0x08008000处是否为app_main的第一条指令。 - 跳转验证:在Bootloader中调用
jump_to_app()后,通过调试器观察PC寄存器是否跳转到0x08008000地址处执行。
通过条件:入口函数地址与链接脚本中ORIGIN(FLASH)一致,且Bootloader跳转后能执行到应用程序代码。
关键说明
- 链接脚本中
KEEP()防止链接器优化掉未引用的入口段;.app_entry段必须放在Flash起始位置,确保入口地址固定。 - Bootloader跳转时读取
APP_FLASH_BASE + 4处的值作为函数指针,这是Cortex-M架构的标准做法——起始地址存放初始栈顶,紧接着存放复位向量(即入口函数地址)。 - 若使用IAR或Keil,需将
__attribute__((section(".app_entry")))替换为对应的段声明语法(如#pragma location),但链接脚本定位原理相同。