最近在研究 UEFI Application结构相关的内容。除了静态的分析,还需要找到一种观察和调试加载到内存后EFI文件的方法。经过比较和研究, NT32 模拟器是很好的选择。通过它能够方便的进行观察和反编译。
需要解决的第一个问题是:找到跳转到Application Entry Point 处的代码。经过研究入口位于 \MdeModulePkg\Core\Dxe\Image\Image.c 文件中下面这个函数:
EFI_STATUS
EFIAPI
CoreStartImage (
IN EFI_HANDLE ImageHandle,
OUT UINTN *ExitDataSize,
OUT CHAR16 **ExitData OPTIONAL
)
前面准备妥当后,在下面的语句中跳转到Application Entry来开始执行:
//
// Call the image's entry point
//
Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
接下来研究如何实现如何触发,这里采用根据文件大小的方式进行触发。同样的上面这个函数中,申明了下面这个变量:
LOADED_IMAGE_PRIVATE_DATA *Image;
这个结构体定义在 \MdePkg\Include\Protocol\LoadedImage.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/restore
VOID *JumpBuffer;
/// Pointer to buffer for context save/restore
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 Protocol
EFI_DEVICE_PATH_PROTOCOL *LoadedImageDevicePath;
/// PeCoffLoader ImageContext
PE_COFF_LOADER_IMAGE_CONTEXT ImageContext;
/// Status returned by LoadImage() service.
EFI_STATUS LoadImageStatus;
} LOADED_IMAGE_PRIVATE_DATA;
其中的 EFI_LOADED_IMAGE_PROTOCOL 包含了当前的 Image Size 信息:
///
/// Revision defined in EFI1.1.
///
#define EFI_LOADED_IMAGE_INFORMATION_REVISION EFI_LOADED_IMAGE_PROTOCOL_REVISION
///
/// Can be used on any image handle to obtain information about the loaded image.
///
typedef struct {
UINT32 Revision; ///< Defines the revision of the EFI_LOADED_IMAGE_PROTOCOL structure.
///< All future revisions will be backward compatible to the current revision.
EFI_HANDLE ParentHandle; ///< Parent image's image handle. NULL if the image is loaded directly from
///< the firmware's boot manager.
EFI_SYSTEM_TABLE *SystemTable; ///< the image's EFI system table pointer.
//
// Source location of image
//
EFI_HANDLE DeviceHandle; ///< The device handle that the EFI Image was loaded from.
EFI_DEVICE_PATH_PROTOCOL *FilePath; ///< A pointer to the file path portion specific to DeviceHandle
///< that the EFI Image was loaded from.
VOID *Reserved; ///< Reserved. DO NOT USE.
//
// Images load options
//
UINT32 LoadOptionsSize;///< The size in bytes of LoadOptions.
VOID *LoadOptions; ///< A pointer to the image's binary load options.
//
// Location of where image was loaded
//
VOID *ImageBase; ///< The base address at which the image was loaded.
UINT64 ImageSize; ///< The size in bytes of the loaded image.
EFI_MEMORY_TYPE ImageCodeType; ///< The memory type that the code sections were loaded as.
EFI_MEMORY_TYPE ImageDataType; ///< The memory type that the data sections were loaded as.
EFI_IMAGE_UNLOAD Unload;
} EFI_LOADED_IMAGE_PROTOCOL;
我们使用代码中的 Hello.EFI 作为例子,它的大小是7712bytes。最终,代码如下:
SetJumpFlag = SetJump (Image->JumpContext);
//
// The initial call to SetJump() must always return 0.
// Subsequent calls to LongJump() cause a non-zero value to be returned by SetJump().
//
if (SetJumpFlag == 0) {
RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));
//LABZDEBUG_Start
DEBUG ((EFI_D_INFO,"Current size [%d] bytes\n", Image->Info.ImageSize));
if (Image->Info.ImageSize == 7712) {
CpuBreakpoint();
}
//LABZDEBUG_End
//
// Call the image's entry point
//
Image->Started = TRUE;
Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);
就是说当发现加载的Image 大小是 7712 bytes 的时候自动触发一个 Breakpoint 打开 VS 进行调试:

接下来我们可以跳入Image 的领空进行查看和调试了。但是显而易见,这样的方法并不完美,如果Image 大小有变化,我们就需要重新编译运行 NT32 模拟器。后面会介绍如何使用文件名称来作为触发的判定条件,有兴趣的朋友可以尝试自己先进性研究。