Windows编程系列:获取系统BIOS信息

在前面的文章中,介绍过WMI的使用

https://www.cnblogs.com/zhaotianff/p/14764740.html

 

Win32 Provider下面提供了一个Win32_BIOS类,可以获取BIOS信息

 

 

我们还可以通过SMBIOS标准规范来获取BIOS信息

 

SMBIOS介绍

SMBIOS (System Management BIOS ,系统管理BIOS)是通过系统固件传递管理信息的标准,定义了主板或系统制造商以标准格式显示产品管理信息所需遵循的规范。

现在支持IA-32(x86)、x64(Intel 64和AMD64)、IA-64、Aarch32/64(ARM)。

 

获取SMBIOS原理

在非UEFI系统上,32位和64位SMBIOS入口点可以通过物理内存地址范围0x000F0000~0x000FFFFF的16字节边界上搜索锚字符串来定位,入口点封装了一些现有的DMI浏览器使用的中间锚字符串。

在UEFI系统上,32位SMBIOS入口结构可以通过在EFI配置表中查找SMBIOS GUID(SMBIOS_TABLE_GUID,{EB9D2D31-2D88-11D3-9A16-0090273FC14D})并使用相关指针来定位SMBIOS入口点结构;

64位SMBIOS入口点结构可以通过在EFI配置表中查找SMBIOS 3.x GUID(SMBIOS3_TABLE_GUID、{F2FD1544-9794-4A2C-992E-E5BBCF20E394})并使用相关指针来定位SMBIOS入口点结构。

 

Windows提供了一些SMBIOS相关API,我们可以直接去调用并获取SMBIOS,而不用直接操作物理内存。

 

GetSystemFirmwareTable

从固件表提供程序检索指定的固件表。

 

函数声明如下:

1 UINT GetSystemFirmwareTable(
2   [in]  DWORD FirmwareTableProviderSignature,
3   [in]  DWORD FirmwareTableID,
4   [out] PVOID pFirmwareTableBuffer,
5   [in]  DWORD BufferSize
6 );

 

参数说明:

[in] FirmwareTableProviderSignature

固件表提供程序的Id,该参数可选以下值

ID值 描述
'ACPI' ACPI固件表提供程序
'FIRM' 原始固件表提供程序
'RMSB' 原始SMBIOS固件表提供程序

这里我们使用的是'RMSB'

 

[in] FirmwareTableID

固件表Id,对于使用GetSystemFirmwareTable来获取原始SMBIOS固件表时,可以指定为0。

其它情况时,可以访问文末的参考资料进行了解

 

[out] pFirmwareTableBuffer

请求的固件表缓冲区指针,如果该参数设置为NULL,则返回缓冲区所需要的大小

 

[in] BufferSize

pFirmwareTableBuffer缓冲区的大小,以字节为单位

 

返回值

如果函数执行成功,返回写入缓冲区的字节数。

 

说明:可以两次调用GetSystemFirmwareTable函数,第一次将pFirmwareTableBuffer设置为NULL,获取所需缓冲区大再,再根据大小分配缓冲区后再次调用。

 

RawSMBIOSData

通过GetSystemFirmwareTable获取原始SMBIOS固件表,pFirmwareTableBuffer缓冲区是一个原始SMBIOS固件表RawSMBIOSData结构

定义如下:

复制代码
1 struct RawSMBIOSData
2 {
3     BYTE    Used20CallingMethod;
4     BYTE    SMBIOSMajorVersion;
5     BYTE    SMBIOSMinorVersion;
6     BYTE    DmiRevision;
7     DWORD   Length;
8     BYTE    SMBIOSTableData[];
9 };
复制代码

 

字段说明:

Used20CallingMethod‌:一个字节,具体用途未详细说明。

‌SMBIOSMajorVersion‌:SMBIOS的主要版本号。

‌SMBIOSMinorVersion‌:SMBIOS的次要版本号。

‌DmiRevision‌:DMI(Desktop Management Interface)的修订版本号。

‌Length‌:数据长度,表示SMBIOSTableData数组的长度。

‌SMBIOSTableData[]‌:一个字节数组,存储实际的SMBIOS数据‌

它是一个SMBIOS结构数据,每一个SMBIOS结构代表一个固件的信息,需要注意的是,所有数据均为ASCII编码,每一个SMBIOS结构以2字节(WORD)的0结尾。

每个SMBIOS结构都包括一个格式化区域和一个可选的未格式化区域(字符串数组)。

格式化区域以一个4字节的SMBIOS结构头SMBIOSHeader开始,紧接在后面的数据则由固件类型决定。因此格式化区域的总长度由结构类型决定。

未格式化区域的数据内容也是由结构类型、主板或系统支持的具体版本决定。

因为每一个SMBIO结构都是以2字节的0结尾,所以我们可以通过遍历的形式获取所需类型的SMBIOS结构。

 


 

说明:前面4个字段在实际使用中没有用到。

 


 

说明:格式化区域中获取的字符串数据需要从未格式化区域去取,可以通过下面的函数

复制代码
 1 LPTSTR FindStrFromSMBIOSDataStruct(PSMBIOSHeader pStructHeader, BYTE bNum)
 2 {
 3     // 指向SMBIOS结构的未格式化区域(字符串数组)
 4     LPBYTE lpByte = (LPBYTE)pStructHeader + pStructHeader->Length;
 5 
 6     // 字符串编号从1开始
 7     for (BYTE i = 1; i < bNum; i++)
 8         lpByte += strlen((LPSTR)lpByte) + 1;
 9 
10     return (LPSTR)lpByte;
11 }
复制代码

 


 

 

SMBIOSHeader

SMBIOS固件信息头

 

定义如下:

1 typedef struct SMBIOSHeader {    
2      BYTE    Type;         // 结构类型    
3      BYTE    Length;       // 该类型结构的格式化区域长度(请注意,长度取决于主板或系统支持的具体版本)    
4      WORD    Handle;       // 结构句柄(0~0xFEFF范围内的数字)
}

 

字段说明:

Type

结构类型

类型0~127(7Fh)的固件数据的定义由DMTF(制定SMBIOS规范的组织)规定,

类型128~256可以由操作系统和OEM原始设备制造商自行定义,

例如

BIOS信息(Type 0)

系统信息(Type 1)

基板(或模块)信息(Type 2)

系统外壳或外围设备(Type 3)

处理器信息(Type 4)

缓存信息(Type 7)

 

完整的类型可以访问https://www.dmtf.org/standards/smbios

找到最新的SMBIOS文档里的第6.2章, Required structures and data进行了解

 

Length

该类型结构的格式化区域长度

 

Handle

指向本结构的句柄

 

在介绍RawSMBIOSData结构时,提到过格式化区域以一个4字节的SMBIOS结构头SMBIOSHeader开始,紧接在后面的数据则由固件类型决定。

根据上面的Type类型,我们需要定义对应的结构类型。

 

例如

我们要获取 Type1,也就是系统信息,找到SMBIOS文档里相关的定义,数据结构所示

 

 

 定义如下:

复制代码
 1 #pragma pack(1)
 2 // 系统信息(Type 1)SMBIOS结构的格式化区域的完整定义
 3 typedef struct _Type1SystemInformation
 4 {
 5    SMBIOSStructHeader m_sHeader; // SMBIOS结构头SMBIOSStructHeader
 6    BYTE    m_bManufacturer;      // Manufacturer字符串的编号
 7    BYTE    m_bProductName;       // Product Name字符串的编号
 8    BYTE    m_bVersion;           // Version字符串的编号
 9    BYTE    m_bSerialNumber;      // BIOS Serial Number字符串的编号
10    UUID    m_uuid;               // UUID
11    BYTE    m_bWakeupType;        // 标识导致系统启动的事件(原因)
12    BYTE    m_bSKUNumber;         // SKU Number字符串的编号
13    BYTE    m_bFamily;            // Family字符串的编号
14 }Type1SystemInformation, * PType1SystemInformation;
复制代码

 

说明:结构定义需要使用1字节对齐,否则可能会导致其中的字段引用错误。

 

获取SMBIOS示例

1、调用GetSystemFirmwareTable获取原始SMBIOS固件表

复制代码
 1     UINT nSize = GetSystemFirmwareTable('RSMB', 0, NULL, 0);
 2 
 3     if (nSize == 0)
 4         return 0;
 5 
 6     PRawSMBIOSData pRawBiosData = NULL;
 7 
 8     BYTE* buf = new BYTE[nSize];
 9 
10 
11     if (GetSystemFirmwareTable('RSMB', 0, buf, nSize) != nSize)
12         return 0;
复制代码

 

2、定义RawSMBIOSData

复制代码
1 typedef struct tagRawSMBIOSData
2 {
3     BYTE    Used20CallingMethod;
4     BYTE    SMBIOSMajorVersion;
5     BYTE    SMBIOSMinorVersion;
6     BYTE    DmiRevision;
7     DWORD   Length;
8     BYTE    SMBIOSTableData[];
9 }RawSMBIOSData,*PRawSMBIOSData;
复制代码

 

3、将GetSystemFirmwareTable获取的buf转换为RawSMBIOSData

1  PRawSMBIOSData pRawBiosData = (PRawSMBIOSData)buf;

 

4、定义PSMBIOSHeader

1 typedef struct SMBIOSHeader {
2     BYTE    Type;         // 结构类型    
3     BYTE    Length;       // 该类型结构的格式化区域长度(请注意,长度取决于主板或系统支持的具体版本)    
4     WORD    Handle;       // 结构句柄(0~0xFEFF范围内的数字)
5 }*PSMBIOSHeader;

 

5、遍历SMBIOS固件表数据

复制代码
 1 //当前指针位置
 2 auto lpData = pRawBiosData->SMBIOSTableData;
 3 
 4 while ((lpData - pRawBiosData->SMBIOSTableData) < pRawBiosData->Length)
 5 {
 6     // 根据SMBIOS结构的格式化区域中的结构头的Type字段确定结构类型
 7     // 确定结构类型以后再把pStructHeader转换为指向对应的格式化区域完整定义的指针
 8     pStructHeader = (PSMBIOSHeader)lpData;
 9    
10     // 遍历完格式化区域
11     lpData += pStructHeader->Length;
12 
13     // 跳过未格式化区域
14     while ((*(LPWORD)lpData) != 0)
15     {
16         lpData++;
17     }
18 
19     // 末尾 加2字节
20     lpData += 2;
21 }
复制代码

 

6、根据固件信息头里的类型字段,定义对应的固件表数据结构

这里以Type 1 BIOS 系统信息为例

复制代码
 1 #pragma pack(1)
 2 // 系统信息(Type 1)SMBIOS结构的格式化区域的完整定义
 3 typedef struct _Type1SystemInformation
 4 {
 5     SMBIOSHeader m_sHeader;       // SMBIOS结构头SMBIOSHeader
 6     BYTE    m_bManufacturer;      // Manufacturer字符串的编号
 7     BYTE    m_bProductName;       // Product Name字符串的编号
 8     BYTE    m_bVersion;           // Version字符串的编号
 9     BYTE    m_bSerialNumber;      // BIOS Serial Number字符串的编号
10     UUID    m_uuid;               // UUID
11     BYTE    m_bWakeupType;        // 标识导致系统启动的事件(原因)
12     BYTE    m_bSKUNumber;         // SKU Number字符串的编号
13     BYTE    m_bFamily;            // Family字符串的编号
14 }Type1SystemInformation, * PType1SystemInformation;
复制代码

 

7、根据固件信息头里的类型字段,获取具体的数据

复制代码
 1 //从未格式化区域去取字符串数据
 2 LPSTR FindStrFromSMBIOSDataStruct(PSMBIOSHeader pStructHeader, BYTE bNum)
 3 {
 4     // 指向SMBIOS结构的未格式化区域(字符串数组)
 5     LPBYTE lpByte = (LPBYTE)pStructHeader + pStructHeader->Length;
 6 
 7     // 字符串编号从1开始
 8     for (BYTE i = 1; i < bNum; i++)
 9         lpByte += strlen((LPSTR)lpByte) + 1;
10 
11     return (LPSTR)lpByte;
12 }
复制代码

 

复制代码
 1 pStructHeader = (PSMBIOSHeader)lpData;
 2 
 3 switch (pStructHeader->Type)
 4 {
 5 case 1: //Type 1
 6 {
 7     PType1SystemInformation pType1 = (PType1SystemInformation)pStructHeader;
 8     //输出
 9 }
10     break;
11 default:
12     break;
13 }
复制代码

 

运行效果:

 

示例代码

https://github.com/zhaotianff/WindowsProgramming/tree/master/BIOS/GetBIOSInfo

 

参考资料

https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemfirmwaretable

https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-enumsystemfirmwaretables

 

posted @   zhaotianff  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
历史上的今天:
2023-02-28 如何在IIS中为typecho博客启用HTTPS访问
2019-02-28 Powershell学习笔记:(二)、基础知识
2019-02-28 程序员长寿指南
点击右上角即可分享
微信分享提示