3. 固件镜像头部定义

固件镜像头部定义 学习卡片

任务目标

为Bootloader差分OTA机制定义统一的固件镜像头部结构,包含魔数、版本号、镜像长度、CRC32校验值等关键字段,并实现对应的头部解析函数,使Bootloader能够识别合法镜像并提取升级所需元数据。

镜像头部结构定义

头部采用固定长度64字节设计,字段按小端序存储。魔数用于快速识别固件合法性,版本号采用主版本+次版本组合,CRC32覆盖整个镜像文件(含头部自身)。头部结构体定义如下:

// firmware_header.h
#ifndef FIRMWARE_HEADER_H
#define FIRMWARE_HEADER_H

#include <stdint.h>

#define FW_HEADER_MAGIC      0x4649524D  // "FIRM"
#define FW_HEADER_SIZE       64
#define FW_VERSION_MAJOR_MIN 1
#define FW_VERSION_MINOR_MIN 0

#pragma pack(push, 1)
typedef struct {
    uint32_t magic;          // 魔数,固定为0x4649524D
    uint16_t version_major;  // 主版本号
    uint16_t version_minor;  // 次版本号
    uint32_t image_length;   // 固件总长度(含头部)
    uint32_t crc32;          // 整个镜像的CRC32校验值
    uint32_t reserved[3];    // 保留字段,置0
    uint8_t  reserved2[32];  // 保留区域,置0
} firmware_header_t;
#pragma pack(pop)

// 解析结果状态码
#define FW_PARSE_OK          0
#define FW_ERR_MAGIC        -1
#define FW_ERR_VERSION      -2
#define FW_ERR_LENGTH       -3
#define FW_ERR_CRC          -4

// 解析头部并校验
int firmware_header_parse(const uint8_t *image, uint32_t image_size,
                          firmware_header_t *out_header);

// 计算CRC32(标准多项式0xEDB88320)
uint32_t crc32_compute(const uint8_t *data, uint32_t len);

#endif

头部解析实现

// firmware_header.c
#include "firmware_header.h"
#include <string.h>

// CRC32查表法实现
static uint32_t crc32_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;
        for (int j = 0; j < 8; j++) {
            c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1);
        }
        crc32_table[i] = c;
    }
    crc_table_ready = 1;
}

uint32_t crc32_compute(const uint8_t *data, uint32_t len) {
    if (!crc_table_ready) crc32_init_table();
    uint32_t crc = 0xFFFFFFFF;
    for (uint32_t i = 0; i < len; i++) {
        crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
    }
    return crc ^ 0xFFFFFFFF;
}

int firmware_header_parse(const uint8_t *image, uint32_t image_size,
                          firmware_header_t *out_header) {
    if (!image || !out_header || image_size < FW_HEADER_SIZE) {
        return FW_ERR_LENGTH;
    }

    // 拷贝头部数据(防止对齐问题)
    memcpy(out_header, image, FW_HEADER_SIZE);

    // 1. 校验魔数
    if (out_header->magic != FW_HEADER_MAGIC) {
        return FW_ERR_MAGIC;
    }

    // 2. 校验版本号范围
    if (out_header->version_major < FW_VERSION_MAJOR_MIN ||
        out_header->version_minor < FW_VERSION_MINOR_MIN) {
        return FW_ERR_VERSION;
    }

    // 3. 校验镜像长度合法性
    if (out_header->image_length != image_size ||
        out_header->image_length < FW_HEADER_SIZE) {
        return FW_ERR_LENGTH;
    }

    // 4. 校验CRC32(覆盖整个镜像)
    uint32_t calc_crc = crc32_compute(image, image_size);
    if (calc_crc != out_header->crc32) {
        return FW_ERR_CRC;
    }

    return FW_PARSE_OK;
}

最小验证程序

// test_header.c
#include <stdio.h>
#include <string.h>
#include "firmware_header.h"

int main(void) {
    // 构造一个合法的测试镜像
    uint8_t image[256];
    memset(image, 0, sizeof(image));

    firmware_header_t hdr;
    hdr.magic         = FW_HEADER_MAGIC;
    hdr.version_major = 1;
    hdr.version_minor = 2;
    hdr.image_length  = sizeof(image);
    hdr.crc32         = 0;  // 先置0,后面计算
    memset(hdr.reserved, 0, sizeof(hdr.reserved));
    memset(hdr.reserved2, 0, sizeof(hdr.reserved2));

    memcpy(image, &hdr, FW_HEADER_SIZE);
    // 填充一些镜像数据(模拟固件内容)
    for (int i = FW_HEADER_SIZE; i < (int)sizeof(image); i++) {
        image[i] = (uint8_t)(i * 3);
    }

    // 计算并写入CRC
    uint32_t crc = crc32_compute(image, sizeof(image));
    memcpy(image + 8, &crc, 4);  // crc32字段偏移为8

    // 解析验证
    firmware_header_t parsed;
    int ret = firmware_header_parse(image, sizeof(image), &parsed);
    if (ret == FW_PARSE_OK) {
        printf("解析成功: 版本=%u.%u, 长度=%u, CRC=0x%08X\n",
               parsed.version_major, parsed.version_minor,
               parsed.image_length, parsed.crc32);
    } else {
        printf("解析失败, 错误码=%d\n", ret);
    }

    // 测试错误魔数场景
    image[0] = 0x00;
    ret = firmware_header_parse(image, sizeof(image), &parsed);
    printf("错误魔数测试: 返回码=%d (预期=%d)\n", ret, FW_ERR_MAGIC);

    return 0;
}

编译与运行

# 编译(需支持C99或以上标准)
gcc -Wall -Wextra -o test_header firmware_header.c test_header.c

# 运行验证
./test_header

验证方法与通过标准

  • 正常路径:程序输出“解析成功: 版本=1.2, 长度=256, CRC=0xXXXXXXXX”,且CRC值与实际计算一致,说明头部解析和CRC校验逻辑正确。
  • 异常路径:错误魔数测试返回码为-1(FW_ERR_MAGIC),证明非法镜像能被正确拒绝。
  • 边界测试:可自行修改image_size小于64字节,应返回FW_ERR_LENGTH;修改头部版本号为0,应返回FW_ERR_VERSION。

关键说明

  • 头部采用#pragma pack(1)确保结构体在内存中紧密排列,无填充字节,保证跨平台解析一致性。
  • CRC32计算覆盖整个镜像(含头部),Bootloader在升级前必须对完整镜像做此校验,防止传输损坏或非法篡改。
  • 保留字段预留32字节,可用于后续扩展(如签名信息、硬件兼容性标记等),当前实现将其置0并在解析时不作额外检查。
  • 实际Bootloader环境中,解析后的头部信息应传递给差分合并模块,用于确认目标版本与当前运行版本的关系,决定是否执行回滚操作。