UEFI开发学习21 – 运行EFI APP
好久没更新了,趁着端午假期休息,安排几篇文章 ~
我们知道,UEFI的启动流程中,BDS阶段最终会把操作系统启动起来,UEFI 所做的只是运行一个操作系统的bootloader,而bootloader实际上就是一个EFI APP,那它是如何启动的呢?这就是本篇要探究的话题:如何在程序中运行一个EFI APP
Image Services
不管是EFI Driver,还是EFI APP,它们都统称为EFI Image。UEFI 中定义了 EFI Image 的操作方法及其标准,即Image Services,如下:

typedef EFI_STATUS (EFIAPI *EFI_IMAGE_ENTRY_POINT) ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable );
当一个EFI Image被执行时,便是从入口函数开始执行的,这也是我们写inf模块的时候,入口函数必须这么写的原因。
EFI 启动示例
启动一个EFI Image总的来说分两步,首先是使用LoadImage()加载到内存,然后再使用StartImage()运行。EDK2中有相关的代码,详见IntelFrameworkModulePkg\Library\GenericBdsLib\BdsBoot.c中的BdsLibBootViaBootOption()函数,我参照此写了个示例,如下:
StartEfi.c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/DevicePath.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <IndustryStandard/PeImage.h>
#include <Protocol/BlockIo.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/LoadFile.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/DevicePathToText.h>
#include <Guid/FileInfo.h>
/**
Get the headers (dos, image, optional header) from an image
@param Device SimpleFileSystem device handle
@param FileName File name for the image
@param DosHeader Pointer to dos header
@param Hdr The buffer in which to return the PE32, PE32+, or TE header.
@retval EFI_SUCCESS Successfully get the machine type.
@retval EFI_NOT_FOUND The file is not found.
@retval EFI_LOAD_ERROR File is not a valid image file.
**/
EFI_STATUS
EFIAPI
BdsLibGetImageHeader (
IN EFI_HANDLE Device,
IN CHAR16 *FileName,
OUT EFI_IMAGE_DOS_HEADER *DosHeader,
OUT EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Volume;
EFI_FILE_HANDLE Root;
EFI_FILE_HANDLE ThisFile;
UINTN BufferSize;
UINT64 FileSize;
EFI_FILE_INFO *Info;
Root = NULL;
ThisFile = NULL;
//
// Handle the file system interface to the device
//
Status = gBS->HandleProtocol (
Device,
&gEfiSimpleFileSystemProtocolGuid,
(VOID *) &Volume
);
if (EFI_ERROR (Status)) {
goto Done;
}
Status = Volume->OpenVolume (
Volume,
&Root
);
if (EFI_ERROR (Status)) {
Root = NULL;
goto Done;
}
ASSERT (Root != NULL);
Status = Root->Open (Root, &ThisFile, FileName, EFI_FILE_MODE_READ, 0);
if (EFI_ERROR (Status)) {
goto Done;
}
ASSERT (ThisFile != NULL);
//
// Get file size
//
BufferSize = SIZE_OF_EFI_FILE_INFO + 200;
do {
Info = NULL;
Status = gBS->AllocatePool (EfiBootServicesData, BufferSize, (VOID **) &Info);
if (EFI_ERROR (Status)) {
goto Done;
}
Status = ThisFile->GetInfo (
ThisFile,
&gEfiFileInfoGuid,
&BufferSize,
Info
);
if (!EFI_ERROR (Status)) {
break;
}
if (Status != EFI_BUFFER_TOO_SMALL) {
FreePool (Info);
goto Done;
}
FreePool (Info);
} while (TRUE);
FileSize = Info->FileSize;
FreePool (Info);
//
// Read dos header
//
BufferSize = sizeof (EFI_IMAGE_DOS_HEADER);
Status = ThisFile->Read (ThisFile, &BufferSize, DosHeader);
if (EFI_ERROR (Status) ||
BufferSize < sizeof (EFI_IMAGE_DOS_HEADER) ||
FileSize <= DosHeader->e_lfanew ||
DosHeader->e_magic != EFI_IMAGE_DOS_SIGNATURE) {
Status = EFI_LOAD_ERROR;
goto Done;
}
//
// Move to PE signature
//
Status = ThisFile->SetPosition (ThisFile, DosHeader->e_lfanew);
if (EFI_ERROR (Status)) {
Status = EFI_LOAD_ERROR;
goto Done;
}
//
// Read and check PE signature
//
BufferSize = sizeof (EFI_IMAGE_OPTIONAL_HEADER_UNION);
Status = ThisFile->Read (ThisFile, &BufferSize, Hdr.Pe32);
if (EFI_ERROR (Status) ||
BufferSize < sizeof (EFI_IMAGE_OPTIONAL_HEADER_UNION) ||
Hdr.Pe32->Signature != EFI_IMAGE_NT_SIGNATURE) {
Status = EFI_LOAD_ERROR;
goto Done;
}
//
// Check PE32 or PE32+ magic
//
if (Hdr.Pe32->OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC &&
Hdr.Pe32->OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
Status = EFI_LOAD_ERROR;
goto Done;
}
Done:
if (ThisFile != NULL) {
ThisFile->Close (ThisFile);
}
if (Root != NULL) {
Root->Close (Root);
}
return Status;
}
/**
Return the bootable media handle.
First, check the device is connected
Second, check whether the device path point to a device which support SimpleFileSystemProtocol,
Third, detect the the default boot file in the Media, and return the removable Media handle.
@param DevicePath Device Path to a bootable device
@return The bootable media handle. If the media on the DevicePath is not bootable, NULL will return.
**/
EFI_HANDLE
EFIAPI
BdsLibGetBootableHandle (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
IN CHAR16 *ImageName
)
{
EFI_STATUS Status;
EFI_TPL OldTpl;
EFI_DEVICE_PATH_PROTOCOL *UpdatedDevicePath;
EFI_DEVICE_PATH_PROTOCOL *DupDevicePath;
EFI_HANDLE Handle;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
VOID *Buffer;
EFI_DEVICE_PATH_PROTOCOL *TempDevicePath;
UINTN Size;
UINTN TempSize;
EFI_HANDLE ReturnHandle;
EFI_HANDLE *SimpleFileSystemHandles;
UINTN NumberSimpleFileSystemHandles;
UINTN Index;
EFI_IMAGE_DOS_HEADER DosHeader;
EFI_IMAGE_OPTIONAL_HEADER_UNION HdrData;
EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr;
UpdatedDevicePath = DevicePath;
//
// Enter to critical section to protect the acquired BlockIo instance
// from getting released due to the USB mass storage hotplug event
//
OldTpl = gBS->RaiseTPL (TPL_CALLBACK);
//
// Check whether the device is connected
//
Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &UpdatedDevicePath, &Handle);
if (EFI_ERROR (Status)) {
//
// Skip the case that the boot option point to a simple file protocol which does not consume block Io protocol,
//
Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &UpdatedDevicePath, &Handle);
if (EFI_ERROR (Status)) {
//
// Fail to find the proper BlockIo and simple file protocol, maybe because device not present, we need to connect it firstly
//
UpdatedDevicePath = DevicePath;
Status = gBS->LocateDevicePath (&gEfiDevicePathProtocolGuid, &UpdatedDevicePath, &Handle);
gBS->ConnectController (Handle, NULL, NULL, TRUE);
}
} else {
//
// For removable device boot option, its contained device path only point to the removable device handle,
// should make sure all its children handles (its child partion or media handles) are created and connected.
//
gBS->ConnectController (Handle, NULL, NULL, TRUE);
//
// Get BlockIo protocol and check removable attribute
//
Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **)&BlockIo);
ASSERT_EFI_ERROR (Status);
//
// Issue a dummy read to the device to check for media change.
// When the removable media is changed, any Block IO read/write will
// cause the BlockIo protocol be reinstalled and EFI_MEDIA_CHANGED is
// returned. After the Block IO protocol is reinstalled, subsequent
// Block IO read/write will success.
//
Buffer = AllocatePool (BlockIo->Media->BlockSize);
if (Buffer != NULL) {
BlockIo->ReadBlocks (
BlockIo,
BlockIo->Media->MediaId,
0,
BlockIo->Media->BlockSize,
Buffer
);
FreePool(Buffer);
}
}
//
// Detect the the default boot file from removable Media
//
//
// If fail to get bootable handle specified by a USB boot option, the BDS should try to find other bootable device in the same USB bus
// Try to locate the USB node device path first, if fail then use its previous PCI node to search
//
DupDevicePath = DuplicateDevicePath (DevicePath);
ASSERT (DupDevicePath != NULL);
UpdatedDevicePath = DupDevicePath;
Status = gBS->LocateDevicePath (&gEfiDevicePathProtocolGuid, &UpdatedDevicePath, &Handle);
//
// if the resulting device path point to a usb node, and the usb node is a dummy node, should only let device path only point to the previous Pci node
// Acpi()/Pci()/Usb() --> Acpi()/Pci()
//
if ((DevicePathType (UpdatedDevicePath) == MESSAGING_DEVICE_PATH) &&
(DevicePathSubType (UpdatedDevicePath) == MSG_USB_DP)) {
//
// Remove the usb node, let the device path only point to PCI node
//
SetDevicePathEndNode (UpdatedDevicePath);
UpdatedDevicePath = DupDevicePath;
} else {
UpdatedDevicePath = DevicePath;
}
//
// Get the device path size of boot option
//
Size = GetDevicePathSize(UpdatedDevicePath) - sizeof (EFI_DEVICE_PATH_PROTOCOL); // minus the end node
ReturnHandle = NULL;
gBS->LocateHandleBuffer (
ByProtocol,
&gEfiSimpleFileSystemProtocolGuid,
NULL,
&NumberSimpleFileSystemHandles,
&SimpleFileSystemHandles
);
for (Index = 0; Index < NumberSimpleFileSystemHandles; Index++) {
//
// Get the device path size of SimpleFileSystem handle
//
TempDevicePath = DevicePathFromHandle (SimpleFileSystemHandles[Index]);
TempSize = GetDevicePathSize (TempDevicePath)- sizeof (EFI_DEVICE_PATH_PROTOCOL); // minus the end node
//
// Check whether the device path of boot option is part of the SimpleFileSystem handle's device path
//
if (Size <= TempSize && CompareMem (TempDevicePath, UpdatedDevicePath, Size)==0) {
//
// Load the default boot file \EFI\BOOT\boot{machinename}.EFI from removable Media
// machinename is ia32, ia64, x64, ...
//
Hdr.Union = &HdrData;
Status = BdsLibGetImageHeader (
SimpleFileSystemHandles[Index],
ImageName,
&DosHeader,
Hdr
);
if (!EFI_ERROR (Status) &&
EFI_IMAGE_MACHINE_TYPE_SUPPORTED (Hdr.Pe32->FileHeader.Machine) &&
Hdr.Pe32->OptionalHeader.Subsystem == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) {
ReturnHandle = SimpleFileSystemHandles[Index];
break;
}
}
}
FreePool(DupDevicePath);
if (SimpleFileSystemHandles != NULL) {
FreePool(SimpleFileSystemHandles);
}
gBS->RestoreTPL (OldTpl);
return ReturnHandle;
}
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE MyImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN Index;
UINTN NoHandles;
EFI_HANDLE * Buffer;
EFI_DEVICE_PATH_PROTOCOL * DevicePath;
EFI_HANDLE ImageHandle;
CHAR16 * EfiImageName = L"\\HelloWorld.efi";
Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NoHandles, &Buffer);
if (EFI_ERROR(Status)) {
Print(L"Unsupported\n\r");
return 0;
}
for (Index = 0; Index < NoHandles; Index ++){
DevicePath = DevicePathFromHandle (Buffer[Index]);
ImageHandle = BdsLibGetBootableHandle(DevicePath, EfiImageName);
DevicePath = FileDevicePath (ImageHandle, EfiImageName);
if (ImageHandle == NULL) {
Print(L"Open Image fail\n\r");
continue;
}
Status = gBS->LoadImage (
FALSE,
gImageHandle,
DevicePath,
NULL,
0,
&ImageHandle
);
if (EFI_ERROR (Status)) {
Print(L"Load Image error %r %x\n\r", Status, gImageHandle);
continue;
}
Status = gBS->StartImage (ImageHandle, NULL, NULL);
if (!EFI_ERROR (Status)) {
Print(L"Start Image successfully\n\r");
break;
}
else {
Print(L"Start Image Failed!\n\r");
}
}
return EFI_SUCCESS;
}
StartEfi.inf
## @file # A simple, basic, EDK II native, "hello" application. # # Copyright (c) 2010, Intel Corporation. All rights reserved.<BR> # This program and the accompanying materials # are licensed and made available under the terms and conditions of the BSD License # which accompanies this distribution. The full text of the license may be found at # http://opensource.org/licenses/bsd-license. # # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. # ## [Defines] INF_VERSION = 0x00010006 BASE_NAME = StartEfi FILE_GUID = a912f198-7f0e-4803-b908-b757b806ec81 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = UefiMain # # VALID_ARCHITECTURES = IA32 X64 IPF # [Sources] StartEfi.c [Packages] MdePkg/MdePkg.dec MdeModulePkg/MdeModulePkg.dec [LibraryClasses] UefiLib UefiHiiServicesLib UefiApplicationEntryPoint [Guids] gEfiFileInfoGuid [Protocols] gEfiBlockIoProtocolGuid gEfiSimpleFileSystemProtocolGuid
该实例是运行一个HelloWorld.efi,结果如下:
注意被运行的EFI一定要用标准的EFI Image入口,如果是AppPkg中使用的main函数作为入口,是无法被启动的!
运行BootLoader
操作系统安装的时候,会创建一个FAT32的分区,然后在EFI\BOOT\目录放入BOOTXXX.efi文件,XXX表示平台架构,x86平台64位系统为BOOTX64.EFI,Arm架构64位则为BOOTAA64.EFI...目前大多数使用的都是x86架构64位系统。
知道此原理后,我们可以将前面例子中 L"\\HelloWorld.efi" 改为 L"\\EFI\\BOOT\\BOOTX64.EFI",即可启动系统。测试可以使用虚拟机或者直接用实体机器,效果如下:
附件
已上传至Gitee:https://gitee.com/ay123net/uefistudy
版权声明:
作者:bin
链接:https://ay123.net/mystudy/uefi/1412/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。


共有 0 条评论