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

验证方法

  1. 地址验证:执行arm-none-eabi-nm app.elf | grep app_main,预期输出地址为08008000(或08008001表示Thumb模式),确认入口函数被放置在Flash起始位置。
  2. 反汇编验证:执行arm-none-eabi-objdump -d app.elf,查看0x08008000处是否为app_main的第一条指令。
  3. 跳转验证:在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),但链接脚本定位原理相同。