一、标准应用工程编译

首先了解下,应用程序是怎么被编译成.efi文件:

  1. UefiMain.c首先被编译成目标文件UefiMain.obj
  2. 连接器将目标文件UefiMain.obj和其他库连接成UefiMain.dll
  3. GenFw工具将UefiMain.dll转换成UefiMain.efi

说明:连接器在生成UefiMain.dll时使用了/dll/entry:_ModuleEntryPoint..efi是遵循PE32格式的二进制文件,_ModuleEntryPoint是这个二进制的入口函数

模块入口函数一般为UefiMain,但在inf工程文件里面可以更改

ENTRY_POINT                    = UefiMain

那么_ModuleEntryPoint跟UefiMain有什么关系呢?

让我们带着这个疑问来看应用程序的加载过程

二、标准应用程序加载

2.1将UefiMain.efi加载到内存

在shell下执行UefiMain.c时,shell执行的大致步骤:

  1. 首先用gBS->LoadImage()将UefiMain.efi文件加载到内存生成Image对象
  2. 然后调用gBS->StartImage(Image)启动这个对象.
    代码清单如下:
edk2/ShellPkg/Application/Shell/ShellProtocol.c
EFI_STATUS
InternalShellExecuteDevicePath(IN CONST EFI_HANDLE               *ParentImageHandle, //正在执行命令行的句柄IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath, //UefiMain.efi的设备路径IN CONST CHAR16                   *CommandLine OPTIONAL, //应用程序所需的命令行参数IN CONST CHAR16                   **Environment OPTIONAL, //UEFI环境变量OUT EFI_STATUS                    *StartImageStatus OPTIONAL  //程序UefiMain.efi的返回值)
{...//第一步:将UefiMain.efi文件加载到内存, 生成Image对象,NewHandle是这个对象的句柄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);}//第二步:取得命令行参数,并将命令行参数交给UefiMain.efi的Image对象,即NewHandleStatus = 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;}
...省略
//第三步:启动所加载的Imageif (!EFI_ERROR(Status)) {StartStatus      = gBS->StartImage(NewHandle,0,NULL);if (StartImageStatus != NULL) {*StartImageStatus = StartStatus;}}

加载应用程序最重要的一步,就是gBS->StartImage(NewHandle, 0,NULL)。StartImage主要作用就是找出可执行映象(Image)的入口函数并执行找到的入口函数。gBS-》StartImage是一个函数指针,它实际指向CoreStartImage函数

2.2进入映象的入口函数

CoreStartImage的主要作用调用映象的入口函数。CoreStartImage如下:

edk2/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 thatincludes a Null-terminated string,optionally followed by additional binary data.The string is a description that the caller mayuse to further indicate the reason for theimage'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'sentry point.**/
EFI_STATUS
EFIAPI
CoreStartImage (IN EFI_HANDLE  ImageHandle,OUT UINTN      *ExitDataSize,OUT CHAR16     **ExitData  OPTIONAL)
{...省略
//设置LongJump,用于退出此程序Image->JumpBuffer = AllocatePool (sizeof (BASE_LIBRARY_JUMP_BUFFER) + BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);if (Image->JumpBuffer == NULL) {...return EFI_OUT_OF_RESOURCES;}Image->JumpContext = ALIGN_POINTER (Image->JumpBuffer, BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);SetJumpFlag = SetJump (Image->JumpContext);//首次调用SetJump()返回0。通过LongJump(Image->JumpContext)跳转到此处时返回非零值if (SetJumpFlag == 0) {RegisterMemoryProfileImage (Image, (Image->ImageContext.ImageType == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION ? EFI_FV_FILETYPE_APPLICATION : EFI_FV_FILETYPE_DRIVER));//调用Image的入口函数Image->Started = TRUE;Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);//设置执行后的状态,然后通过LongJump跳到应用程序退出点CoreExit (ImageHandle, Image->Status, 0, NULL);}
//此处是应用程序退出点
//程序通过LongJump跳转到此处,然后根据Image->进行错误处理
...
}

在gBS->StartImge中,SetJump/LongJump为应用程序提供了一种错误处理机制,如下:

gBS->StartImge的核心是Image->EntryPoint(),它就是程序映象的入口函数,对应用程序来说,就是_ModuleEntryPoint后,控制权才转交给应用程序(此处就是我们的UefiMain.efi),_ModuleEntryPoint代码如下:

edk2/MdePkg/Library/UefiApplicationEntryPoint/ApplicationEntryPoint.c
/**Entry point to UEFI Application.This function is the entry point for a UEFI Application. This function must callProcessLibraryConstructorList(), ProcessModuleEntryPointList(), and ProcessLibraryDestructorList().The return value from ProcessModuleEntryPointList() is returned.If _gUefiDriverRevision is not zero and SystemTable->Hdr.Revision is less than _gUefiDriverRevison,then return EFI_INCOMPATIBLE_VERSION.@param  ImageHandle                The image handle of the UEFI Application.@param  SystemTable                A pointer to the EFI System Table.@retval  EFI_SUCCESS               The UEFI Application exited normally.@retval  EFI_INCOMPATIBLE_VERSION  _gUefiDriverRevision is greater than SystemTable->Hdr.Revision.@retval  Other                     Return value from ProcessModuleEntryPointList().**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (IN EFI_HANDLE        ImageHandle,IN EFI_SYSTEM_TABLE  *SystemTable)
{EFI_STATUS                 Status;if (_gUefiDriverRevision != 0) {//确保系统平台的UEFI版本号大于或者等于ImageHandle的UEFI版本号if (SystemTable->Hdr.Revision < _gUefiDriverRevision) {return EFI_INCOMPATIBLE_VERSION;}}//所有将被使用的库的构造函数ProcessLibraryConstructorList (ImageHandle, SystemTable);//调用Image的入口函数Status = ProcessModuleEntryPointList (ImageHandle, SystemTable);//所有库的析构函数ProcessLibraryDestructorList (ImageHandle, SystemTable);// Return the return status code from the driver entry pointreturn Status;
}

_ModuleEntryPoint主要处理三个事情:

  1. 初始化:在初始化函数ProcessLibraryConstructorList中会调用一系列的构造函数
  2. 调用本模块的入口函数:在ProcessModuleEntryPointList中会调用应用程序模块的真正入口函数(即我们在inf文件定义的入口函数UefiMain)
  3. 析构:在析构函数ProcessLibraryDestructorList中会调用一系列析构函数

那么上面这三个函数是在哪里定义的呢?

在执行build命令的时候,build命令会解析模块的工程文件(即inf文件),然后生成AutoGen.h和AutoGen.c,这三个函数就是在AutoGen.c中定义的。
一般而言,在inf文件的 [LibraryClasses] 段声明了某个库后,如果这个库有构造函数,AutoGen便会在ProcessLibraryConstructorList中加入这个库的构造函数,另外,ProcessLibraryConstructorList还会加入启动服务和运行时服务的构造函数。
这里看一个例子:
inf PATH:

edk2/MdeModulePkg/Application/HelloWorld/HelloWorld.inf

把该工程加到OvmfPkg中编译,找到它编译生成的AutoGen.c:

edk2/Build/OvmfX64/DEBUG_GCC5/X64/MdeModulePkg/Application/HelloWorld/HelloWorld/DEBUG/AutoGen.c
...省略//库构造
VOID
EFIAPI
ProcessLibraryConstructorList (IN EFI_HANDLE        ImageHandle,IN EFI_SYSTEM_TABLE  *SystemTable)
{EFI_STATUS  Status;Status = PlatformDebugLibIoPortConstructor ();ASSERT_RETURN_ERROR (Status);//初始化全局变量gBS、gST和gImageHandleStatus = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);ASSERT_EFI_ERROR (Status);//初始化全局变量gRTStatus = UefiRuntimeServicesTableLibConstructor (ImageHandle, SystemTable);ASSERT_EFI_ERROR (Status);Status = DevicePathLibConstructor (ImageHandle, SystemTable);ASSERT_EFI_ERROR (Status);//初始化UefiLib,Print函数就是在UefiLib中实现的Status = UefiLibConstructor (ImageHandle, SystemTable);ASSERT_EFI_ERROR (Status);}//析构
VOID
EFIAPI
ProcessLibraryDestructorList (IN EFI_HANDLE        ImageHandle,IN EFI_SYSTEM_TABLE  *SystemTable)
{}//入口
EFI_STATUS
EFIAPI
ProcessModuleEntryPointList (IN EFI_HANDLE        ImageHandle,IN EFI_SYSTEM_TABLE  *SystemTable){//工程入口函数return UefiMain (ImageHandle, SystemTable);
}...省略

gBS指向启动服务表,gST指向系统表(System Table),gImageHandle指向正在执行的驱动或者服务程序,gRT指向运行时服务表,这几个全局变量在开发应用程序和驱动程序是会经常用到。使用gBS、gST、gImageHandle钱需加 #include <Library/UefiBootServicesTableLib.h>。使用gRT之前需加 #include <Library/UefiRuntimeServicesTableLib.h>

与构造函数相似,AutoGen会在ProcessLibraryDestructorList调用相应的析构函数。在这里里面是空的,是因为UefiBootServiceTableLib、UefiRuntimeServicesTableLib、UefiLib这三个Library都没有析构函数。

最后在ProcessModuleEntryPointList中调用应用程序工程模块的真正入口函数UefiMain

回顾下标准应用程序工程模块的调用过程:
LoadImage -> StartImage -> _ModuleEntryPoint -> ProcessModuleEntryPointList -> UefiMain

本文来源:
《UEFI原理与编程》戴正华著

UEFI shell - 标准应用程序的编译和加载过程相关推荐

  1. 程序的编译和链接过程

    一.虚拟机.linux简介 简单介绍一下虚拟机还有就是各种操作系统,比如centos,Ubuntu 操作系统:linux(centos.Ubuntu.redhat),Android,Windows(x ...

  2. erlang 动态编译和加载遇到的问题。

    erlang具有动态加载代码的能力.在开发过程中一般都让代码自动编译加载,这样开发起来很爽. 最有名项目应该是sync了地址:https://github.com/rustyio/sync 最近在用r ...

  3. ELF文件的格式和加载过程

    (一) ELF 文件的格式       ELF 文件类型 (1) 可重定位文件( .o 目标文件) : 用于链接创建可执行文件或 so 文件 (2) 可执行文件                     ...

  4. 52.一文带你理解ARM程序的编译及执行过程

    一.我们的的程序是如何被芯片识别的? 有时我们会想,我们使用c语言或者更高级的语言写好代码,这些类似英文字母的东西芯片是怎么识别并且按照我们的想法执行的? 上图是一个ARM芯片基本的硬件组成,我们的程 ...

  5. ofbiz下的ajax,ofbiz中的ofbiz-component.xml和加载过程

    在ofbiz中,有一个非常重要的配置文件ofbiz-component.xml,这个文件告诉ofbiz需要加载那些容器和类,在start.properties中有如下定义//ofbiz容器加载类,用来 ...

  6. Chipsec UEFI Shell启动盘的制作与使用,让你可以在所有平台轻松使用Chipsec

    为什么要制作Chipsec UEFI Shell启动盘 Chipsec的编译以及驱动的构建对系统环境具有要求,并且在执行过程中也会出现这样那样的问题.但是如果使用UEFI Shell启动盘就可以再任何 ...

  7. gcc编译c文件_Linux下C语言程序的编译过程

    Linux下C语言程序的编译过程 使用gcc编译程序时,编译工程分为4个阶段: (1)预处理:(Pre-Processing) (2)编译:(Compiling) (3)汇编:(Assembling) ...

  8. 一个C程序的编译过程(Linux环境下Gcc)

    一 以下是C程序一般的编译过程: 从图中看到: 将编写的一个c程序(源代码 )转换成可以在硬件上运行的程序(可执行代码 ),需要进行编译阶段  和链接这两个阶段. 其中, 1.  编译阶段先通过&qu ...

  9. Linux下C语言程序的编译过程

    使用gcc编译程序时,编译工程分为4个阶段: (1)预处理:(Pre-Processing) (2)编译:(Compiling) (3)汇编:(Assembling) (4)链接:(linking) ...

  10. C/C++的编译和链接过程

    目录 从源文件生成可执行文件(书中第2章) 1.Preprocessing预处理--预处理器cpp 2.Compilation编译--编译器cll ps:vs中优化选项设置 3.Assembly汇编- ...

最新文章

  1. linux与linux的连接,linux 硬链接与软连接
  2. 大数据平台容量评估_大数据平台
  3. leetcood学习笔记-58-最后一个单词的长度
  4. java 数据库 事务 只读_java – odd SQLException – 无法检索转换只读状态服务器
  5. java里正数和负数_Java程序检查数字是正数还是负数
  6. 057 Java中kafka的Producer程序实现
  7. C语言形参和实参以及C#中的ref
  8. 百度地图之添加控件——比例尺、缩略图、平移缩放
  9. 区分Collection、Collector和collect Collectors类的静态工厂方法
  10. IT人的算法书单:挖掘程序的灵魂
  11. 银行卡Bin和Logo
  12. linux基础之系统安装
  13. android金钱符号变形,使用¥(一个中文字宽)还是¥(半个中文字宽)?
  14. 安装包百度云网盘链接
  15. java递归查询数据库树
  16. linux下ms安装教程,MS在linux的安装过程.doc
  17. git如何撤销某次提交记录
  18. Cannot open D:\Anaconda3\Scripts\pip-script.py 解决
  19. 人工蜂群算法python_python实现人工蜂群算法
  20. sqoop的入门使用

热门文章

  1. python实战演练二:抓取我自己csdm博客信息的标题和文章链接,并存入文件夹《列表存入数据到txt》
  2. 什么是人工神经网络模型,人工神经网络模型定义
  3. Zabbix Server 5.2安装教程
  4. 数字信号处理_巴特沃斯低通滤波器实验
  5. CMDN创新应用推荐:泊车伴侣Parkbud
  6. Houdini函数表达式
  7. 将APP发布到各大官方网站的方法,如华为、360手机助手、小米等
  8. 苹果怎么解ID锁?苹果ID锁解锁办法汇总
  9. python 趋势跟踪算法_Dual Thrust 区间突破策略 Python 版
  10. 物联网时代即将到来,LED显示屏内容显示安全尤为重要