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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
2023-02-28 如何在IIS中为typecho博客启用HTTPS访问
2019-02-28 Powershell学习笔记:(二)、基础知识
2019-02-28 程序员长寿指南