1. OTA安全需求分析与威胁建模
任务目标
在STM32平台上实现两个基础功能:读取当前固件版本号,并计算固件区域的CRC32校验值。该功能作为OTA升级的前置模块,用于版本比对与固件完整性验证。
实现方案
将版本号以字符串常量形式固化在Flash的固定地址段,通过链接脚本保证其位置确定。CRC32计算采用查表法,对内部Flash的固件区域(从起始地址到当前程序运行地址之前)进行校验。核心代码包含版本读取接口与CRC32计算接口,不依赖外部库。
关键完整代码
// firmware_info.h
#ifndef FIRMWARE_INFO_H
#define FIRMWARE_INFO_H
#include <stdint.h>
#define FW_VERSION_MAJOR 1
#define FW_VERSION_MINOR 0
#define FW_VERSION_PATCH 0
// 版本信息存放在Flash固定地址(由链接脚本保证)
#define FW_VERSION_ADDR 0x0800F000
typedef struct {
uint8_t major;
uint8_t minor;
uint8_t patch;
uint8_t reserved;
} fw_version_t;
void fw_get_version(fw_version_t *ver);
uint32_t fw_calc_crc32(uint32_t start_addr, uint32_t length);
#endif // FIRMWARE_INFO_H
// firmware_info.c
#include "firmware_info.h"
#include <string.h>
// 版本号常量,放置在指定Flash地址
const fw_version_t fw_version __attribute__((section(".fw_version_section"))) = {
FW_VERSION_MAJOR, FW_VERSION_MINOR, FW_VERSION_PATCH, 0
};
// CRC32查表法实现(多项式 0x04C11DB7)
static uint32_t crc_table[256];
static uint8_t crc_table_ready = 0;
static void crc32_init_table(void) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t c = i << 24;
for (uint8_t k = 0; k < 8; k++) {
c = (c & 0x80000000) ? (c << 1) ^ 0x04C11DB7 : (c << 1);
}
crc_table[i] = c;
}
crc_table_ready = 1;
}
void fw_get_version(fw_version_t *ver) {
memcpy(ver, (const void*)FW_VERSION_ADDR, sizeof(fw_version_t));
}
uint32_t fw_calc_crc32(uint32_t start_addr, uint32_t length) {
if (!crc_table_ready) {
crc32_init_table();
}
uint32_t crc = 0xFFFFFFFF;
const uint8_t *data = (const uint8_t*)start_addr;
for (uint32_t i = 0; i < length; i++) {
crc = (crc << 8) ^ crc_table[((crc >> 24) ^ data[i]) & 0xFF];
}
return ~crc;
}
// 链接脚本片段(.ld文件添加)
.fw_version_section 0x0800F000 :
{
KEEP(*(.fw_version_section))
} > FLASH
// 调用示例
#include "firmware_info.h"
void ota_precheck(void) {
fw_version_t ver;
fw_get_version(&ver);
// 计算固件区CRC(从0x08000000到当前代码之前)
extern uint32_t _sidata; // 由链接脚本提供
uint32_t fw_end = (uint32_t)&_sidata;
uint32_t crc = fw_calc_crc32(0x08000000, fw_end - 0x08000000);
// 此时ver.major/minor/patch为版本号,crc为校验值
// 可将两者通过串口或调试器输出
}
编译与运行
- 将上述代码加入STM32工程(如STM32CubeIDE或Keil),确保链接脚本包含
.fw_version_section段定义。 - 编译工程,确认无错误。在链接映射文件(.map)中检查
fw_version符号地址是否为0x0800F000。 - 烧录程序后,在
ota_precheck()处设置断点,通过调试器查看ver结构体变量值应为1.0.0。 - 记录
crc变量值,该值即为当前固件区域的CRC32校验结果。
验证方法
- 版本读取验证:调试器中观察
ver.major==1、ver.minor==0、ver.patch==0,与宏定义一致。 - CRC32验证:将
fw_calc_crc32计算的起始地址改为0x08000000、长度为0x1000(4KB),与PC端用相同算法计算该区域二进制文件的CRC值比对,结果应一致。 - 确定性验证:连续两次调用
fw_calc_crc32,返回值必须相同;修改Flash中任意一个字节后重新计算,CRC值应发生变化。
注意事项
- 版本号地址
0x0800F000需避开实际代码占用区域,建议放在Flash末尾前预留扇区。 - CRC计算范围应排除版本号自身所在扇区(若包含则每次升级版本号变化会导致CRC变化,无法作为固件完整性依据)。
- 若使用STM32CubeIDE,链接脚本修改后需重新生成工程或手动更新
.ld文件。