Step to UEFI (48) ----- 被加载程序的ENTRYPOINT

之前能获得被加载程序的一些基本信息,但是只是“基本”的信息,比如我们需要 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文件的入口。运行结果

N48

完整代码下载
exec4

本程序示例代码和编译中间文件

Hello2

Hello2build

《Step to UEFI (48) ----- 被加载程序的ENTRYPOINT》有5个想法

  1. 你好,最近在调研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 需要怎么处理呢?期待你的回复,谢谢

  2. 使用了两种方式进行加载:
    1. 实现一个EFI 程序,在这里通过 LoadImage + StartImage 启动,会报 not a ImageHandle
    2.在qemu 中,直接运行,报 out of resource

  3. 我用了两种方式:
    1.实现一个EFI 在其中通过 LoadImage 和 StartImage 来尝试启动 Linux.efi, 这里报的是 "not a ImageHandle"
    2.在QEMU 启动 UEFI 的shell中直接运行 Linux.efi, 此时报 "out of resource"
    NOTE: qemu 启动的是 aarch64 的 UEFI, 其中 linux 也是aarch64 交叉编译工具编译,两者工具链一致

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注