2. 理解MCUboot启动流程

任务说明

本案例目标是通过阅读MCUboot源码与调试手段,梳理从芯片复位到跳转应用镜像的完整启动流程。你需要明确MCUboot在启动各阶段执行的关键函数、数据结构与判定逻辑,并能在开发板上通过调试输出验证流程顺序。最终交付物为:一张启动流程阶段图、一份关键函数调用链说明,以及一组可执行的调试观测方法。

MCUboot启动流程总览

MCUboot的启动流程可划分为五个阶段:复位入口 → 平台初始化 → 启动前置检查 → 镜像选择与校验 → 跳转应用。整个流程的核心目标是在可信环境中完成对固件镜像的完整性/真实性验证,并决定启动哪个镜像(主镜像或恢复镜像)。

复位入口 Reset_Handler 平台初始化 sys_arch_init / 时钟/串口 启动前置检查 boot_prepare / 标志位 镜像选择校验 boot_select_image 跳转应用 boot_jump_to_image ① 向量表偏移 ② 仅最小必要外设 ③ 升级标志/回滚计数 ④ 签名/哈希校验 ⑤ 设置VTOR + 栈指针跳转

关键源码路径与函数调用链

以下梳理基于MCUboot v1.9+的典型实现(以Zephyr或裸机移植为例),核心代码位于boot/bootutil/src与boot/zephyr(或对应平台目录)。

阶段 关键函数 职责
复位入口 Reset_Handler → boot_platform_init 设置栈指针、调用系统初始化(时钟、调试串口)
前置检查 boot_prepare → boot_read_image_headers 读取镜像头、检查升级标志(swap_type)
镜像选择 boot_select_image → boot_validate_image 根据标志位选择主/恢复镜像,执行签名校验
跳转 boot_jump_to_image 设置VTOR、MSP,将PC指向应用入口

最小可复现调试工程

以下代码展示如何在MCUboot的main.c中添加阶段打印,用于观测启动流程。该实现基于Zephyr平台的MCUboot,使用printk输出阶段标记。

// 文件: boot/zephyr/main.c (添加调试打印)
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <bootutil/bootutil.h>
#include <bootutil/image.h>

/* 阶段标记宏 */
#define BOOT_DBG_STAGE(s) printk("[MCUBOOT] STAGE_%s\n", s)

void main(void)
{
    /* 阶段1: 平台初始化完成 */
    BOOT_DBG_STAGE("PLATFORM_INIT_DONE");

    /* 阶段2: 读取镜像头与升级标志 */
    struct boot_rsp rsp;
    int rc = boot_go(&rsp);
    if (rc != 0) {
        BOOT_DBG_STAGE("BOOT_FAILED");
        while (1) {}
    }

    /* 阶段3: 镜像校验通过,准备跳转 */
    BOOT_DBG_STAGE("IMAGE_VALIDATED");
    BOOT_DBG_STAGE("JUMP_TO_APP");

    /* 跳转由 boot_go 内部完成,此处不会返回 */
    while (1) {}
}

若使用裸机移植(如STM32),可在boot_prepare与boot_jump_to_image之间添加串口输出函数,逻辑相同。

验证方法与判定标准

验证步骤:

  1. 编译MCUboot并烧录至开发板(如nRF52840 DK或STM32F429)。
  2. 使用串口工具(115200-8-N-1)连接调试UART。
  3. 复位开发板,观察串口输出。
  4. 预期输出顺序(确定性输出,由代码逻辑唯一推出):
[MCUBOOT] STAGE_PLATFORM_INIT_DONE
[MCUBOOT] STAGE_IMAGE_VALIDATED
[MCUBOOT] STAGE_JUMP_TO_APP

判定标准:若串口依次出现上述三条打印且开发板应用镜像正常运行(如LED闪烁或应用串口输出),则证明启动流程正确。若缺少IMAGE_VALIDATED,说明镜像校验失败;若卡在PLATFORM_INIT_DONE后无输出,则问题在镜像选择或校验阶段。

补充观测点(可选)

如需更细粒度追踪,可在boot_validate_image内部添加打印,输出当前校验的镜像槽位(slot 0/1)与签名算法ID。但注意不要修改校验逻辑本身,仅添加观测代码。