27. dmem/EFI_SMBIOS_PROTOCOL/smbiosview를 통해서 SMBIOS 정보 가져오기
이전 장에서 확인한 시스템 테이블 중 SMBIOS에 대해서 알아보도록 하겠다.\
"System Management BIOS(SMBIOS) 참조 스펙"의 최신 버전은 아래의 사이트에서 확인할 수 있으며 현재는 3.6.0이 최신 버전인 것을 알 수 있다. https://www.dmtf.org/dsp/DSP0134
이전 시간에 우리는 SMBIOS 테이블이 gEfiSmbiosTableGuid 에 선언된 것을 확인했다.
해당 테이블의 주소를 인쇄하는 간단한 앱을 만들어 보겠다.
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
for (UINTN i=0; i<SystemTable->NumberOfTableEntries; i++) {
if (CompareGuid(&(SystemTable->ConfigurationTable[i].VendorGuid), &gEfiSmbiosTableGuid)) {
Print(L"SMBIOS table is placed at %p\n", SystemTable->ConfigurationTable[i].VendorTable);
}
}
return EFI_SUCCESS;
}해당 코드에서 사용된 CompareGuid 함수는 아래의 코드에서 정의가 되어있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/BaseMemoryLib.h
잊지 말고 inf파일에GUID에 값을 추가 해주자.
빌드 후 실행하면 아래와 같이 주소가 나오는 것을 볼 수 있다.
dmem을 통한 SMBIOS 테이블 가져오기
dmem을 통한 SMBIOS 테이블 가져오기UEFI Shell에는 메모리를 덤프하는 dmem이라는 명령어가 존재한다.
SMBIOS 테이블 포인터로 출력된 0x793F000주소에서 0x30 바이트를 출력해보겠다.
SMBIOS 스펙에 나와있는 SMBIOS Entry point 구조에 따르면 _SM_ 및 _DMI_가 해당 구조에서 미리 정의된 값임을 알 수 있다.

SMBIOS에 대한 구조는 EDKII에서 또한찾을 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/SmBios.h
해당 구조를 따라서 덤프된 메모리를 계산하면 아래와 같이 볼 수 있다. 시스템에는 0xBF53E000에서 (0xBF53E000+0x1B2)까지 배치된 0xA개의 SMBIOS 구조가 존재한다. (환경별로 상이할 가능성 존재)

SMBIOS구조의 주소를 구하는 것이 가능하기 때문에 이를 통해서 덤프도 가능해진다.

EFI_SMBIOS_PROTOCOL을 사용하여 SMBIOS 데이터 파싱하기
EFI_SMBIOS_PROTOCOL을 사용하여 SMBIOS 데이터 파싱하기위에서 한 방식대로 직접 포인터를 이용하여 SMBIOS 테이블을 계산을 통하여 구할 수 있지만, 매우 비합리적인 일이다. 다행히 UEFI PI 스펙은 SMBIOS 데이터를 가져오는데 사용할 수 있는 EFI_SMBIOS_PROTOCOL을 정의하고 있다.
지금 우리는 SMBIOS 테이블의 파싱을 하기 위해서 GetNext 함수를 사용하겠다.
우선 해당 프로토콜을 불러와야 한다.
해당 기능을 사용하기 위해서는 헤더 파일을 명시해야 한다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/Smbios.h
그리고 프로토콜의 GUID 값을 inf 파일에 작성한다.
이제 SMBIOS 테이블을 가져오도록 하겠다.
프로토콜 호출에서 SMBIOS_HANDLE_PI_RESERVED(0 xFFE)를 EFI_SMBIOS_HANDLE로 사용할 것이다. SMBIOS_HANDLE_PI_RESERVED에 대한 설명은 아래의 링크에서 볼 수 있다.
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/SmBios.h
또한 코드 작성전 GetNext 호출에서 파라미터로 사용되는 EFI_SMBIOS_TABLE_HEADER 구조의 정의를 살펴보겠다.
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/Smbios.h
https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/SmBios.h
이를 통해서 몇가지 코드 작성 준비가 되었다. 시스템에 있는 모든 유형의 SMBIOS 테이블을 인쇄하는 코드를 작성하겠다.
빌드 후 실행시 아래와 같은 결과를 볼 수 있다.
이제는 이 테이블의 정보를 파싱할 수 있는 코드를 Type 0 테이블부터 시작하겠다. 아래 링크에 EDKII에서 해당 구조의 정의에대한 설명이 존재한다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/SmBios.h
해당 구조에서 SMBIOS_STRUCTURE Hdr은 프로토콜 함수 호출에서 수신한 EFI_SMBIOS_TABLE_HEADER와 동일하게 필수적인 필드이다.
또한 SMBIOS_TABLE_STRING은 ASCII 문자열 배열의 문자형 숫자를 정의하는 UINT8 값이다.
따라서 EFI_SMBIOS_TABLE_HEADER* 및 문자형 숫자에서 실제 ASCII 문자열을 반환하는 간단한 함수를 작성하겠다.
해당 함수에서는 아래의 링크에 정의된 AsciiStrSize 함수를 사용했다.
https://github.com/tianocore/edk2/blob/master/MdePkg/Library/BaseLib/String.c
위의 모든 것을 바탕으로 while문을 이용해서 실제적으로 파싱을 하는 코드를 작성할 수 있다.
여기서 ASCII 문자열을 인쇄하기 위해서 Print 함수의 %a 옵션을 사용했다. 이후 빌드를 하면 아래와 같은 결과가 나온다.
해당 결과는 dmem을 통해서 dump한 결과가 실제로 메모리에 존재하는 문자열임을 알 수 있다. Type 0 이외에도 다른 SMBIOS 테이블을 parse하는 코드를 추가해보겠다.
이번에는 Type 1에 대해서 파싱하는 코드를 추가했고, 이에 대한 구조는 아래와 같다.
빌드 후 실행시 아래와 같은 결과가 나온다.
이러한 SMBIOS 테이블에 있는 정보와 동일한 정보가 기본 BIOS 메뉴에 존재하는 것을 기억하고 있어야 한다. 해당 SMBIOS 구조에 대한 정보의 위치와 EFI_SMBIOS_PROTOCOL의 구현은 아래의 링크에 위치한다.
https://github.com/tianocore/edk2/blob/master/OvmfPkg/SmbiosPlatformDxe/SmbiosPlatformDxe.c
https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Universal/SmbiosDxe/SmbiosDxe.c

smbiosview 명령어
smbiosview 명령어EFI_SMBIOS_PROTOCOL을 사용하여 SMBIOS 테이블 정보를 파싱할 수 있지만 실제로 SMBIOS 정보만 보려면 더 좋은 옵션이 있다. UEFI Shell에는 정확히 필요한 작업을 수행하는 smbiosview라는 명령어가 있다. 이는 아래의 소스를 통해서 확인이 가능하다. https://github.com/tianocore/edk2/tree/master/ShellPkg/Library/UefiShellDebug1CommandsLib/SmbiosView
해당 명령어에 대한 설명을 확인하자.
위에서 파싱한 구조 중 하나를 명령어를 통해서 덤프 해보겠다.
보다시피 EFI_SMBIOS_PROTOCOL을 사용한 것과 동일한 정보가 나왔다. 이외에도
SMBIOS 구조 내부에 무엇이 있는지 확인하는 명령어도 존재한다. 해당 결과 값은 너무 크기 때문에 직접 확인해보자.
Last updated