UEFI原理与编程第二章学习-UEFI标准应用工程模块文件介绍及编译流程

标准应用程序工程模块

   标准应用程序工程模块是其他应用程序工程模块的基础,也是UEFI中常见的一种应用程序工程模块。每个工程模块分为两部分:工程文件和源文件,标准应用程序工程模块也不例外。其中,源文件包括:C/C++文件、.asm汇编文件,也可以包括.uni(字符串资源文件)和 .vfr(窗口资源文件)等资源文件。

   一个简单的标准应用程序工程模块应该包含一个C程序模块(本例中为Main.c)以及一个工程文件(Main.inf),接下来逐部分编写一个简单的标准应用程序的各个模块。

  • 1、入口函数
    //简单的标准应用程序
    
    #include<Uefi,h>
    EFI_STATUS UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
    {
    	SystemTable->ConOut->OutputString(SystemTable->ConOut, L"HelloWorld\n");
    	return EFI_SUCCESS;
    }
    

    一般来说标准应用程序至少要包含以下两个部分:

    1. 头文件 :所有的UEFI程序都要包含头文件Uefi.h。Uefi.h定义了UEFI基本数据类型以及核心数据结构。

    2. 入口文件 :UEFI标准应用程序入口函数可以由开发者自行指定,但通常命名为UefiMain。入口函数由工程文件UefiMain.inf指定。虽然入口函数的函数名可以变化,但其函数签名(即返回值类型和参数列表类型)不能变化。

      入口函数返回值与参数讲解:

      1. 入口函数的返回值类型是EFI_STATUS。
        • 在UEFI程序中基本所有返回值类型都是EFI_STATUS。EFI_STATUS本质上是无符号程序长整数型变量。
        • 在最高位为1时值为错误代码,为0时表示没有错误的状态值或返回值。通过宏EFI_ERROR(Status)可以判断返回值Status是否为错误码。若Status是错误吗,则EFI_ERROR的返回值为TRUE,否则为False。
        • EFI_SUCCESS为预定义常量,其值为0,表示没有错误的状态或是返回值。
      2. 入口函数的参数是ImageHandle和SystemTable。
        • .efi文件(UEFI应用程序或UEFI驱动程序)加载到内存后生成的对象称为Image映像。ImageHandle是Image对象的句柄,作为模块入口函数,它表示模块自身加载到内存后生产的Image对象。
        • SystemTable是程序同UEFI内核交互的桥梁,程序通过SystemTable可以获得UEFI提供的各种服务,如启动(BS)服务和运行时(RT)服务。SystemTable时UEFI内核中的一个全局结构体。

         向标准输出设备打印字符串是通过SystemTable的ConOut提供的服务完成的。ConOut是EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的一个实例。而EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的主要功能是控制字符输出设备。向输出设备打印字符串是通过ConOut提供的OutputString服务完成的。该服务(函数)第一个参数是This指针,指向EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL实列(此处为ConOut)本身;第二个参数是Unicode字符串。这条打印语句的流程是通过SystemTable->ConOut->OutputString五福将字符串L"HelloWorld"打印到SystemTable->ConOut所控制的字符输出设备。

  • 2、工程文件

       在编写Main.c文件的同时,需要编写.inf(Module Information File)文件。.inf文件时模块的工程文件,其作用类似于Visual Studio的.proj文件,用于指导EDK2编译工具自动编译模块。

       工程文件分为很多个块,每个块以“[块名]”开头,“[块名]”必须单独占一行。有的块是所有工程文件都必需的块,还有另一部分的块仅在用的时候需要编写。

    必须块块描述
    [Defines]定义本模块的属性变量及其他变量,这些变量可在其他块中引用
    [Sources]列出本模块的所有源文件及资源文件
    [Packages]列出本模块引用到的所有包的包声明文件
    [LibraryClasses]列出本模块要链接的库模块
    非必须块块描述
    [Protocols]列出本模块用到的Protocol
    [Guids]列出本模块用到的GUID
    [Pcd]列出本模块用到的Pcd变量,这些Pcd变量可被整个UEFI系统访问
    [PcdEx]列出本模块用到的Pcd变量,这些Pcd变量可被整个UEFI系统访问
    [FixedPcd]列出本模块用到的Pcd编译期常量
    [FeaturePcd]用于列出本模块用到的Pcd常量
    [PatchPcd]写出的Pcd变量进本模块可以使用
    [BuildOptions]指定编译和链接选项
    1. [Defines] 块:

      [Defines]块用于定义模块的属性和其他变量,块内定义的变量可被其他块引用。

      (1)属性定义的语法

      属性名=属性值

      (2)属性

      块内必须定义的属性包括:

      • INF_VERSION: INF标准的版本号。EDK2的build会检查INF_VERSION的值,并根据这个值解释.inf文件。通常将INF_VERSION的值设置为0x00010005,其中前半部分为主版本号,后半部分为次版本号。
      • BASE_NAME:模块名字符串,不能包含空格。它通常也是输出文件的名字。例如,BASE_NAME=UefiMain,则最终生成的文件为UefiMain.efi。
      • FILE_GUID:每个工程文件必须有一个8-4-4-4-12格式的GUID,用于生产固件。例如,FILE_GUID=6987936E-GUID-UEFI-AE86-012345678987。
      • VERSION_STRING:模块的版本号字符串。例如,可以设置为 VERSION_STRING=1.0。
      • MODULE_TYPE:定义模板的模板类型,可以是SEC、PEI_CORE、PEIM、DXE_CORE、DEX_SAL_DRIVER、UEFI_APPLICATION、BASE中的一个。对标准应用程序工程模块来说,MODULE_TYPE的值为UEFI_APPLICATION。
      • ENTRY_POINT:定义模块的入口函数。模块的入口函数就是通过设置ENTRY_POINT的值进行配置的。

      [Defines]块示例代码:

      [Defines]
      	INF_VERSION = 0x00010005
      	BASE_NAME = UefiMain
      	FILE_GUID = 6987936E-GUID-UEFI-AE86-012345678987
      	MODULE_TYPE = UEFI_APPLICATION
      	VERSION_STRING = 1.0
      	ENTRY_POINT = UefiMain
      
    2. [Sources]

      (1)语法

      块内每一行代表一个文件,文件使用相对路径,根路径是工程文件所在的目录。这次编写的简单标准应用程序工程模块仅含有一个源文件。 [Sources]块如下所示:

      [Sources.$(Arch)]
      	UefiMain.c
      

      (2)体系结构相关块

      .$(Arch)是可选项,可以是IA32、X64、IPF、EBC、ARM中的一个,表示本模块适用的体系结构。[Sources]块适用于任何体系结构。例如,编译32位模块时(即,在build命令选项中选择使用 -a IA32选项),工程将包含[Sources]和[Sources.IA32]中的源文件,编译64位模块时,工程将包含[Sources]和[Sources.X64]中的源文件。

      (3)示例

      示例包含了三个块:[Sources]块、[Sources.IA32]块、[Sources.X64]块。在编译32位的模块时,模块包含[Sources.IA32]中的Cpu32.c文件和[Sources]中的Common.c文件;在编译64位系统时,模块包含文件[Sources.X64]中的Cpu64.c和[Sources]中的Common.c。

      [Sources]块、[Sources.IA32]块、[Sources.X64]块示例:

      [Sources]
      	common.c
      [Sources.IA32]
      	Cpu32.c
      [Sources.X64]
      	Cpu64.c
      

      (4)编译工具链相关的源文件

      有时文件名后面会有一个“|”符号,该符号后面会跟工具链名字,如下例所示:

      工具链相关的源文件:

      [Sources]
      	TimerWin,c | MSFT
      	TimerLinux.c | GCC
      

      这表示TimerWin.c仅在使用 Visual Studio 编译器时有效,TimerLinux.c仅在使用GCC编译器时有效。处理MSFT和GCC外,EDK2还定义了INTEL和RVCT两种工具链。INTEL工具链是ICC编译器或Intel EFI 字节码编译器;RVCT是ARM编译器。

    3. [Packages]

      [Packages]块列出了本模块引用到的所有包的包声明文件(类似C++开头的import),即.dec文件。

      (1)[Packages]语法

      [packages]块内每一行列出一个文件,文件使用相对路径,相对路径的根路径位EDK2的根目录。如果[Sources]块内列出了源文件,则在[Packages]块内必须列出MdePkg/MdePkg。.dec,并将其放在本块的首行(详见示例)。

      (2)示例

      由于编写的简单标准应用工程模块示例( [Sources] 块)中, UefiMain.c仅引用了MdePkg中的头文件Uefi.h,因此[Packages]仅列出MdePkg/MdePkg.dec即可,示例如下:

      [Packages]
      	MdePkg/MdePkg.dec
      
    4. [LibraryClasses]

      [LibraryClasses]块的功能是列出本模块要链接的库模块。

      (1)语法

      块内每一行声明一个要链接的库(库定义在包的 .dsc文件中),语法如下:

      [LibraryClasses]

      ​ 库名称

      (2)常用库

      应用程序工程模块必须链接UefiApplicationEntryPoint库;驱动模块必须链接UefiDriverEntryPoint库。

      (3)示例

      这次尝试编写的简单应用程序工程模块中,UefiMain.c文件的UefiMain函数没有调用除UefiApplicationEntryPoint以外的其他库函数,因此只需要在[LibraryClasses]块中列出UefiApplicationEntryPoint即可,示例如下:

      [LibraryClasses]
      	UefiApplicationEntryPoint
      
    5. [Protocols]块

      [Protocols]块中列出的是在模块中使用到的Protocol对应的GUID。如果模块中未使用任何Protocol,则[Protocols]块为空。

      (1)语法

      块内的每一行声明一个在本模块中引用的Protocol。语法格式如下:

      [LibraryClasses]

      ​ Protocol 的 GUID

      (2)示例

      如果在程序中使用了某个Protocol的GUID。例如,源程序中使用了类似代码:

      Status = gBS->LocateProtocol ( &gEfiHiiDatabaseProtocolGuid, NULL, (VOID**) &HiiDatabsa);
      

      则在[Protocols]块中必须要列出gEfiHiiDatabaseProtocolGuid,示例如下:

      [LibraryClasses]
      	gEfiHiiDatabaseProtocolGuid
      
    6. [BuildOptions]块

      [BuildOptions]块制定了本模块的编译和链接选项。

      (1)语法

      [BuildOptions]
      [编译器家族]: [$(Target)]_[TOOL_CHAIN_TAG]_[$(Arch)]_[CC|DLINK]_FLAGS [=|==] [选项]
      

      各选项说明如下:

      • [编译器家族]:可以是MSFT(Visual Studio编译器家族)、INTEL(Intel编译器家族)、GCC(GCC编译器家族)和RVCT(ARM编译器家族)中的某一个。
      • [Target]:值可以是DEBUG、RELEASE和*中的任意一个,*为通配符,表示对DEBUG和RELEASE都有效。
      • [TOOL_CHAIN_TAG]:是编译器的名字。编译器的名字在Conf\target.txt文件中的第60行。预定的编译器名字可以是:VS系列(VS2010、VS2015、VS2017等)、GCC系列(GCC45、GCC46等)、可以是CYGGCC或是ICC等,还可以使用通配符*表示对所有编译家族内的所有编译器都适用。
      • [Arch]:是体系结构,可以IA32、X64、IPF、EBC、ARM或*,其中*表示对所有体系结构都有效。
      • [CC|DLINK]:CC表示编译选项。DLINK表示链接选项。
      • [=|==]:=表示将选项附加到默认选项后;==表示仅使用所定义的选项,弃用默认选项。
      • [选项]:此项为编译选项或连接选项。

      (2)示例

      示例6.1 使用’='的[BuildOptions]块,编写用Visual Studio编译器进行编译,且在编译时添加/wd4804编译选项,连接时添加/BASE:0选项。

      [BuildOptions]
      	MSFT:*_*_*_CC_FLAGS = /wd4804
      	MSFT:*_*_*_DLINK_FLAGS = /BASE:0
      

      示例6.2 使用"=="的[BuildOptions]块,编写是使用Visual Studio编译器进行编译32位的DEBUG版本仅使用指定的编译选项,并忽略所有默认的编译选项。

      [BuildOptions]
      MSTF:DEBUG_VS2010_IA32_CC_FLAGES == /nologo /c /WX /GS- /W4 /Gs32768 /DUNICODE /Olib2 /GL /EHs-c- /GR- /GY /Zi /Gm /D EFI_SPECIFCATION_VERSION = 0x0002000A /D TIANO_RELEASE_VERSION = 0x00080006 /FAs /Oi-
      

    等上述代码块都编译好后,将其放在一起,组成完整的标准应用程序工程文件,示例如下:

    示例,标准应用程序HelloWorld的完整工程文件

    [Defines]
    	INF_VERSION = 0x00010005
    	BASE_NAME = UefiMain
    	FILE_GUID = 6987936E-GUID-UEFI-AE86-012345678987
    	MODULE_TYPE = UEFI_APPLICATION
    	VERSION_STRING = 1.0
    	ENTRY_POINT = UEFIMAIN
    	
    [Sources]
    	main.c
    	
    [Packages]
    	MdePkg/MdePkg.dec
    	
    [LibraryClasses]
    	UefiApplicationEntryPoint
    	UefiLib
    	
    
  • 3、编译和运行

    源文件和工程都编写完成后,将UefiMain.inf的相对(uefi)路径添加到Nt32Pkg.dsc或UnixPkg.dsc的[Components]部分,示例代码如下(uefi在EDK2文件夹中):

    [Components]
    uefi/book/infs/UefiMain.inf
    

    添加完成后打开BaseTools下的build工具进行编译即可

    Windows下执行下列命令进行编译:

    C:\EDK2>edksetup.bat --nt32
    C:\EDK2>build -p Nt32PkgNt32Pkg.dsc -a (IA32||X64)
    

    Linux下执行下例命令进行编译:

    $>source ./edksetup.sh BaseTools
    $>source -p Unixpkg/UnixPkg.dsc -a IA32
    
  • ※4、标准应用程序的加载过程
    1. 应用程序被编译成 .efi 文件

      1)UefiMain.c首先被编译成目标文件UefiMain.obj。

      2)连接器将目标文件UefiMain.obj和其他库连接成UefiMain.dll。

      3)GenFw工具将UefiMain.dll转换成UefiMain.efi。

      以上整个过程由build命令自动完成,2)、3)阶段执行的命令如下:

      C文件转Uefi文件的几个步骤1

      由图可以看出,连接器在生产UefiMain.dll文件时使用了/dll/entry:_ModuleEntryPoint。.efi文件是遵循PE32格式的二进制文件,_ModuleEntryPoint是这个二进制文件的入口。

    2. 将UefiMain.efi文件加载到内存

      在Shell中执行UefiMain.efi时,Shell首先用gBS->LoadImage()将UefiMain.efi文件加载到内存生成Image对象,然后调用gBS->StartImage(Image)启动这个Image。具体加载过程代码如下:

      //@file ShellPkg\Application\Shell\ShellProtocol.c
      EFI_STATUS EFIAPI InternalShellExecuteDevicrPath(
      	IN CONST EFI_HANDLE*ParentImageHandle, 
      	IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath,  //UefiMain.efi的设备路径
      	IN CONST CHAR16 *CommandLine OPTIONAL,  //应用程序所需的命令行参数
      	IN CONST CHAR16 **Environmrnt OPTIONAL,  //UEFI环境变量
      	OUT EFI_STATUS *StatusCode OPTIONALa //程序UefiMain.efi的返回值
      	)
      {
      	//定义参数:
      	EFI_STATUS Status;
      	EFI_HANDLE NewHandle;
      	EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
      	LIST_ENTRY OrigEnvs;
      	EFI_SHELL_PARAMETERS_PROTOCOL ShellParamsProtocol;
      	...
              
      	
      	/*
      	第一步:将UefiMain.efi文件加载到内存,生成Image对象,NewHandle为其句柄
      		句柄:
      		1、特殊的智能指针(当一个应用程序要引用其他系统管理的内存块或对象时)
      		2、Windows编程的基础。句柄指的是使用一个唯一的4字节型整数值,来标识应用程序中
      		的不同对象和同类中的不同示例。应用程序能通过句柄访问相应对象的信息,但这种句柄
      		不是指针,应用程序不能通过句柄直接阅读文件中的信息。句柄是Windows系统用来标识
      		应用程序中建立的或是使用的资源的唯一整数。
      		
      	使用gBS->LoadImage()函数,将加载结果返回给Status参数,同时改变NewHand的值。
      	使用EFI_ERROR()函数判断是否加载成功。
      		
      	*/
      	Status = gBS->LoadImage(
      		FALSE,
      		*ParentImageHandle,
      		(EFI_DEVICE_PATH_PROTOCOL*)DevicePath,
      		NULL,
      		0,
      		&NewHandle);
      		
      	if (EFI_ERROP(Status))
      	{
      		if (NewHandle != NULL)
      			gBS->UnloadImage(NewHandle); //这里为什么要UnloadImage?
      		return (Status);
      	}
      	
      	/*
      	第二步:获取命令行参数,并将获取的命令行参数交给UefiMain.efi的Image对象,即句柄NewHandle
      	
      	gBS->OpenProtocol()函数传入所得的信息和NewHandle。
      	
      	*/
      	Status = gBS->OpenProtocol(
      		NewHandle,
      		&gEfiLoadedImageProtocolGuid,
      		(VOID**) &LoadedImage,
      		gImageHandle,
      		NULL,
      		EFI_OPEN_PROTOCOL_GET_PROTOCOL);
      		
      	if(!EFI_ERROR(Status))
      	{
      		ASSERT(LoadedImage->LoadOptionsSize == 0;
      		if (NULL != CommandLine)
      		{
      			LoadedImage->LoadOptionsSize = (UINT32)StrSize(CommandLine));
      			LoadedImage->LoadOptions = (VOID*)CommandLine;
      		}
      	}
      	...
      	
      	/*
      	第三步:启动加载的Image
      	
      	gBS->StartImage()函数根据所传入的NewHandle启动相应的Image。
      	
      	*/
      	if (!EFI_ERROR(Status))
      	{
      		if (StatusCode != UNLL)
      			*StatusCode = gBS->StartImage(NewHandle, NULL, NULL);
      		else
      			Status = gBS->StartImage(NewHandle, NULL, NULL);
      	}
      	...
      	
      	//退出应用程序后清理资源
      }
      
      

      加载应用程序中最重要的一步就是gBS->StartImage(NewHandle, NULL, NULL),程序通过这一步启动所加载的Image。StartImage的主要租用作用是找出可执行程序映像(Image)的入口函数并执行gBS->StartImage是函数指针,实际指向CoreStartImage函数。

    3. 进入映像函数的入口函数

      CoreStartImage的主要作用是调用映像的入口函数。具体代码清单:

      //@file MdeModulePkg\Core\Dxe\Image\Image.c
      EFI_STATUS EFIAPI CoreStartImage (
      	IN EFI_HANDLE ImageHandle, 
      	OUT NINTN *ExitDataSize, 
      	OUT CHAR16 **ExitData OPTIONDAL
      	)
      {
      	//定义参数
      	EFI_STATUS Status;
      	LOADED_IMAGE_PRIVATE_DATA *Image;
      	LOADED_IMAGE_PRIVATE_DATA *LastImage;
      	UINT64 HandleDatabaseKey;
      	UINTN SetJumpFlag;
      	UINT64 Tick;
      	EFI_HANDLE Handle;
      	 
      	//设置LongJump,用于退出此程序
      	Image->JumpBuffer = AllocatePool (sizeof (BASE_LIBRARY_JUMP_BUFFER) + BASE_LIBRARY_JUMP_BUFFER_ALLGNMENT);
          if(Image->JumpBuffer == NULL)
              return EFI_OUT_OF_RESOURCES;
          Image->JumpContext = ALIGN_POINTER (Image->JumpBuffer, BASE_LIBRARY_JUMP_BUFFER_ALIGNMENT);
          SetJumpFlage = SetJump(Image->JumpContest);
          
          //首次调用SetJump()返回0。通过 LongJump(Image->JumpCOntext) 跳转到此处时,返回非零值。
          if (0 == SetJumpFlag)
          {
              //调用Image的入口函数
              Image->Started = TRUE;
              Image->Status = Image->ENtryPoint(ImageHandle,Image->Info.SystemTable);
              //设置Image执行后的状态,然后通过LongJump跳到应用程序退出点。
              CoreExit(ImageHandle, Image->Status, 0, NULL);
          }
          /*
          此处是应用程序退出点。
          程序通过LongJump跳转到此处,然后根据Image->Status进行错误处理。
          */
      	...
      }
      
      

      在gBS->StartImage中,SetJump/LongJump为应用程序的执行提供了一种错误处理机制,执行流程如图所示:

      gBS.StartImage执行流程

      gBS->StartImage的核心是Image->EntryPoint(···),它是程序映像(Image)的入口函数,应用程序的入口函数是_ModuleEntryPoint。进入_ModuleEntryPoint后,控制权才转交给应用程序(此例的应用程序指UefiMain.efi),_ModuleEntryPoint的代码如下:

      //@file MdePkg\UefiApplicationEntryPoint\ApplicationEntryPoint.c
      EFI_STATUS EFIAPI _ModuleEntryPoint (IN EFI_HANDLE ImageHandle, IN EFI _SYSTEM_TABLE *SystemTable )
      {
          EFI_STATUS Status;
          if (0 != _gUefiDriverRevision)
          {
              //确保系统平台的UEFI版本号大于等于ImageHandle的UEFI版本号
              if (SystemTable->Hdr.Revision < _gUefiDriverRevision)
                  return EFI_INCOMPATIBLE_VERSION;
          }
          
          //(调用)所有将被调用的库的构造函数,进行初始化。
          ProcessLibraryConstructorList(ImageHandle, SystemTable);
          
          //调用Image的入口函数
          Status = ProcessModuleEntryPointList(ImageHandle, SystemTable);
          
          //所有库的析构函数
          ProcessLibraryDestructorList (ImageHandle, SystemTable);
          
          return Status;
      }
      
      

      _ModuleEntryPoint主要处理一下三件事:

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

         这三个Process*函数是在系统执行build命令时,build命令解析模块的工程文件(即.inf文件),然后生成AutoGen.h和AutoGen.c,其中AutoGen.c中便含有这三个函数。一般而言,在.inf文件的[LibraryClass]段声明了某个库后,如果这个库由构造函数,AutoGen便会在ProcessLibraryConstructorList中加入这个库的构造函数。另外ProcessLibraryConstructorList还会自动加入启动服务(BS)和运行时服务(RT)的构造函数。

      工程模块HelloWorld的ProcessLibraryConstructorList函数代码如下:\?

      VOID EFIAPI ProcessLibraryConstructorList(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
      {
      	EFI_STATUS Status;
      	
      	//初始化全局变量gBS、gST和gImageHandle
      	Status = UefiBootServicesTableLibConstructor(ImageHanle, SystemTable);
      	ASSERT_EFI_ERROR(Status);
      	
      	//初始化全局变量gRT
      	Status = UefiRuntimeServicesTableLibConstructor (ImageHandle, SystemTable);
      	ASSERT_EFI_ERROR(Status);
      	
      	//初始化UefiLib,Print函数是在UefiLib中实现的
      	Status = UefiLibConstructor(ImageHandle, SystemTable);
      	ASSERT_EFI_ERROR(Status);
      }
      
      

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

      与构造函数相似,AutoGen会在析构函数中调用相应Library的析构函数。HelloWorld标准应用程序工程模块的析构函数ProcessLibraryDestructorList为空,这是因为UefiBootServicesTableLib、UefiRuntimeServicesTableLib、UefiLib这三个Library都没有析构函数。

      标准应用程序工程HelloWorld的析构函数ProcessLibraryDestructorList,代码如下:

      VOID EFIAPI ProcessLibraryDesturctorList (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
      {
      	
      }
      
      
    4. 进入模块入口函数

      在ProcessModuleEntryPointList中,调用了应用程序工程模块的真正入口函数UefiMain,代码如下:

      EFI_STATUS EFIAPI ProcessModuleEntryPointList(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
      {
      	return UefiMain(ImageHandle, SystemTable);
      }
      
      
    5. 总结:

      标准应用工程模块入口函数的整个调用过程为:

      ​                      ->PLCL       :运行所有构造函数

      LoadImage->StartImage->_ModuleEntryPoint->PMEP-UefiMain   :执行入口函数

                             ->PLDL       :运行所有析构函数

  • 5、总结

    本篇介绍了标准应用程序工程的模块的组成、入口函数UefiMain的结构,以及.inf文件的结构

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: UEFI(Unified Extensible Firmware Interface)是一种固件接口标准,用于用于替换传统的BIOS(Basic Input/Output System)固件,旨在提高系统启动速度、安全性和可靠性。 UEFI原理编程完整版PDF是一份详细的技术指南,提供了UEFI开发所需的全部信息,包括如何设计UEFI驱动程序、应用程序和固件,以及如何使用UEFI的各种功能。 这份PDF文件UEFI划分为四个主要模块:启动服务、管理服务、硬件抽象层和UEFI Shell。每个模块都有详细的描述和示例代码,此外还包含了UEFI的系统架构、数据结构、驱动程序与应用程序的开发方法等内容。 此外,UEFI原理编程完整版PDF还介绍UEFI与安全性、多核技术、嵌入式系统等方面的关系,并为读者提供了关键的UefiSpec和UefiDebugTools库文档。 总之,UEFI原理编程完整版PDF是一份权威的技术指南,涵盖了UEFI方方面面的内容,非常适合开发人员和工程学习和参考。 ### 回答2: UEFI(Unified Extensible Firmware Interface)是一种新型的启动编程接口,它替代了旧版的BIOS(基本输入输出系统)。UEFI原理编程可以帮助开发人员理解UEFI的工作原理编程技巧。 UEFI拥有完善的安全性和可靠性,其编程方式也十分灵活。与BIOS相比,UEFI可以支持更多的硬件平台和更大的硬盘容量,同时也可以优化启动速度和程序的执行效率。 UEFI原理编程pdf完整版可以帮助开发人员更好地了解UEFI的概念和架构,并提供丰富的代码示例,帮助开发人员快速掌握UEFI编程技能。除此之外,该书还介绍UEFI的启动流程、安全策略、UEFI应用的开发和调试等内容,对于想要深入学习UEFI编程的开发人员来说是一本很有价值的参考书。 总之,UEFI原理编程pdf完整版是一本优秀的技术参考书,其内容涵盖了UEFI的方方面面,对于想要深入了解和运用UEFI的开发人员来说具有很高的参考价值。 ### 回答3: UEFI(Unified Extensible Firmware Interface)是一种新型的系统固件接口,它为操作系统与硬件之间提供了一种标准的接口。相比传统的BIOS,UEFI可以支持更多的硬件功能,同时也更加安全和灵活。 UEFI原理主要包括以下几个方面:1、UEFI接口包括了启动管理器,硬件抽象层,运行环境和应用层等模块;2、UEFI支持模块化设计和驱动加载,可以动态地加载和卸载硬件驱动,提高了系统灵活性和安全性;3、UEFI使用GUID Partition Table(GPT)替代MBR分区表,支持更大的硬盘容量和更稳定的系统启动;4、UEFI还支持Secure Boot功能,可以防止恶意程序篡改启动软件和系统文件,提高了系统安全性。 关于UEFI编程,需要掌握以下几个核心内容:1、UEFI开发环境的搭建,需要熟悉UEFI规范以及系统固件接口的程序编写;2、UEFI应用程序的设计和开发,可以利用UEFI提供的应用程序接口(API)或者开发自定义的应用程序;3、UEFI驱动程序的编写,需要掌握UEFI驱动程序的架构和编程模型;4、UEFI启动管理器的开发,需要熟悉UEFI启动管理器的设计和实现。 总之,UEFI是一种新型的系统固件接口,可以为操作系统和硬件提供一个标准的接口,提高系统的安全性和灵活性。对于UEFI编程学习和掌握,则需要对UEFI规范和程序设计有深入的了解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值