UEFI应用程序加载过程
当在Shell中执行UefiMain.efi时,Shell首先使用gBS->LoadImage()将UefiMain.efi文件加载到内存生成Image对象,然后调用gBS->StartImage(Image)启动这个Image对象。
1 2 3 4 5 6 7 8 9
| EFI_STATUS 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 )
|
第一步:将UefiMain.efi文件加载到内存,生成Image对象,NewHandle是这个对象的句柄。
1 2 3 4 5 6 7 8 9 10
| Status = gBS->LoadImage (FALSE, *ParentImageHandle, (EFI_DEVICE_PATH_PROTOCOL *)DevicePath, NULL, 0, &NewHandle); if (EFI_ERROR (Status)) { if (NewHandle != NULL) { gBS->UnloadImage (NewHandle); } FreePool (NewCmdLine); return (Status); }
|
函数指针LoadImage()指向函数CoreLoadImage(),函数内部再调用CoreLoadImageCommon()将EFI镜像加载到内存中,并返回镜像句柄,函数内部会优先判断是从内存加载还是从设备加载,如果SourceBuffer不为NULL,则返回指向内存位置的指针,此时DevicePath仅作为标识,不参与实际读取,SourceBuffer为NULL是最常见的Shell加载方式,此时会按照FirmwareVolume2(BIOS芯片内部的固件卷),SimpleFileSystem(文件系统),LoadFile2和LoadFile(PXE网络启动等没有本地存储)的顺序解析HandleFilePath,通过设备路径获取源文件指针并保存到FHand.Source。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| if (SourceBuffer != NULL) { FHand.Source = SourceBuffer; FHand.SourceSize = SourceSize; Status = CoreLocateDevicePath (&gEfiDevicePathProtocolGuid, &HandleFilePath, &DeviceHandle); if (EFI_ERROR (Status)) { DeviceHandle = NULL; } if (SourceSize > 0) { Status = EFI_SUCCESS; } else { Status = EFI_LOAD_ERROR; } } else { if (FilePath == NULL) { return EFI_INVALID_PARAMETER; } Node = NULL; Status = CoreLocateDevicePath (&gEfiFirmwareVolume2ProtocolGuid, &HandleFilePath, &DeviceHandle); if (!EFI_ERROR (Status)) { ImageIsFromFv = TRUE; } else { HandleFilePath = FilePath; Status = CoreLocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &HandleFilePath, &DeviceHandle); if (EFI_ERROR (Status)) { if (!BootPolicy) { HandleFilePath = FilePath; Status = CoreLocateDevicePath (&gEfiLoadFile2ProtocolGuid, &HandleFilePath, &DeviceHandle); } if (EFI_ERROR (Status)) { HandleFilePath = FilePath; Status = CoreLocateDevicePath (&gEfiLoadFileProtocolGuid, &HandleFilePath, &DeviceHandle); if (!EFI_ERROR (Status)) { ImageIsFromLoadFile = TRUE; Node = HandleFilePath; } } } } FHand.Source = GetFileBufferByFilePath (BootPolicy, FilePath, &FHand.SourceSize, &AuthenticationStatus); if (FHand.Source == NULL) { Status = EFI_NOT_FOUND; } else { FHand.FreeBuffer = TRUE; if (ImageIsFromLoadFile) { OriginalFilePath = AppendDevicePath (DevicePathFromHandle (DeviceHandle), Node); if (OriginalFilePath == NULL) { Image = NULL; Status = EFI_OUT_OF_RESOURCES; goto Done; } } }
|
读取到文件后为其构建文件对象,注意分配大小是LOADED_IMAGE_PRIVATE_DATA,其结构包含类型为EFI_LOADED_IMAGE_PROTOCOL的Info字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| Image = AllocateZeroPool (sizeof (LOADED_IMAGE_PRIVATE_DATA)); if (Image == NULL) {
Status = EFI_OUT_OF_RESOURCES; goto Done; }
FilePath = OriginalFilePath; if (DeviceHandle != NULL) { Status = CoreHandleProtocol (DeviceHandle, &gEfiDevicePathProtocolGuid, (VOID **)&HandleFilePath); if (!EFI_ERROR (Status)) { FilePathSize = GetDevicePathSize (HandleFilePath) - sizeof (EFI_DEVICE_PATH_PROTOCOL); FilePath = (EFI_DEVICE_PATH_PROTOCOL *)(((UINT8 *)FilePath) + FilePathSize); } }
Image->Signature = LOADED_IMAGE_PRIVATE_DATA_SIGNATURE; Image->Info.SystemTable = gDxeCoreST; Image->Info.DeviceHandle = DeviceHandle; Image->Info.Revision = EFI_LOADED_IMAGE_PROTOCOL_REVISION; Image->Info.FilePath = DuplicateDevicePath (FilePath); Image->Info.ParentHandle = ParentImageHandle; if (NumberOfPages != NULL) { Image->NumberOfPages = *NumberOfPages; } else { Image->NumberOfPages = 0; }
|
安装镜像又分为四步,任何一步出错会通过goto Done处理,CoreInstallProtocolInterfaceNotify()用于在Boot Services环境安装协议接口,此时初步挂载gEfiLoadedImageProtocolGuid到Image->Handle,但不加载PE;CoreLoadPeImage()开始加载、重定位并调用PE/COFF镜像(实际内部并没有任何函数调用PE镜像),函数内部通过PeCoffLoaderGetImageInfo()获取PE镜像信息,PeCoffLoaderLoadImage()将镜像加载到分配的内存中,PeCoffLoaderRelocateImage()重定位在内存中的镜像,在确认可用的条件下填充EntryPoint作为返回结果;CoreReinstallProtocolInterface()在设备句柄重新安装协议接口,协议的旧接口被新接口替换,内部会调用CoreNotifyProtocolEntry()以触发通知;CoreInstallProtocolInterface()本身作为公共EFIAPI,内部会调用CoreInstallProtocolInterfaceNotify()重新为Image->Handle安装LoadedImageDevicePath协议。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| Status = CoreInstallProtocolInterfaceNotify (&Image->Handle, &gEfiLoadedImageProtocolGuid, EFI_NATIVE_INTERFACE, &Image->Info, FALSE); if (EFI_ERROR (Status)) { goto Done; }
Status = CoreLoadPeImage (BootPolicy, &FHand, Image, DstBuffer, EntryPoint, Attribute); if (EFI_ERROR (Status)) { if ((Status == EFI_BUFFER_TOO_SMALL) || (Status == EFI_OUT_OF_RESOURCES)) { if (NumberOfPages != NULL) { *NumberOfPages = Image->NumberOfPages; } } goto Done; } if (NumberOfPages != NULL) { *NumberOfPages = Image->NumberOfPages; }
Status = CoreReinstallProtocolInterface (Image->Handle, &gEfiLoadedImageProtocolGuid, &Image->Info, &Image->Info); if (EFI_ERROR (Status)) { goto Done; }
Status = CoreInstallProtocolInterface (&Image->Handle, &gEfiLoadedImageDevicePathProtocolGuid, EFI_NATIVE_INTERFACE, Image->LoadedImageDevicePath); if (EFI_ERROR (Status)) { goto Done; }
*ImageHandle = Image->Handle;
|
第二步:取得命令行参数,并将命令行参数交给UefiMain.efi的Image对象,即NewHandle。
1 2 3 4 5 6 7 8 9 10
| Status = gBS->OpenProtocol (NewHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (!EFI_ERROR (Status)) { if (LoadedImage->ImageCodeType != EfiLoaderCode) { ShellPrintHiiDefaultEx ( STRING_TOKEN (STR_SHELL_IMAGE_NOT_APP), ShellInfoObject.HiiHandle ); goto UnloadImage; }
|
第三步:启动所加载的Image。
1 2 3 4 5 6 7 8 9
| if (!EFI_ERROR (Status)) { StartStatus = gBS->StartImage (NewHandle, 0, NULL); if (StartImageStatus != NULL) { *StartImageStatus = StartStatus; } CleanupStatus = gBS->UninstallProtocolInterface (NewHandle, &gEfiShellParametersProtocolGuid, &ShellParamsProtocol); ASSERT_EFI_ERROR (CleanupStatus); goto FreeAlloc; }
|
函数指针StartImage()指向函数CoreStartImage(),函数内部将控制权转移到已加载镜像的入口点,通过AllocatePool()申请内存,使用ALIGN_POINTER保证JumpContext符合CPU架构的内存对齐要求,对于X64的函数调用规则,SetJump()会负责将需要的Rbx,Rsp,Rbp,Rdi,Rsi,R12,R13,R14,R15,Rip,MxCsr,XmmBuffer(需要注意这里会保存浮点寄存器),Ssp寄存器的内容保存到JumpBuffer,并且首次调用SetJumpFlag为0,控制权的交接在Image->EntryPoint()(是不是觉得这里的ImageHandle和SystemTable很熟悉),而应用程序返回有两种方式,正常结束return EFI_SUCCESS,这样会向下执行CoreExit(),内部调用LongJump()再次回到SetJump(),SetJumpFlag为EFI_SUCCESS,跳过分支执行后续清理流程;强制退出使用gBS->Exit(),内部调用LongJump(),过程同上,唯一不同的是SetJumpFlag为传给Exit()的非0状态码(不理解这里的可以搜索C语言使用setjmp()和longjmp()非局部跳转实现异常处理,两个函数解决goto局部跳转问题,但这里更多是为了避免应用程序陷入兔子洞的情况,这种方法可以将堆栈重置到原来状态而无需解析整个程序的调用链,且拥有返回值说明原因,需要注意除非遇到非常具体的情况,否则最好避免使用它)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| Image->JumpBuffer = AllocatePool (sizeof (BASE_LIBRARY_JUMP_BUFFER) + BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT); if (Image->JumpBuffer == NULL) { PERF_START_IMAGE_END (NULL); mCurrentImage = LastImage; return EFI_OUT_OF_RESOURCES; } Image->JumpContext = ALIGN_POINTER (Image->JumpBuffer, BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT); SetJumpFlag = SetJump (Image->JumpContext);
if (SetJumpFlag == 0) { RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER)); Image->Started = TRUE; Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable); DEBUG_CODE_BEGIN (); if (EFI_ERROR (Image->Status)) { DEBUG ((DEBUG_ERROR, "Error: Image at %11p start failed: %r\n", Image->Info.ImageBase, Image->Status)); } DEBUG_CODE_END (); CoreExit (ImageHandle, Image->Status, 0, NULL); }
|