對於BIOS來說,用戶能夠看到的是非常重要的事情。開機Logo就是這樣。自從 Win8.1開始,Windows在進入桌面的時候可以顯示用戶自定義的Logo,而不是Microsoft自定義的圖片,這是很有意思的事情。
最近看了一篇介紹的文章,恍然大悟,原來是BIOS解壓自己的Logo在內存中,然後通過ACPI Table將這個Logo傳遞給Windows,於是開機Logo比以前顯示的時間更長更持久。
具體的Table就是 Boot Graphics Resource Table。在 ACPI 6.1的5.2.22有專門的介紹。
根據上面的原理,我們可以編寫一個UEFI Application,將內存存放的 Logo Dump出來。具體操作:
1. 找到RSDP,找到 XSDT
2. 在XSDT中檢查每一個Entry,根據 Signature 找到BGRT
3. 解析 BGRT ,得到Logo 圖像的地址,這個 Logo一定是 BMP格式的
4. 根據BMP格式能夠解析出文件大小,直接Memory Dump即可得到結果
完整的代碼:
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include "acpi.h"
#include "acpi61.h"
extern EFI_BOOT_SERVICES *gBS;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_SYSTEM_TABLE *gST;
#pragma pack(push, 1)
/** BGRT structure */
typedef struct {
EFI_ACPI_DESCRIPTION_HEADER header;
UINT16 version;
UINT8 status;
UINT8 image_type;
UINT64 image_address;
UINT32 image_offset_x;
UINT32 image_offset_y;
} ACPI_BGRT;
/** Bitmap file header */
typedef struct {
UINT8 magic_BM[2];
UINT32 file_size;
UINT8 unused_0x06[4];
UINT32 pixel_data_offset;
UINT32 dib_header_size;
UINT32 width;
UINT32 height;
UINT16 planes;
UINT16 bpp;
} BMP;
#pragma pack(pop)
EFI_GUID gEfiAcpi20TableGuid = { 0x8868E871, 0xE4F1, 0x11D3,
{ 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
EFI_GUID gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2,
{ 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
EFI_STATUS
SaveToFile(
IN UINT8 *FileData,
IN UINTN FileDataLength)
{
EFI_STATUS Status;
EFI_FILE_PROTOCOL *FileHandle;
UINTN BufferSize;
EFI_FILE_PROTOCOL *Root;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
Status = gBS->LocateProtocol(
&gEfiSimpleFileSystemProtocolGuid,
NULL,
(VOID **)&SimpleFileSystem);
if (EFI_ERROR(Status)) {
Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
return Status;
}
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
if (EFI_ERROR(Status)) {
Print(L"OpenVolume error \r\n");
return Status;
}
Status = Root->Open(
Root,
&FileHandle,
L"BIOSLogo.bmp",
EFI_FILE_MODE_READ |
EFI_FILE_MODE_WRITE |
EFI_FILE_MODE_CREATE,
0);
if (EFI_ERROR(Status)){
Print(L"Error Open NULL [%r]\n",Status);
return Status;
}
BufferSize = FileDataLength;
Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
FileHandle->Close(FileHandle);
return Status;
}
/***
Demonstrates basic workings of the main() function by displaying a
welcoming message.
Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
The easiest way to access the command line parameters is to cast Argv as:
wchar_t **wArgv = (wchar_t **)Argv;
@param[in] Argc Number of argument tokens pointed to by Argv.
@param[in] Argv Array of Argc pointers to command line tokens.
@retval 0 The application exited normally.
@retval Other An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status;
EFI_ACPI_DESCRIPTION_HEADER *XSDT;
EFI_ACPI_6_1_ROOT_SYSTEM_DESCRIPTION_POINTER *RSDP;
UINT8 *p;
UINTN Index;
UINT64 *Entry;
ACPI_BGRT *pBGRT;
BMP *pBMP;
//1. Find RSDP
Status=EfiGetSystemConfigurationTable(&gEfiAcpi20TableGuid,(VOID**)&RSDP);
if(EFI_ERROR(Status)) {
Print(L"Can't find Acpi Table\n");
return 0;
}
//2. Find XSDT
Print(L"RSDP address [%X]\n",RSDP);
Print(L"XSDT address [%X]\n",RSDP->XsdtAddress);
XSDT=(EFI_ACPI_DESCRIPTION_HEADER*)RSDP->XsdtAddress;
Print(L"XSDT information\n");
p=(UINT8*)XSDT;
//Show some DSDT information
Print(L" Signature [%c%c%c%c]\n",*p,*(p+1),*(p+2),*(p+3));
//3.Find entries
Entry=(UINT64*)&XSDT[1];
Print(L" Entry 0 @[0x%x]\n",Entry);
for (Index=0;Index<(XSDT->Length-sizeof(EFI_ACPI_DESCRIPTION_HEADER))/8;Index++) {
//Print(L" Entry [0x%x]",Index);
p=(UINT8*)(*Entry);
//You can show every signature here
//Print(L" [%x][%c%c%c%c]\n",*Entry,*p,*(p+1),*(p+2),*(p+3));
if ((*p=='B')&&(*(p+1)=='G')&&(*(p+2)=='R')&&(*(p+3)=='T')) {
pBGRT=(ACPI_BGRT*)p;
Print(L" Found BGRT @[0x%X]\n",*Entry);
Print(L" Image address @[0x%X]\n",pBGRT->image_address);
//Get BMP address
pBMP=(BMP*)(pBGRT->image_address);
Print(L" [0x%X]\n",pBMP);
Print(L" Image size [0x%X]\n",pBMP->file_size);
Print(L" Data offset [0x%X]\n",pBMP->pixel_data_offset);
Print(L" Header size [0x%X]\n",pBMP->dib_header_size);
Print(L" Width [0x%X]\n",pBMP->width);
Print(L" Height[0x%X]\n",pBMP->height);
Print(L" Planes[0x%X]\n",pBMP->planes);
Print(L" BPP [0x%X]\n",pBMP->bpp);
SaveToFile((UINT8*)pBMP,pBMP->file_size);
Print(L"BIOS logo has been saved to 'BIOSLogo.bmp'\n");
}
Entry++;
}
return 0;
}
運行結果,測試平臺爲 Intel Kabylake-R HDK,使用的是 Byo BIOS
(不知道爲啥,他家的 Shell分辨率很高, 字體極小,看起來簡直要瞎) 取得的 BIOSLogo.bmp 結果如下
完整的代碼下載:FindBGRT
特別鳴謝sssky307在之前的文章中給出了EfiGetSystemConfigurationTable函數使得代碼能夠能夠大幅度化簡。