自由自在的在空中飞翔一直是人类梦想和追求的目标。在飞机发明之前,人类能够通过热气球氢气球的方式实现滞空飞行,1903年12月17日莱特兄弟实验成功的“飞行者一号”是完全受控、依靠自身动力、机身比空气重、持续滞空不落地的飞行器。因此,莱特兄弟也是世界公认的飞机发明者。他们能够成功的一个重要原因是他们实验的方法和之前的先驱相比,更加安全和高效。莱特兄弟于1900年建造了一个风洞,截面40.6厘米×40.6厘米,长1.8米,气流速度40~56.3千米/小时。1901年莱特兄弟又建造了风速12米/秒的风洞,为他们的飞机进行有关的实验测试。【来自百度百科】
对于我们来说,EDK2 自带的 NT32 模拟环境也是一个便于实验的风洞。在没有实体机的情况下,它提供更加简单便捷的测试方法。
前面提到了可以通过在 Application 中直接 Include 另外一个 EFI Application ,然后通过 StartImage 执行之。剩下的问题就是为什么当我们使用UnloadImage 的时候会出现 Error。

首先,要找到出现这个错误的位置。根据我们之前的经验,在\MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c 有定义LoadImage StartImage 和 UnloadImage
//
// DXE Core Module Variables
//
EFI_BOOT_SERVICES mBootServices = {
{
EFI_BOOT_SERVICES_SIGNATURE, // Signature
EFI_BOOT_SERVICES_REVISION, // Revision
sizeof (EFI_BOOT_SERVICES), // HeaderSize
0, // CRC32
0 // Reserved
},
……………………
(EFI_IMAGE_LOAD) CoreLoadImage, // LoadImage
(EFI_IMAGE_START) CoreStartImage, // StartImage
(EFI_IMAGE_UNLOAD) CoreUnloadImage, // UnloadImage
……………………
当我们调用 gBS->UnloadImage 的时候,实际上是由CoreUnloadImage来完成的。对应的代码在\MdeModulePkg\Core\Dxe\Image\Image.c 中。
/**
Unloads an image.
@param ImageHandle Handle that identifies the image to be
unloaded.
@retval EFI_SUCCESS The image has been unloaded.
@retval EFI_UNSUPPORTED The image has been started, and does not support
unload.
@retval EFI_INVALID_PARAMPETER ImageHandle is not a valid image handle.
**/
EFI_STATUS
EFIAPI
CoreUnloadImage (
IN EFI_HANDLE ImageHandle
)
{
EFI_STATUS Status;
LOADED_IMAGE_PRIVATE_DATA *Image;
Image = CoreLoadedImageInfo (ImageHandle);
if (Image == NULL ) {
//
// The image handle is not valid
//
Status = EFI_INVALID_PARAMETER;
goto Done;
}
通过前面介绍的插入 DEBUG 输出 Message 的方法,可以看到最终的错误是CoreLoadedImageInfo (ImageHandle); 调用返回的错误找到的,在同样的文件中还可以找到CoreLoadedImageInfo 的定义:
/**
Get the image's private data from its handle.
@param ImageHandle The image handle
@return Return the image private data associated with ImageHandle.
**/
LOADED_IMAGE_PRIVATE_DATA *
CoreLoadedImageInfo (
IN EFI_HANDLE ImageHandle
)
{
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
LOADED_IMAGE_PRIVATE_DATA *Image;
Status = CoreHandleProtocol (
ImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID **)&LoadedImage
);
if (!EFI_ERROR (Status)) {
Image = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS (LoadedImage);
} else {
DEBUG ((DEBUG_LOAD, "CoreLoadedImageInfo: Not an ImageHandle %p\n", ImageHandle));
Image = NULL;
}
return Image;
}
下面的错误是NT32 模拟环境输出的 Debug 信息,Hello2.efi 的 Handle 是 4BFE318:

从上面的信息可以确定错误发生在 CoreHandleProtocol 函数的调用过程中。此时正在尝试在给定的Handle 上查找gEfiLoadedImageProtocolGuid Protocol( gEfiLoadedImageProtocolGuid = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }})。
继续查找这个 Protocol 的来源,是在 CoreLoadImageCommon 函数中安装的:
//
// Install the protocol interfaces for this image
// don't fire notifications yet
//
Status = CoreInstallProtocolInterfaceNotify (
&Image->Handle,
&gEfiLoadedImageProtocolGuid,
EFI_NATIVE_INTERFACE,
&Image->Info,
FALSE
);
if (EFI_ERROR (Status)) {
goto Done;
}
经过试验,运行 RIM.EFI 后会多次调用CoreLoadedImageInfo 函数,但是奇怪的是前面几次不会有问题,最后一次UnloadImage 的时候才会出现错误。因此,这意味着有人在整个过程中卸载了这个 Protocol。接下来尝试在 Application 中去掉了 StartImage 函数,惊奇的发现问题会消失。接下来就研究StartImage对应的CoreStartImage 函数,在 \MdeModulePkg\Core\Dxe\Image\Image.c
/**
Transfer control to a loaded image's entry point.
@param ImageHandle Handle of image to be started.
@param ExitDataSize Pointer of the size to ExitData
@param ExitData Pointer to a pointer to a data buffer that
includes a Null-terminated string,
optionally followed by additional binary data.
The string is a description that the caller may
use to further indicate the reason for the
image's exit.
@retval EFI_INVALID_PARAMETER Invalid parameter
@retval EFI_OUT_OF_RESOURCES No enough buffer to allocate
@retval EFI_SECURITY_VIOLATION The current platform policy specifies that the image should not be started.
@retval EFI_SUCCESS Successfully transfer control to the image's
entry point.
**/
EFI_STATUS
EFIAPI
CoreStartImage (
IN EFI_HANDLE ImageHandle,
OUT UINTN *ExitDataSize,
OUT CHAR16 **ExitData OPTIONAL
)
看到了其中有如下操作:
//
// If the image returned an error, or if the image is an application
// unload it
//
if (EFI_ERROR (Image->Status) || Image->Type == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) {
CoreUnloadAndCloseImage (Image, TRUE);
//
// ImageHandle may be invalid after the image is unloaded, so use NULL handle to record perf log.
//
Handle = NULL;
}
这段代码的意思是:如果加载的代码运行有问题或者Image->Type 是EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION 就直接在 StartImage 中释放掉 Image了。而我们调用的 Hello2.efi 类型是EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ,因此这里释放掉了Handle 上面的 Protocol,当我们调用的时候确实也无法找到。
结论:我们碰到的错误是因为RIM 这个 Application中多此一举的添加了UnLoadImage的操作,去掉这个动作就正常了。