2. SMBIOS 数据结构基础

SMBIOS 的数据组织遵循一套严格的分层结构:系统固件在内存中首先放置一个 Entry Point 结构,用于定位和管理后续的 结构表;结构表由多个 结构表头 开头的数据块组成,每个数据块尾部跟随 字符串区域,并且整个结构表在内存中按 双字节对齐 规则排列。理解这三层基础是开发 SMBIOS 解析器、模拟器或调试工具的前提。

2.1 SMBIOS Entry Point 结构

Entry Point 是操作系统或调试工具查找 SMBIOS 信息的入口。根据 SMBIOS 规范版本,存在两种 Entry Point 格式:SMBIOS 2.1~2.7 使用的 32 位 Entry Point(位于物理内存 0xF0000~0xFFFFF 之间,以 "_SM_" 签名开头),以及 SMBIOS 3.0+ 使用的 64 位 Entry Point(以 "_SM3_" 签名开头,可位于 4GB 以上地址空间)。

字段 偏移 长度 说明
Anchor String 0x00 4 字节 "_SM_" (0x5F534D5F)
Entry Point Structure Checksum 0x04 1 字节 整个 Entry Point 结构的校验和
Entry Point Length 0x05 1 字节 Entry Point 结构总长度,通常为 0x1F
Major Version 0x06 1 字节 SMBIOS 主版本号,如 3
Minor Version 0x07 1 字节 SMBIOS 次版本号,如 5
Maximum Structure Size 0x08 2 字节 最大结构体大小,用于分配缓冲区
Entry Point Revision 0x0A 1 字节 实现修订版本
Formatted Area 0x0B 5 字节 保留或特定实现使用,通常填 0
Intermediate Anchor String 0x10 5 字节 "_DMI_" (0x5F444D495F)
Intermediate Checksum 0x15 1 字节 从 0x10 开始的中间区域校验和
Structure Table Length 0x16 2 字节 整个结构表的总字节数
Structure Table Address 0x18 4 字节 结构表在物理内存中的 32 位基地址
Number of SMBIOS Structures 0x1C 2 字节 结构表中包含的结构总数
SMBIOS BCD Revision 0x1E 1 字节 BCD 格式的版本号,如 0x35 表示 3.5
调试要点: 在 UEFI 环境下,Entry Point 通常通过 UEFI 配置表 GUID SMBIOS_TABLE_GUID 或 SMBIOS3_TABLE_GUID 获取,不再需要扫描 0xF0000 区域。但在 Legacy BIOS 调试时,扫描内存并验证 "_SM_" 签名和校验和是第一步。

2.2 结构表头格式

每个 SMBIOS 结构都以一个 4 字节的固定表头 开始,紧接的是结构体特有的格式化数据区域。表头定义了结构的类型、长度和句柄,是解析所有结构体的统一入口。

字段 偏移 长度 说明
Type 0x00 1 字节 结构类型编号,如 Type 0=BIOS Information
Length 0x01 1 字节 格式化区域长度(含表头),不包括字符串
Handle 0x02 2 字节 唯一标识符,用于交叉引用

表头之后是 格式化区域,其长度由 Length 字段指定。格式化区域中可能包含指向字符串区域的索引值(通常为 1 字节或 2 字节的编号),这些编号用于引用结构体末尾的字符串。

关键规则: 结构表头中的 Length 字段 不包含 字符串区域的长度。解析器必须通过扫描字符串区域中的双空终止符(\0\0)来确定字符串区域的结束位置,从而定位下一个结构体的起始地址。

2.3 字符串区域与索引规则

字符串区域紧跟在每个结构的格式化数据之后,用于存储结构体中引用的所有字符串(如厂商名称、版本号、序列号等)。字符串区域采用 连续存储、双空终止 的布局方式。

  • 存储顺序: 字符串按结构体格式化区域中出现的索引顺序依次存放,每个字符串以 \0 结尾。
  • 索引规则: 格式化区域中引用字符串的字段值为 1-based 索引。索引值 1 指向字符串区域中的第一个字符串,索引值 2 指向第二个,以此类推。索引值 0 表示该字段未使用或为空字符串。
  • 区域终止: 字符串区域以 两个连续的 \0 字节 结束,表示该结构体的字符串列表结束。
  • 空字符串: 如果某个字符串字段的索引值为 0,解析器应返回空字符串或 NULL,而不是尝试从字符串区域读取。
/* 字符串区域解析示例:假设结构体格式化区域中引用了索引 1 和 2 */
/* 内存布局: "ACME Corp.\0" "Rev 1.0\0" "\0\0" */
/* 索引 1 -> "ACME Corp." */
/* 索引 2 -> "Rev 1.0"   */
/* 索引 3 -> 无效,返回空字符串 */
常见陷阱: 某些固件实现可能将字符串区域中的字符串按非连续顺序存储,或包含重复字符串。调试时务必按照索引顺序逐个扫描 \0 分隔符,而不是假设字符串按固定偏移排列。另外,字符串区域中的字符串 不包含 引号或额外转义字符,直接以原始 ASCII/UTF-8 编码存储。

2.4 双字节对齐规则

SMBIOS 规范要求整个结构表在内存中按 双字节(2 字节)对齐。这意味着每个结构体的结束位置(即下一个结构体的起始位置)必须落在偶数地址上。如果字符串区域结束后地址为奇数,则填充一个 \0 字节使其对齐。

场景 字符串区域结束地址 对齐填充 下一个结构体起始地址
偶数地址 0x1000 无填充 0x1000
奇数地址 0x1001 1 字节 \0 0x1002

对齐填充字节 不计入 前一个结构体的 Length 字段,也不属于字符串区域的一部分。解析器在计算下一个结构体偏移时,必须显式进行对齐处理。

/* 计算下一个结构体偏移的伪代码 */
uint8_t* next_structure(uint8_t* current_struct) {
    uint8_t* formatted_end = current_struct + current_struct->Length;
    uint8_t* string_end = formatted_end;

    /* 扫描字符串区域直到双空终止符 */
    while (!(string_end[0] == 0 && string_end[1] == 0)) {
        string_end++;
    }
    string_end += 2;  /* 跳过双空终止符 */

    /* 双字节对齐 */
    if ((uintptr_t)string_end % 2 != 0) {
        string_end++;
    }

    return string_end;
}
调试技巧: 在解析 SMBIOS 结构表时,建议先遍历一遍所有结构体,验证每个结构体的 Length 字段是否合理、字符串区域是否以双空终止符结束、以及对齐填充是否正确。许多固件 bug 都出现在对齐填充或字符串区域边界处理上。

2.5 整体数据结构关系图

下图展示了 SMBIOS 数据结构从 Entry Point 到结构表、再到单个结构体内部布局的完整层次关系:

SMBIOS 数据结构层次关系 SMBIOS Entry Point (32-bit 或 64-bit) 签名 ("_SM_" / "_SM3_") | 校验和 | 版本号 | 结构表地址 | 结构数量 位于 0xF0000~0xFFFFF 或 UEFI 配置表 SMBIOS 结构表 (Structure Table) 连续存放多个结构体,整体双字节对齐 每个结构体由 [表头 + 格式化数据 + 字符串区域 + 对齐填充] 组成 单个 SMBIOS 结构体内部布局 结构表头 (4 字节) Type | Length | Handle 格式化数据区域 包含字符串索引值 字符串区域 以双空终止符结束 对齐填充 0 或 1 字节 \0 说明:字符串区域结束后,若地址为奇数则填充 1 字节 \0 实现双字节对齐; 对齐填充字节不计入前一个结构体的 Length 字段。

2.6 结构表遍历流程

基于上述规则,解析 SMBIOS 结构表的完整流程如下:

  1. 定位 Entry Point: 在 Legacy BIOS 下扫描 0xF0000~0xFFFFF 区域查找 "_SM_" 签名;在 UEFI 下通过 gEfiSmbiosTableGuid 或 gEfiSmbios3TableGuid 获取入口地址。
  2. 验证 Entry Point: 计算校验和,确认版本号,读取结构表基地址和总长度。
  3. 初始化遍历指针: 将指针指向结构表基地址,设置剩余长度计数器。
  4. 解析当前结构体: 读取 Type、Length、Handle,然后跳过 Length 指定的格式化区域。
  5. 扫描字符串区域: 从格式化区域末尾开始,查找双空终止符 \0\0,确定字符串区域结束位置。
  6. 应用双字节对齐: 如果字符串区域结束地址为奇数,则跳过 1 字节填充。
  7. 移动到下一个结构体: 更新指针到对齐后的地址,重复步骤 4~6,直到遍历完所有结构体或超出结构表总长度。
  8. 终止检测: 遇到 Type=127(End-of-Table)结构体时,停止遍历。
开发建议: 在实现 SMBIOS 解析器时,建议将 Entry Point 解析、结构体遍历、字符串提取封装为独立的模块。遍历过程中加入边界检查和校验和验证,可以有效防御固件中的异常数据导致解析崩溃。对于嵌入式系统调试场景,可以在串口或日志中输出每个结构体的 Type、Handle 和字符串摘要,便于快速定位问题。