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,如下:
这里要特别提一下 EFI_IMAGE_ENTRY_POINT ,它定义了一个标准 EFI Image 的入口函数:
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 条评论