继续阅读《UEFI 原理与编程》,其中的第三章介绍了一个 EFI Application如何在 Shell下加载运行的。ShellProtocol.c中的InternalShellExecuteDevicePath 是具体实现这个功能的函数。
函数头:
/**
internal worker function to load and run an image via device path.
@param ParentImageHandle A handle of the image that is executing the specified
command line.
@param DevicePath device path of the file to execute
@param CommandLine Points to the NULL-terminated UCS-2 encoded string
containing the command line. If NULL then the command-
line will be empty.
@param Environment Points to a NULL-terminated array of environment
variables with the format 'x=y', where x is the
environment variable name and y is the value. If this
is NULL, then the current shell environment is used.
@param[out] StartImageStatus Returned status from gBS->StartImage.
@retval EFI_SUCCESS The command executed successfully. The status code
returned by the command is pointed to by StatusCode.
@retval EFI_INVALID_PARAMETER The parameters are invalid.
@retval EFI_OUT_OF_RESOURCES Out of resources.
@retval EFI_UNSUPPORTED Nested shell invocations are not allowed.
**/
EFI_STATUS
EFIAPI
InternalShellExecuteDevicePath(
IN CONST EFI_HANDLE *ParentImageHandle,
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath,
IN CONST CHAR16 *CommandLine OPTIONAL,
IN CONST CHAR16 **Environment OPTIONAL,
OUT EFI_STATUS *StartImageStatus OPTIONAL
)
第一步,先将 EFI Application加载到内存中,生成Image对象。取得这个对象的句柄为 NewHandle。
//
// Load the image with:
// FALSE - not from boot manager and NULL, 0 being not already in memory
//
Status = gBS->LoadImage(
FALSE,
*ParentImageHandle,
(EFI_DEVICE_PATH_PROTOCOL*)DevicePath,
NULL,
0,
&NewHandle);
if (EFI_ERROR(Status)) {
if (NewHandle != NULL) {
gBS->UnloadImage(NewHandle);
}
return (Status);
}
第二步,取得命令行参数,将命令行的参数交给 NewHandle
Status = gBS->OpenProtocol(
NewHandle,
&gEfiLoadedImageProtocolGuid,
(VOID**)&LoadedImage,
gImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (!EFI_ERROR(Status)) {
ASSERT(LoadedImage->LoadOptionsSize == 0);
if (NewCmdLine != NULL) {
LoadedImage->LoadOptionsSize = (UINT32)StrSize(NewCmdLine);
LoadedImage->LoadOptions = (VOID*)NewCmdLine;
}
看到这里我有一个问题:前面加载之后,为什么第二步就能够在被加载的Image上找到“EFI_Loaded_Image_Protocol”? 带着这样的问题追踪了代码。首先要找到 gBS->LoadImage 的真正实现代码,在\MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c有定义,
//
// DXE Core Module Variables
//
EFI_BOOT_SERVICES mBootServices = {
{
.................
.................
(EFI_IMAGE_LOAD) CoreLoadImage, // LoadImage
.................
.................
}
再追CoreLoadImage代码在 \MdeModulePkg\Core\Dxe\Image\Image.c
/**
Loads an EFI image into memory and returns a handle to the image.
@param BootPolicy If TRUE, indicates that the request originates
from the boot manager, and that the boot
manager is attempting to load FilePath as a
boot selection.
@param ParentImageHandle The caller's image handle.
@param FilePath The specific file path from which the image is
loaded.
@param SourceBuffer If not NULL, a pointer to the memory location
containing a copy of the image to be loaded.
@param SourceSize The size in bytes of SourceBuffer.
@param ImageHandle Pointer to the returned image handle that is
created when the image is successfully loaded.
@retval EFI_SUCCESS The image was loaded into memory.
@retval EFI_NOT_FOUND The FilePath was not found.
@retval EFI_INVALID_PARAMETER One of the parameters has an invalid value.
@retval EFI_UNSUPPORTED The image type is not supported, or the device
path cannot be parsed to locate the proper
protocol for loading the file.
@retval EFI_OUT_OF_RESOURCES Image was not loaded due to insufficient
resources.
@retval EFI_LOAD_ERROR Image was not loaded because the image format was corrupt or not
understood.
@retval EFI_DEVICE_ERROR Image was not loaded because the device returned a read error.
@retval EFI_ACCESS_DENIED Image was not loaded because the platform policy prohibits the
image from being loaded. NULL is returned in *ImageHandle.
@retval EFI_SECURITY_VIOLATION Image was loaded and an ImageHandle was created with a
valid EFI_LOADED_IMAGE_PROTOCOL. However, the current
platform policy specifies that the image should not be started.
**/
EFI_STATUS
EFIAPI
CoreLoadImage (
IN BOOLEAN BootPolicy,
IN EFI_HANDLE ParentImageHandle,
IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
IN VOID *SourceBuffer OPTIONAL,
IN UINTN SourceSize,
OUT EFI_HANDLE *ImageHandle
)
{
EFI_STATUS Status;
UINT64 Tick;
EFI_HANDLE Handle;
Tick = 0;
PERF_CODE (
Tick = GetPerformanceCounter ();
);
Status = CoreLoadImageCommon (
BootPolicy,
ParentImageHandle,
FilePath,
SourceBuffer,
SourceSize,
(EFI_PHYSICAL_ADDRESS) (UINTN) NULL,
NULL,
ImageHandle,
NULL,
EFI_LOAD_PE_IMAGE_ATTRIBUTE_RUNTIME_REGISTRATION | EFI_LOAD_PE_IMAGE_ATTRIBUTE_DEBUG_IMAGE_INFO_TABLE_REGISTRATION
);
Handle = NULL;
if (!EFI_ERROR (Status)) {
//
// ImageHandle will be valid only Status is success.
//
Handle = *ImageHandle;
}
PERF_START (Handle, "LoadImage:", NULL, Tick);
PERF_END (Handle, "LoadImage:", NULL, 0);
return Status;
}
在CoreLoadImageCommon 中可以找到加载安装EFI_Loaded_Image_Protocol 的代码。
//
//Reinstall loaded image protocol to fire any notifications
//
Status = CoreReinstallProtocolInterface (
Image->Handle,
&gEfiLoadedImageProtocolGuid,
&Image->Info,
&Image->Info
);
if (EFI_ERROR (Status)) {
goto Done;
}
就是说,在加载过程中,还要对Image 安装EFI_Loaded_Image_Protocol。因此,Load 的动作并不是简单的读取到内存中。
继续回到InternalShellExecuteDevicePath 代码中。
//第三步,将修改好的ShellParamsProtocol 再安装到 Image上。
//
// Initialize and install a shell parameters protocol on the image.
//
ShellParamsProtocol.StdIn = ShellInfoObject.NewShellParametersProtocol->StdIn;
ShellParamsProtocol.StdOut = ShellInfoObject.NewShellParametersProtocol->StdOut;
ShellParamsProtocol.StdErr = ShellInfoObject.NewShellParametersProtocol->StdErr;
Status = UpdateArgcArgv(&ShellParamsProtocol, NewCmdLine, NULL, NULL);
ASSERT_EFI_ERROR(Status);
//
// Replace Argv[0] with the full path of the binary we're executing:
// If the command line was "foo", the binary might be called "foo.efi".
// "The first entry in [Argv] is always the full file path of the
// executable" - UEFI Shell Spec section 2.3
//
ImagePath = EfiShellGetFilePathFromDevicePath (DevicePath);
// The image we're executing isn't necessarily in a filesystem - it might
// be memory mapped. In this case EfiShellGetFilePathFromDevicePath will
// return NULL, and we'll leave Argv[0] as UpdateArgcArgv set it.
if (ImagePath != NULL) {
if (ShellParamsProtocol.Argv == NULL) {
// Command line was empty or null.
// (UpdateArgcArgv sets Argv to NULL when CommandLine is "" or NULL)
ShellParamsProtocol.Argv = AllocatePool (sizeof (CHAR16 *));
if (ShellParamsProtocol.Argv == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto UnloadImage;
}
ShellParamsProtocol.Argc = 1;
} else {
// Free the string UpdateArgcArgv put in Argv[0];
FreePool (ShellParamsProtocol.Argv[0]);
}
ShellParamsProtocol.Argv[0] = ImagePath;
}
Status = gBS->InstallProtocolInterface(&NewHandle, &gEfiShellParametersProtocolGuid, EFI_NATIVE_INTERFACE, &ShellParamsProtocol);
ASSERT_EFI_ERROR(Status);
//第四步,用 gBS->StartImage执行Image。
//
// now start the image and if the caller wanted the return code pass it to them...
//
if (!EFI_ERROR(Status)) {
StartStatus = gBS->StartImage(
NewHandle,
0,
NULL
);
if (StartImageStatus != NULL) {
*StartImageStatus = StartStatus;
}