如何获取BIOS信息

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

Windows编程系列:WMI - zhaotianff - 博客园

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)

完整的类型可以访问SMBIOS | DMTF

找到最新的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 }

运行效果:

示例代码

WindowsProgramming/BIOS/GetBIOSInfo at master · zhaotianff/WindowsProgramming · GitHub

参考资料

GetSystemFirmwareTable function (sysinfoapi.h) - Win32 apps | Microsoft Learn

EnumSystemFirmwareTables function (sysinfoapi.h) - Win32 apps | Microsoft Learn

一般获取主板UID都是cmd命令 执行"wmic path Win32_ComputerSystemProduct get uuid" 这里说的是使用API获取,找了半天没找到,最后发现只能通过 GetSystemFirmwareTable 获取 SMBIOS信息 ,在这个结构体里存储的相当复杂,看的有点头晕 首先调用 GetSystemFirmwareTable  先让他返回结构体大小,然后我们申请一块内存,再调用一次,就会把  SMBIOS信息 存储到 我们申请的内存里了. SMBIOS信息 前8个字节就不看了,可以百度慢慢看,从第9个字节开始是TYPE结构,第一个字节是type类型,第二个字节就是这个type类型的大小,从type起始地址计算,后面的就是格式化区域,不同type类型格式化区域代表的信息也不同,格式化区域完后,就会跟一段u字符串,我们知道ascii字符串是0结尾,u字符串是00 结尾,字符串结尾后就是下一个type结构的开始了. 这里就只看下type1类型结构  从资料里看,在第8个字节后的16位就是uid了.   其他类型大家就自己查一下了. 里面的模块命令 就用了下 精益的十六进制转换命令,这个大家应该都有. 代码有点丑,见笑了. 系统信息 (Type 1) : SMBIOS 实现只关联一个单一的系统实例,并且包含且只包含一个系统信息结构。 位置 名称 长度 描述 00h TYPE 号 1BYTE 结构的TYPE 号,此处是1 01h 长度 1BYTE 格式区域总长度,2.0 版为08h ,2.1-2.3.4 版为19h,从2.4 版开始为1Bh 02h 句柄 2BYTE 指向本结构的句柄 04h 电脑制造商 1BYTE 一般为01h ,表示在字符串区域中的编号 05h 产品名称 1BYTE [url=]在字符串区域中的编号[/url] 06h 版本号 1BYTE 在字符串区域中的编号 07h 序列号 1BYTE 在字符串区域中的编号 09h UUID 16BYTE 通用唯一标识符 18h 唤醒类型 BYTE 用来标识导致系统开电启动的事件 19h SKU 号 BYTE 在字符串区域中的编号,SKU 号通常为产品ID 或采购订单号 1Ah 产品家族 1BYTE 在字符串区域中的编号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

△曉風殘月〆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值