之前能获得被加载程序的一些基本信息,但是只是“基本”的信息,比如我们需要 EntryPoint应该怎么办呢?
我在网上搜索了一下无果,请教 HZZZ,他给我的建议是:LOADED_IMAGE_PRIVATE_DATA_TEMP。
可以在 \MdeModulePkg\Core\Dxe\Image\Image.h 中看到这个定义。
typedef struct { UINTN Signature; /// Image handle EFI_HANDLE Handle; /// Image type UINTN Type; /// If entrypoint has been called BOOLEAN Started; /// The image's entry point EFI_IMAGE_ENTRY_POINT EntryPoint; /// loaded image protocol EFI_LOADED_IMAGE_PROTOCOL Info; /// Location in memory EFI_PHYSICAL_ADDRESS ImageBasePage; /// Number of pages UINTN NumberOfPages; /// Original fixup data CHAR8 *FixupData; /// Tpl of started image EFI_TPL Tpl; /// Status returned by started image EFI_STATUS Status; /// Size of ExitData from started image UINTN ExitDataSize; /// Pointer to exit data from started image VOID *ExitData; /// Pointer to pool allocation for context save/retore VOID *JumpBuffer; /// Pointer to buffer for context save/retore BASE_LIBRARY_JUMP_BUFFER *JumpContext; /// Machine type from PE image UINT16 Machine; /// EBC Protocol pointer EFI_EBC_PROTOCOL *Ebc; /// Runtime image list EFI_RUNTIME_IMAGE_ENTRY *RuntimeData; /// Pointer to Loaded Image Device Path Protocl EFI_DEVICE_PATH_PROTOCOL *LoadedImageDevicePath; /// PeCoffLoader ImageContext PE_COFF_LOADER_IMAGE_CONTEXT ImageContext; } LOADED_IMAGE_PRIVATE_DATA;
根据我的理解,我们之前使用到的 EFI_LOADED_IMAGE_PROTOCOL 只是这个结构体的一部分。我们知道 EFI_LOADED_IMAGE_PROTOCOL 的内存地址,然后可以反推出整个 LOADED_IMAGE_PRIVATE_DATA_TEMP 结构。为了实现这个需要用一个比较有技巧的宏:
#define _CR(Record, TYPE, Field) ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))
#define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info)
(在其他地方也能看到这个宏的,它的作用就是根据一个结构体中已知Field的地址反推出整个结构体的内存地址。充满了C语言让人炫目的技巧。)
简单起见 HZZZ 给我的建议是这个结构体可以只使用一部分,不需要声明全部。
完整的代码:
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <stdio.h> #include <stdlib.h> #include <wchar.h> #include <Protocol/EfiShell.h> #include <Library/ShellLib.h> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; extern EFI_SHELL_PROTOCOL *gEfiShellProtocol; extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2; extern EFI_HANDLE gImageHandle; typedef struct { UINTN Signature; /// Image handle EFI_HANDLE Handle; /// Image type UINTN Type; /// If entrypoint has been called BOOLEAN Started; /// The image's entry point EFI_IMAGE_ENTRY_POINT EntryPoint; /// loaded image protocol EFI_LOADED_IMAGE_PROTOCOL Info; /// Location in memory EFI_PHYSICAL_ADDRESS ImageBasePage; } LOADED_IMAGE_PRIVATE_DATA_TEMP; #define _CR(Record, TYPE, Field) ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field))) #define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) \ _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info) /** GET DEVICEPATH **/ EFI_DEVICE_PATH_PROTOCOL * EFIAPI ShellGetDevicePath ( IN CHAR16 * CONST DeviceName OPTIONAL ) { // // Check for UEFI Shell 2.0 protocols // if (gEfiShellProtocol != NULL) { return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName)); } // // Check for EFI shell // if (mEfiShellEnvironment2 != NULL) { return (mEfiShellEnvironment2->NameToPath(DeviceName)); } return (NULL); } int EFIAPI main ( IN int Argc, IN CHAR16 **Argv ) { EFI_DEVICE_PATH_PROTOCOL *DevicePath; EFI_HANDLE NewHandle; EFI_STATUS Status; LOADED_IMAGE_PRIVATE_DATA_TEMP *private = NULL; UINTN ExitDataSizePtr; EFI_LOADED_IMAGE_PROTOCOL *ImageInfo = NULL; if (Argc!=2) { Print(L"Usage: Exec4 FileName\n"); return EFI_SUCCESS; } Print(L"File [%s]\n",Argv[1]); DevicePath=ShellGetDevicePath(Argv[1]); // // Load the image with: // FALSE - not from boot manager and NULL, 0 being not already in memory // Status = gBS->LoadImage( FALSE, gImageHandle, DevicePath, NULL, 0, &NewHandle); if (EFI_ERROR(Status)) { if (NewHandle != NULL) { gBS->UnloadImage(NewHandle); } Print(L"Error during LoadImage [%X]\n",Status); return (Status); } Status = gBS -> HandleProtocol ( NewHandle, &gEfiLoadedImageProtocolGuid, &ImageInfo ); private = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(ImageInfo); Print(L"ImageBase in EFI_LOADED_IMAGE_PROTOCOL [%lX]\n",ImageInfo->ImageBase); Print(L"ImageBase in LOADED_IMAGE_PRIVATE_DATA_TEMP [%lX]\n",private->ImageBasePage); Print(L"Entry Point [%lX]\n",private->EntryPoint); Print(L"================================RUN================================\r\n",Status); // // now start the image, passing up exit data if the caller requested it // Status = gBS->StartImage( NewHandle, &ExitDataSizePtr, NULL ); if (EFI_ERROR(Status)) { if (NewHandle != NULL) { gBS->UnloadImage(NewHandle); } Print(L"Error during StartImage [%X]\r\n",Status); return (Status); } Print(L"===============================EXIT================================\r\n",Status); gBS->UnloadImage (NewHandle); return EFI_SUCCESS; }
程序接收文件名作为参数,显示接收到EFI文件的入口。运行结果
完整代码下载
exec4
本程序示例代码和编译中间文件
你好,最近在调研UEFI,在实际使用过程中发现有这样的问题,
1.在编译 linux kernel 时 开启 EFI, EFI_STUB config 选项
2.按照网上的一些办法重命名为 Linux.efi
3.在qemu 启动 uefi 程序中,实现一个 UEFI 程序来加载 Linux.efi
4.结果会报错
原因猜测是:
1.编译的 Linux kernel Image 并不是 EFI 格式
2. 或者没有相应的 EntryPoint
但是file Image 查看是MS-DOS executable格式,和 EFI 是同一种格式,应该排除了1
那么可能是2,请问如果想运行 linux kernel 需要怎么处理呢?期待你的回复,谢谢
看一下加载的错误信息是什么? 具体是无法加载还是已经进入 Linux.efi 然后出错退出的。
使用了两种方式进行加载:
1. 实现一个EFI 程序,在这里通过 LoadImage + StartImage 启动,会报 not a ImageHandle
2.在qemu 中,直接运行,报 out of resource
我用了两种方式:
1.实现一个EFI 在其中通过 LoadImage 和 StartImage 来尝试启动 Linux.efi, 这里报的是 "not a ImageHandle"
2.在QEMU 启动 UEFI 的shell中直接运行 Linux.efi, 此时报 "out of resource"
NOTE: qemu 启动的是 aarch64 的 UEFI, 其中 linux 也是aarch64 交叉编译工具编译,两者工具链一致
我觉得是你的 Linux.efi 不是标准 efi 文件导致的。