为什么需要校验APP?

当Bootloader准备跳转到应用程序时,它必须确认这个APP是"健康"的,而不是一个损坏的、不完整的或错误的数据。如果不加校验直接跳转,可能导致:

  1. "变砖"风险:APP固件不完整或损坏,单片机可能卡死在未知状态,无法再通过正常方式进入Bootloader进行恢复。

  2. 不可预测的行为:程序跑飞,外设错误操作,甚至引发硬件安全问题。

  3. 升级失败无感知:通过OTA或串口升级后,虽然烧录过程没有报错,但固件在传输或存储过程中可能已出错,系统却依然尝试启动损坏的APP。

校验的

常见的校验方法

主要有三种,按实现复杂度和安全性递增:

1. 栈指针(SP)校验

这是最基本、必须做的校验。

  • 原理:在Cortex-M等ARM芯片中,向量表的第一个字(地址APP_BASE + 0)存放的是主堆栈指针的初始值。这个值必须指向一个有效的、可读写的RAM地址。

  • 校验方法

    #define APP_ADDRESS 0x08010000 // APP起始地址

    // 获取APP向量表中的初始SP值
    uint32_t app_sp = *(volatile uint32_t *)APP_ADDRESS;

    // 校验SP值是否在合理的RAM范围内
    if ((app_sp < RAM_START) || (app_sp > (RAM_END - 1024))) { // 留一些栈空间余量
        // SP无效,不跳转
        return ERROR_INVALID_SP;
    } else {
        // SP看起来有效,可以进行下一步校验或跳转
    }

目的就是在跳转前,最后一次确认APP固件的完整性和正确性

  • 优点:简单、快速,能过滤掉大部分完全无效的固件(如全0xFF或全0x00)。

  • 缺点:无法检测固件代码本身的错误或数据损坏

  • 2. 校验和

    这是一种轻量级的完整性校验。

  • 原理:对APP固件的整个或部分二进制数据,进行累加计算,得到一个校验和值。将这个值与预先计算好并存储在固定位置的值进行比较。

  • 实现步骤

    1. 构建阶段:在PC上生成APP二进制文件后,计算其校验和,并 appended到文件的末尾(或写入一个特定的Flash地址,如APP区的最后4个字节)。

    2. Bootloader中:读取APP区的数据(不包括存储校验和本身的那部分),同样计算校验和,再与存储的校验和值进行比较。

  • 代码示例(简单求和校验)

  • uint32_t Calculate_Checksum(uint32_t *start_addr, uint32_t size_bytes) {
        uint32_t checksum = 0;
        uint32_t num_words = size_bytes / 4;
        
        for (uint32_t i = 0; i < num_words; i++) {
            checksum += start_addr[i];
        }
        return checksum;
    }

    bool Validate_APP_Checksum(void) {
        // 假设APP从 0x08010000 开始,大小为 0x30000
        // 校验和存储在APP区的最后4个字节
        uint32_t app_start = 0x08010000;
        uint32_t app_size = 0x30000 - 4; // 减去存储校验和本身的4字节
        uint32_t stored_checksum_addr = 0x08010000 + 0x30000 - 4;

        uint32_t calculated_checksum = Calculate_Checksum((uint32_t*)app_start, app_size);
        uint32_t stored_checksum = *(volatile uint32_t*)stored_checksum_addr;

        return (calculated_checksum == stored_checksum);
    }

  • 优点:实现简单,计算速度快。

  • 缺点:检错能力较弱。例如,两个数据字节交换位置,校验和可能不变。

  • CRC循环冗余校验

    这是工业界最常用、最推荐的方法。

  • 原理:CRC是一种更强的散列函数,能够检测出多位错误、突发性错误等。它比简单的校验和可靠得多。

  • 实现步骤

    1. 构建阶段:使用CRC算法(如CRC16、CRC32)计算APP固件的CRC值,并将其存储在固件末尾的固定位置。

    2. Bootloader中:使用相同的CRC算法和多项式,对APP区数据重新计算CRC,并与存储的CRC值比对。

  • 代码示例(使用HAL库的CRC外设,如果芯片支持)

    c

    bool Validate_APP_CRC32(void) {
        // 启用CRC外设时钟
        __HAL_RCC_CRC_CLK_ENABLE();
        CRC_HandleTypeDef hcrc;
        hcrc.Instance = CRC;
        HAL_CRC_Init(&hcrc);
    
        // 计算APP区域的CRC(不包括存储CRC自身的4字节)
        uint32_t app_start = 0x08010000;
        uint32_t app_size_words = (0x30000 - 4) / 4; // 转换为32位字的数量
        uint32_t stored_crc_addr = 0x08010000 + 0x30000 - 4;
    
        // HAL_CRC_Calculate 会重置CRC计算单元并开始新计算
        uint32_t calculated_crc = HAL_CRC_Calculate(&hcrc, (uint32_t*)app_start, app_size_words);
        uint32_t stored_crc = *(volatile uint32_t*)stored_crc_addr;
    
        __HAL_RCC_CRC_CLK_DISABLE(); // 用完关闭时钟
    
        return (calculated_crc == stored_crc);
    }

    如果芯片没有硬件CRC,可以使用软件算法,网上有大量开源实现(如 crclib)。

  • 优点:检错能力极强,可靠性高。

  • 缺点:计算量比校验和稍大(但有硬件CRC支持的话速度很快)。

  • 完整的Bootloader跳转流程(包含校验)

    一个健壮的Bootloader跳转逻辑如下:

    c

    void Bootloader_JumpToApp(void) {
        uint32_t app_address = 0x08010000;
        uint32_t *app_vector_table = (uint32_t *)app_address;
    
        // 1. 检查APP区是否已被擦除(全0xFF)。如果是,说明没有APP。
        if (*app_vector_table == 0xFFFFFFFF) {
            printf("No APP found.\r\n");
            return;
        }
    
        // 2. 栈指针(SP)校验
        uint32_t app_sp = app_vector_table[0];
        if ((app_sp < 0x20000000) || (app_sp > 0x20010000)) { // 根据实际RAM修改
            printf("Invalid Stack Pointer.\r\n");
            return;
        }
    
        // 3. 程序计数器(PC)校验(可选,但推荐)
        uint32_t app_pc = app_vector_table[1];
        if ((app_pc < app_address) || (app_pc > (app_address + 0x30000))) { // 根据APP大小修改
            printf("Invalid Program Counter.\r\n");
            return;
        }
    
        // 4. CRC校验(或校验和)
        if (!Validate_APP_CRC32()) {
            printf("APP CRC Error! Firmware is corrupted.\r\n");
            return;
        }
    
        // 5. 所有校验通过,准备跳转
        printf("All checks passed. Jumping to APP...\r\n");
    
        // 禁用所有中断
        __disable_irq();
    
        // 设置向量表偏移(对于Cortex-M,如果APP设置了VTOR,这里也需要设置)
        SCB->VTOR = app_address;
    
        // 设置主堆栈指针
        __set_MSP(app_sp);
    
        // 获取复位处理函数地址并跳转
        uint32_t app_reset_handler = app_vector_table[1];
        void (*app_start)(void) = (void (*)(void))app_reset_handler;
    
        // 跳转!
        app_start();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值