UEFI原理與編程(二):UEFI工程模塊文件-標準應用程序工程模塊

UEFI 工程模塊文件-標準應用程序工程模塊

前言

  在EDK2環境下編程之前,先介紹EDK2的兩個概念模塊(Module)和包(Package).
  “包”是一組模塊及平臺描述文件(.dsc文件)、包聲明文件(.dec文件)則、組成的集合,多在以*pkg命名的文件夾中,一般也稱這樣的文件夾爲一個包。
  模塊是UEFI系統的一個特色。模塊(可執行文件,即.efi文件)像插件一樣可以動態地加載到UEFI內核中。對應到源文件,EDK2中的每個工程模塊由元數據文件(.inf)和源文件(有些情況也可以包含.efi文件)組成。
主要介紹3種應用程序模塊、UEFI驅動模塊和庫模塊。

一、標準應用程序工程模塊

  標準引用程序工程模塊是其它應用程序模塊的基礎,也是UEFI中常見的一種應用程序模塊。每個工程模塊由兩部分組成:工程文件和源文件。源文件包括C/C++文件、.asm彙編文件,也可以包括.uni(字符串資源文件)和.vrf(窗體資源文件)等資源文件。

1.源文件

示例程序:

//hello.c
#include<Uefi.h>
EFI_STATUS UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable)
{
    SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Hello man,\n welcome to UEFI world\n");
    return EFI_SUCCESS;
}
  • 頭文件:所有的標準應用程序工程模塊的源文件的頭文件都要包含Uefi.h。Uefi 定義了UEFI基本數據類型和核心數據結構。
  • 入口函數:UEFI標準應用程序的入口函數通常是UefiMain,它是約定成俗的函數,它是可以在.inf文件中指定。它的函數簽名(返回值類型和參數列表類型)是不能變化的。
    • 入口函數的返回值類型是EFI_STATUS
      • 在UEFI程序中基本所有的返回值類型都是EFI_STATUS。它的本質是無符號長整數
      • 最高位爲1時其值爲錯誤代碼,爲0時表示非錯誤值。通過宏EFI_ERROR(Status)可以判斷返回值Status時候爲錯誤代碼。若Status爲錯誤代碼EFI_ERROR(Status)返回值爲真,否則爲假。
      • EFI_SUCCESS爲預定義常量,其值爲0,表示沒有錯誤的狀態值和返回值。
  • 入口函數參數 ImageHandleSystenTable

    • .efi文件(UEFI應用程序或UEFI驅動程序)加載到內存後生成的對象成爲Image(映像)。ImageHandleImage的句柄,作爲模塊入口函數的參數,它表示模塊自身加載到內存後生成的Image對象 。
    • SystemTable是程序同UEFI內核交互的橋樑,通過它可以獲得UEFI提供的各種服務(BT/RT),SystemTable是UEFI內核的一個全局結構體。

    向標準輸出設備打印字符串是通過SystemTableConOut提供的OutputString服務完成的。ConOutEFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的一個實例,而EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL主要功能是控制字符輸出設備。OutputString服務的第一個參數是指向EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的一個實例即:ConOut。第二個參數是Unicode字符串。簡單講就是SystemTable->ConOut->OutputString服務將字符串L”Hello World”打印到SystemTable->ConOut所控制的字符串輸出設備。

2.工程文件

  工程模塊分爲很多塊,每個塊以“[快名]”開頭,它必須單獨佔一行。有些塊是所有工程文件都必需的塊,這些塊包括[Defines]、[Sources]、[Packages]和[LibraryClass]。
詳細的工程模塊如下表:
這裏寫圖片描述

  • [Defines]塊
    [Defines]塊用於定義模塊的屬性和其它變量,塊內定義的變量可以被其它塊引用。

    • 屬性定義的語法
      屬性名 = 屬性值
    • 塊內必須屬性
      • INF_VERSION:INF標準版本號。EDK2的build會檢查INF_VERSION 的值並根據這個值解釋.inf文件。設置爲0x00010006或0x00010005。
      • BASE_NAME:模塊名字符串,不能包含空格。它通常也是輸出文件的名字。
      • FILE_GUID:每個工程文件必須有一個 8-4-4-4-12格式的GUID用於生成固件。(每一位數十六進制 0-F)
      • VERSION_STRING:模塊的版本號,一般設置爲1.0,根據自己寫的模塊版本設定即可。
      • MODULE_TYPE:定義模塊的模塊類型,對於標準應用模塊,設爲UEFI_APPLICATION.
      • ENTRY_POINT:定義模塊的入口函數,根據在源文件中的入口函數填寫。一般是UefiMain。
[Defines]
INF_VERSION     = 0x00010006
BASE_NAME       = HelloWorld
FILE_GUID       = 4ea97c46-7491-4dfd-b442-747010f3ce5f
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 1.0
ENTRY_POINT     = UefiMain
  • [Sources]塊
    用於列出模塊的所有源文件和資源文件。
    • 語法
      塊內每一行表示一個文件,文件使用相對工程文件的路徑。
    • 體系結構相關塊
      可以在使用Sources.$(Arch),其中$(Arch)是表示本塊的體系結構,可以是IA32, X64, IPF, EBC, ARM中一個。這個的作用是不同的體系結構可能包含的源文件或資源文件不同,如果都寫進[Sources]可能有問題,但是可以列出對應的[Sources.$(Arch)],然後根據編譯時標識設置,[Sources]都會被編譯,[Sources.$(Arch)]中和標識相符的纔會被編譯。
    • 編譯工具鏈相關的源文件
      有是文件後跟工具鏈的符號表示只有在該工具鏈編譯器編譯時有效。
      • MSFT : Visual Stdio
      • INTEL : ICC編譯器
      • RVCT : ARM編譯器
//體系結構相關塊示例
[Sources]
Common.c
[Sources.IA32]
Cpu32
[Sources.X64]
Cpu64
//編譯工具鏈相關的源文件示例
[Sources]
TimerWin.c | MSFT
TimerLinux.c | GCC
  • [Packages]塊
    [Packages]列出本模塊引用到的所有包的聲明(.dec)文件。
    • 語法
      [Packages]塊內每一行列出一個文件,文件使用相對於EDK2根目錄的路徑。若[Sources]列出了源文件,則[Packages]塊必須列出MdePkg/MdePkg.dec,並將其放在本塊首行。
  • [LibraryClasses]
    [LibraryClasses]塊列出本模塊要連接的庫模塊。
    • 語法
      塊內每一行聲明一個要連接的庫(庫的定義在.dsc文件中)
    • 常用庫
      應用程序工程模塊必須連接UefiApplicationEntryPoint庫,驅動模塊必須連接UefiDriverEntryPoint庫。

非必須塊(如果有用到,則需要寫出)

  • [Protocols]塊
    [Protocol]列出的模塊中使用的Protocol,實際上是Protocol對應的GUID,如果未使用則爲空。
  • [BuildOptions]塊
    • 語法
      [BuildOptions]
      [編譯器家族]:[$(Target)][TOOL_CHAIN_TAG][$(Arch)]_[CC|DLINK]_FLAGS[=|==]選項
      • 編譯器家族:MSFT、INTEL、GCC、RVCT。
      • Target:DEBUG、RELEASE、*(對前兩個都有效)。
      • TOOL_CHAIN_TAG編譯器名字,定義在Conf\tools_def.txt文件中,與定義編譯器名字:VS2003,VS2005,VS2008,VS2010,GCC44,GCC45,GCC46,CYGGCC,ICC等 ,*表示對指定家族的編譯器都有效。
      • Arch是體系結構,與前述相同,可以是IA32, X64, IPF, EBC, ARM中一個,* 對所有體系結構有效。
      • CC表示編譯選項,DLINK表示連接選項
      • =表示選項附加到默認選項後面,==表示僅使用所定義的選項,棄用默認選項
      • =,==後面接選項

注:
這是個很有用的選項,我們寫正常C程序時一些無關緊要的警告在EDK2編譯模塊文件時會將它是做錯誤。所以可以使用下面的[BuildOptions]可以避免將這些警告堪稱錯誤。

[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /w

下面是HelloWorld的.inf文件

// HelloWorld.inf
[Defines]
INF_VERSION     = 0x00010006
BASE_NAME       = HelloWorld
FILE_GUID       = 4ea97c46-7491-4dfd-b442-747010f3ce5f
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 1.0
ENTRY_POINT     = UefiMain

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[BuildOptions]
MSFT:*_*_*_CC_FLAGS = /w

三、編譯運行

1. 添加工程文件

將工程文件即HelloWorld.inf 添加到NT32Pkg.dsc的[Components]部分。

2. 加載EDK2環境

打開MS-DOS(cmd),進入到EDK2的根目錄,用edksetup.bat加載EDK2環境。

3. 輸入

build -p Nt32Pkg\Nt32Pkg.dsc -m [helloworld.inf相對於EDK2根目錄的相對路徑名] -a IA32 (64位用X64)

4. build 信息

build 成功後畫面如下:
這裏寫圖片描述
輸出內容裏也會指明文件輸出的路徑。

5. 運行

在MS-DOS輸入 build run 進入UEFI模擬環境,運行前面目錄下的HelloWorld.efi文件。
注:可以輸入fs0: 快速進入/EDK2/Build/NT32IA32/DEBUG_VS2008/IA32/

6. 運行結果

這裏寫圖片描述

四、標準應用程序加載過程

編譯過程:
  1. HelloWorld.c 首先被編譯成目標文件 HelloWorld.obj
  2. 連接器將目標文件HelloWorld.c 和其它庫連接成HelloWorld.dll。
  3. GenFw 工具將HelloWorld.dll 轉化成 HelloWorld.efi。
上述過程由 build 命令自動完成,連接器在生成HelloWorld.dll時使用了/dll/entry:_ModuleEntryPoint。.efi是遵循了PE32格式的二進制文件,_ModuleEntryPoint便是這個二進制文件的入口函數。下面探討應用程序加載過程,主要看_ModuleEntryPoint和源文件中入口函數UefiMain的關係。

1. 將HelloWorld.efi 文件加載到內存

  當shell中執行HelloWorld.efi時,shell首先用gBS->LoadImage()將HelloWorld.efi文件加載到內存生成Image對象,然後調用gBS->StartImag(Image)啓動這個Image對象。gBS->StartImage()是一個函數指針,它實際指向的是CoreStartImage()

2. 進入映像入口函數

  CoreStartImage()的主要作用是調用映像入口函數,在gBS->StartImage 的核心是Image->EntryPoint(···),它就是程序映像的入口函數,對應程序來說就是_ModuleEntryPoint 函數。進入 _ModuleEntryPoint 後,控制權才轉交給應用程序(HelloWorld.efi)。
  _ModuleEntryPoint主要處理三件事:
  1. 初始化:初始化函數ProcessLibraryConstructorList中調用一系列構造函數
  2. 調用本模塊的入口函數 : ProcessModuleEntryPointList 中調用的是工程模塊定義的入口函數
  3. 析構:ProcessLibraryDestructorList 中調用一系列析構函數。
這三個對應的函數AutoGen.h,AutoGen.c中。

3. 進入模塊入口函數

  在ProcessModuleEntryPointList函數中調用了工程模塊的真正入口函數UefiMain。
  

五、總結

  標準應用程序模塊是其它應用程序模塊的基礎,需要對它熟悉使用掌握,後續會接着介紹其它類型的工程模塊。
  另外,此篇文章後的demo是在已經安裝了EDK2環境基礎上編譯運行的。如果還沒有安裝,可以參考:UEFI原理與編程(一):環境搭建

參考資料

<1>《UEFI原理與編程》戴正華 著
<2> UEFI Spec2_6
<3> 百度百科

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章