找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 4428|回复: 1

【UEFI】【SMBIOS】在UEFI中解析SMBIOS获取内存信息

[复制链接]
发表于 2020-10-13 14:20:44 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
写OS必不可少的就是了解你这个系统的基本信息。其中SMBIOS就是个很有用的东西,本文以解析内存信息为例来了解SMBIOS。本文提到的SMBIOS,特指32位的SMBIOS标准。64位的SMBIOS标准是3.0版本开始才有的。
上回写EFI的时候我自己实现了printf类的函数,但这个轮子根本不完整,并且费力不讨好。其实EDK中早就有了相关的轮子,但是没编译出来,所以当时就临时另造了一个轮子。但今时不同往日,我已经成功以我自己的套路编译出了EDK里的Print函数库,之前造的轮子就可以弃用了。
我的编译方法是非官方的玩法,本文不会赘述EDK库的编译方法,编译脚本已经在GitHub上开源了:https://github.com/MickeyMeowMeowHouse/EDK-II-Library


要解析SMBIOS,首先得找到SMBIOS的地址。根据SMBIOS的标准,在非UEFI环境中,查找SMBIOS的方法是在物理地址[0x000F0000,0x00100000)的区间中,以16字节的对齐粒度暴力搜索字符串"_SM_"。
但本文既然是在说UEFI,那当然得按照UEFI的标准来。在入口函数的SystemTable参数中,结构体里有个成员叫ConfigurationTable,这是个结构体数组,其结构体定义如下:
  1. typedef struct{
  2. EFI_GUID VendorGuid;
  3. VOID *VendorTable;
  4. } EFI_CONFIGURATION_TABLE;
复制代码

在查找SMBIOS时,我们要找到数组的一个特定元素,这个特定元素的VendorGuid是一个特定的GUID。这个特定的GUID当然代表SMBIOS。根据UEFI文档和SMBIOS标准,这个GUID的定义是:
  1. #define SMBIOS_TABLE_GUID \
  2. {0xeb9d2d31,0x2d88,0x11d3,\
  3. {0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
复制代码

那么通过遍历ConfigurationTable数组,我们就能拿到SMBIOS表的地址。但由于UEFI文档里没说这个数组按GUID排了序,因此不能使用二分搜索,只能线性查找,代码如下:
  1. EFI_STATUS EfiLocateSmBiosTable()
  2. {
  3.         for(UINTN i=0;i<gST->NumberOfTableEntries;i++)
  4.         {
  5.                 if(EfiCompareGuid(&gST->ConfigurationTable[i].VendorGuid,&gEfiSmbiosTableGuid)==0)
  6.                 {
  7.                         SmBiosTable=(SMBIOS_TABLE_ENTRY_POINT*)gST->ConfigurationTable[i].VendorTable;
  8.                         return EFI_SUCCESS;
  9.                 }
  10.         }
  11.         return EFI_NOT_FOUND;
  12. }
复制代码

其中EfiCompareGuid是比较GUID的函数,EDK库中似乎没有造过这个轮子,所以只能自己造了,代码如下:
  1. INTN EfiCompareGuid(EFI_GUID *Guid1,EFI_GUID *Guid2)
  2. {
  3.         if(Guid1->Data1>Guid2->Data1)
  4.                 return 1;
  5.         else if(Guid1->Data1<Guid2->Data1)
  6.                 return -1;
  7.         if(Guid1->Data2>Guid2->Data2)
  8.                 return 1;
  9.         else if(Guid1->Data2<Guid2->Data2)
  10.                 return -1;
  11.         if(Guid1->Data3>Guid2->Data3)
  12.                 return 1;
  13.         else if(Guid1->Data3<Guid2->Data3)
  14.                 return -1;
  15.         for(UINT8 i=0;i<8;i++)
  16.         {
  17.                 if(Guid1->Data4[i]>Guid2->Data4[i])
  18.                         return 1;
  19.                 else if(Guid1->Data4[i]<Guid2->Data4[i])
  20.                         return -1;
  21.         }
  22.         return 0;
  23. }
复制代码



拿到SMBIOS表之后我们就要开始解析SMBIOS了。首先先看看SMBIOS表的结构体定义:
  1. typedef struct {
  2.   UINT8   AnchorString[4];
  3.   UINT8   EntryPointStructureChecksum;
  4.   UINT8   EntryPointLength;
  5.   UINT8   MajorVersion;
  6.   UINT8   MinorVersion;
  7.   UINT16  MaxStructureSize;
  8.   UINT8   EntryPointRevision;
  9.   UINT8   FormattedArea[5];
  10.   UINT8   IntermediateAnchorString[5];
  11.   UINT8   IntermediateChecksum;
  12.   UINT16  TableLength;
  13.   UINT32  TableAddress;
  14.   UINT16  NumberOfSmbiosStructures;
  15.   UINT8   SmbiosBcdRevision;
  16. } SMBIOS_TABLE_ENTRY_POINT;
复制代码

我们需要关注的几点:AnchorString, TableLength, TableAddress, NumberOfSmbiosStructures。
AnchorString: 这是个签名,构成一个ANSI字符串"_SM_"。
TableLength: SMBIOS所有表项的字节数。
TableAddress: SMBIOS表项的起始地址(物理地址)。
NumberOfSmbiosStructures: SMBIOS表项总数。
由于是32位SMBIOS,那TableAddress项当然是32位的咯。(笑)


SMBIOS的表项的组织方式是连续排列,即每个表项在地址上是连续的。每个表项的结构均一分为三:表头,定长值表项,字符串数组。
表头是一个简单的结构体,每个类型的表项表头的定义都一样,其定义如下:
  1. typedef struct {
  2.   SMBIOS_TYPE    Type;
  3.   UINT8          Length;
  4.   SMBIOS_HANDLE  Handle;
  5. } SMBIOS_STRUCTURE;
复制代码

其中Type是表项类型,Length是表头加定长值表项但不包括字符串数组的字节数,Handle是可用于区分表项的“句柄”。
不同类型的表项,定长值表项的定义也不同。本文讲的是查询内存信息,那么相关定义如下:
  1. #define SMBIOS_TYPE_MEMORY_DEVICE                        17

  2. typedef struct {
  3.   SMBIOS_STRUCTURE                          Hdr;
  4.   UINT16                                    MemoryArrayHandle;
  5.   UINT16                                    MemoryErrorInformationHandle;
  6.   UINT16                                    TotalWidth;
  7.   UINT16                                    DataWidth;
  8.   UINT16                                    Size;
  9.   UINT8                                     FormFactor;         ///< The enumeration value from MEMORY_FORM_FACTOR.
  10.   UINT8                                     DeviceSet;
  11.   SMBIOS_TABLE_STRING                       DeviceLocator;
  12.   SMBIOS_TABLE_STRING                       BankLocator;
  13.   UINT8                                     MemoryType;         ///< The enumeration value from MEMORY_DEVICE_TYPE.
  14.   MEMORY_DEVICE_TYPE_DETAIL                 TypeDetail;
  15.   UINT16                                    Speed;
  16.   SMBIOS_TABLE_STRING                       Manufacturer;
  17.   SMBIOS_TABLE_STRING                       SerialNumber;
  18.   SMBIOS_TABLE_STRING                       AssetTag;
  19.   SMBIOS_TABLE_STRING                       PartNumber;
  20.   //
  21.   // Add for smbios 2.6
  22.   //
  23.   UINT8                                     Attributes;
  24.   //
  25.   // Add for smbios 2.7
  26.   //
  27.   UINT32                                    ExtendedSize;
  28.   //
  29.   // Keep using name "ConfiguredMemoryClockSpeed" for compatibility
  30.   // although this field is renamed from "Configured Memory Clock Speed"
  31.   // to "Configured Memory Speed" in smbios 3.2.0.
  32.   //
  33.   UINT16                                    ConfiguredMemoryClockSpeed;
  34.   //
  35.   // Add for smbios 2.8.0
  36.   //
  37.   UINT16                                    MinimumVoltage;
  38.   UINT16                                    MaximumVoltage;
  39.   UINT16                                    ConfiguredVoltage;
  40.   //
  41.   // Add for smbios 3.2.0
  42.   //
  43.   UINT8                                     MemoryTechnology;   ///< The enumeration value from MEMORY_DEVICE_TECHNOLOGY
  44.   MEMORY_DEVICE_OPERATING_MODE_CAPABILITY   MemoryOperatingModeCapability;
  45.   SMBIOS_TABLE_STRING                       FirwareVersion;
  46.   UINT16                                    ModuleManufacturerID;
  47.   UINT16                                    ModuleProductID;
  48.   UINT16                                    MemorySubsystemControllerManufacturerID;
  49.   UINT16                                    MemorySubsystemControllerProductID;
  50.   UINT64                                    NonVolatileSize;
  51.   UINT64                                    VolatileSize;
  52.   UINT64                                    CacheSize;
  53.   UINT64                                    LogicalSize;
  54.   //
  55.   // Add for smbios 3.3.0
  56.   //
  57.   UINT32                                    ExtendedSpeed;
  58.   UINT32                                    ExtendedConfiguredMemorySpeed;
  59. } SMBIOS_TABLE_TYPE17;
复制代码

每个SMBIOS_TABLE_TYPE17表项均表示一个内存槽,随着SMBIOS标准的更新,这个结构体也会不断增大。由于市面上多数的SMBIOS至少支持到2.7版,也就是说,常见的SMBIOS结构体至少包含了ExtendedSize成员。
成员太多不一一介绍,只列举几个我们关心的:
Hdr成员即表项的表头。
Size成员表示这个内存槽上插的内存的大小。这个16位的值,最高位表示内存大小的单位。若复位则为MiB,若置位则为KiB。注意1KiB=1024Byte,1MiB=1024KiB;而1KB=1000Byte,1MB=1000KB。若Size成员等于0x7FFF,这个槽上插的内存大于等于32GiB,此时用32位的ExtendedSize成员表示具体的内存大小。如果你的机器不支持SMBIOS 2.7,那么你的机器肯定也不支持单条32GiB的内存。
FormFactor表示内存的构造尺寸,如DIMM,SODIMM等等。
DeviceLocator BankLocator可用于识别内存条所在槽的位置。其类型是SMBIOS_TABLE_STRING,类型相当于BYTE,数值的意义是作为索引在表项的第三部分字符串中引用字符串。
MemoryType表示内存类型,如DDR3 DDR4等等。
TypeDetail表示内存条具备的性质,如Synchronous, Registered等等。
Speed表示内存频率,单位为MT/s,也就是常说的MHz。
Manufacturer, SerialNumber, PartNumber, AssetTag的含义,顾名思义,不言而喻。
ExtendedSize表示内存的大小,最高位为保留位,目测可能还是用于特殊单位的。由于ExtendedSize是32位的,那么我推测当最高位置位的时候,单位是比MiB高三级的单位,也就是PiB。
我们提到过表项还有第三部分,即字符串数组。字符串数组的排列方式也是连续排列。每个字符串均为以零结尾的ANSI字符串,而下一个字符串就紧跟在零结尾之后。最后一个字符串以两个零字节结尾,标识了这个表项的结束。
那么综上所述,计算表项长度的函数代码如下:
  1. UINTN GetSmBiosEntryLength(IN SMBIOS_STRUCTURE *Entry)
  2. {
  3.         CHAR8* StringPool=(CHAR8*)((UINTN)Entry+Entry->Length);
  4.         UINTN i=0;
  5.         while(StringPool[i]!='\0' || StringPool[i+1]!='\0')i++;
  6.         return Entry->Length+i+2;
  7. }
复制代码

那么当我们解析SMBIOS获取内存信息的时候,大致的步骤是:
遍历SMBIOS表,用GetSmBiosEntryLength函数获取当前表项的长度,从而计算出下一条表项的地址。然后判断表项类型,如果不是内存槽的,就直接跳过,看下一个。如果内存槽上的内存大小为零,也就是没有内存,那也直接跳过。当内存槽上有内存的时候,就分析这个表项。
分析表项的过程中,先解析字符串数组,以零为分界,把每个字符串的地址丢进数组里(或者其他的你喜欢用的数据结构),但由于表项记录的特性,使用数组可能会更好点。值得注意的是,第零条字符串必须留空,因为SMBIOS_TABLE_STRING类型的成员为零的时候,表示这个成员没有信息。
那么综上所述,解析SMBIOS获取内存信息的代码如下:
  1. EFI_STATUS EfiQueryMemoryModuleInformation(OUT UINT64 *Size)
  2. {
  3.         EFI_STATUS st=EFI_NOT_FOUND;
  4.         UINTN Len=0,Index=0;
  5.         *Size=0;
  6.         for(UINTN CurEntry=(UINTN)SmBiosTable->TableAddress;CurEntry<(UINTN)(SmBiosTable->TableAddress+SmBiosTable->TableLength);CurEntry+=Len)
  7.         {
  8.                 SMBIOS_STRUCTURE *Entry=(SMBIOS_STRUCTURE*)CurEntry;
  9.                 Len=GetSmBiosEntryLength(Entry);
  10.                 if(Entry->Type==SMBIOS_TYPE_MEMORY_DEVICE)
  11.                 {
  12.                         SMBIOS_TABLE_TYPE17 *MemModInfo=(SMBIOS_TABLE_TYPE17*)Entry;
  13.                         if(MemModInfo->Size)
  14.                         {
  15.                                 CHAR8* Strings=(CHAR8*)((UINTN)Entry+Entry->Length);
  16.                                 // 15 strings should be enough for Memory Module Information.
  17.                                 CHAR8* StringPool[0x10];
  18.                                 UINT8 j=1;                // Leave the zeroth as empty.
  19.                                 UINTN StringLength=0;
  20.                                 // Initialize the String Pool.
  21.                                 __stosp((UINTN*)StringPool,(UINTN)"No Info",0x10);
  22.                                 for(UINTN i=0;i<Len-Entry->Length;i+=StringLength)
  23.                                 {
  24.                                         StringLength=AsciiStrnLenS(&Strings[i],Len-Entry->Length-i)+1;
  25.                                         if(Strings[i]=='\0')break;
  26.                                         StringPool[j++]=&Strings[i];
  27.                                 }
  28.                                 // Memory Module Information in Strings
  29.                                 CHAR8* DeviceLocator=StringPool[MemModInfo->DeviceLocator];
  30.                                 CHAR8* BankLocator=StringPool[MemModInfo->BankLocator];
  31.                                 CHAR8* Manufacturer=StringPool[MemModInfo->Manufacturer];
  32.                                 CHAR8* SerialNumber=StringPool[MemModInfo->SerialNumber];
  33.                                 CHAR8* AssetTag=StringPool[MemModInfo->AssetTag];
  34.                                 CHAR8* PartNumber=StringPool[MemModInfo->PartNumber];
  35.                                 CHAR8* MemoryType=MemTypeList[MemModInfo->MemoryType];
  36.                                 CHAR8* FormFactor=MemFormList[MemModInfo->FormFactor];
  37.                                 Print(L"---------------- Memory Module Information for %a ----------------\n",BankLocator);
  38.                                 Print(L"Device Slot: %a | Manufacturer: %a | Serial Number: %a | Asset Tag: %a\n",DeviceLocator,Manufacturer,SerialNumber,AssetTag);
  39.                                 Print(L"Part Number: %a | Size=",PartNumber);
  40.                                 if(_bittest(&MemModInfo->Size,15))                // Unit is KiB
  41.                                 {
  42.                                         *Size+=(MemModInfo->Size&0x7FFF);
  43.                                         Print(L"%d KiB",MemModInfo->Size&0x7FFF);
  44.                                 }
  45.                                 else                        // Unit is MiB
  46.                                 {
  47.                                         // This RAM module is greater than or equal to 32 GiB.
  48.                                         if(MemModInfo->Size==0x7FFF)
  49.                                         {
  50.                                                 UINT64 MemSize=MemModInfo->ExtendedSize&0x7FFFFFFF;
  51.                                                 *Size+=(MemSize<<10);
  52.                                                 Print(L"%d MiB",MemModInfo->ExtendedSize&0x7FFFFFFF);
  53.                                         }
  54.                                         else
  55.                                         {
  56.                                                 *Size+=(MemModInfo->Size<<10);
  57.                                                 Print(L"%d MiB",MemModInfo->Size);
  58.                                         }
  59.                                 }
  60.                                 Print(L" | Type: %a | Form Factor: %a\n",MemoryType,FormFactor);
  61.                                 StdOut->OutputString(StdOut,L"Memory Type Detail:");
  62.                                 for(UINT8 i=0;i<16;i++)
  63.                                         if(_bittest(&MemModInfo->TypeDetail,i))
  64.                                                 StdOut->OutputString(StdOut,MemTypeDetailList[i]);
  65.                                 Print(L"\nMemory Data Width: %d | Memory Total Width: %d | Transfer Rate: %d MT/s\n",MemModInfo->DataWidth,MemModInfo->TotalWidth,MemModInfo->Speed);
  66.                                 st=EFI_SUCCESS;
  67.                         }
  68.                 }
  69.                 if(++Index==SmBiosTable->NumberOfSmbiosStructures)break;
  70.         }
  71.         if(st==EFI_SUCCESS)StdOut->OutputString(StdOut,L"-------------------- Memory Module Information Listing Ended --------------------\r\n");
  72.         return st;
  73. }
复制代码



我们丢进VMware虚拟机里跑跑看,如图所示:
General UEFI Development-2020-10-13-03-55-40.png
然后再丢进钓鱼派里跑跑看,由于钓鱼派有一个UART接口,那么可以直接用PuTTY来玩控制台,而不需要接采集卡,如图所示:
MinnowBoard PuTTY 20201013 040615.PNG
丢进巫毒派里跑跑看,可以接到采集卡上,然后用OBS截图,如图所示:
Udoo Bolt 20201013 135320.PNG


UEFI的文档可以去UEFI官网下载:https://uefi.org/specifications
SMBIOS的文档可以去DMTF官网下载:https://www.dmtf.org/standards/smbios
本文完整代码已在GitHub上开源:https://github.com/MickeyMeowMeowHouse/UefiAccessSmBios
EFI二进制文件已上传至GitHub:https://github.com/MickeyMeowMeowHouse/UefiAccessSmBios/releases
回复

使用道具 举报

发表于 2020-10-14 06:53:34 | 显示全部楼层
经典提问:这玩意可以用来过保护吗?
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-1 07:57 , Processed in 0.035354 second(s), 30 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表