2022年7月29日 星期五

PE/COFF header of UEFI image

參考網站:
Microsoft pe-format
Wiki Portable_Executable
osdev COFF

UEFI image都是遵循微軟所定義的 PE/COFF格式,PE/COFF的格式可以參考維基百科的這張 圖:

CC BY 4.0, Link

使用 EDK2的 CpuDxe.efi來觀察一下:

可以看到一開始的 0x40個 Byte,是 DOS Header,起始的開頭為"MZ"(0x4D 0x5A),在 EDK2 structure定義如下:

///
/// PE images can start with an optional DOS header, so if an image is run
/// under DOS it can print an error message.
///
typedef struct {
  UINT16    e_magic;    ///< Magic number.
  UINT16    e_cblp;     ///< Bytes on last page of file.
  UINT16    e_cp;       ///< Pages in file.
  UINT16    e_crlc;     ///< Relocations.
  UINT16    e_cparhdr;  ///< Size of header in paragraphs.
  UINT16    e_minalloc; ///< Minimum extra paragraphs needed.
  UINT16    e_maxalloc; ///< Maximum extra paragraphs needed.
  UINT16    e_ss;       ///< Initial (relative) SS value.
  UINT16    e_sp;       ///< Initial SP value.
  UINT16    e_csum;     ///< Checksum.
  UINT16    e_ip;       ///< Initial IP value.
  UINT16    e_cs;       ///< Initial (relative) CS value.
  UINT16    e_lfarlc;   ///< File address of relocation table.
  UINT16    e_ovno;     ///< Overlay number.
  UINT16    e_res[4];   ///< Reserved words.
  UINT16    e_oemid;    ///< OEM identifier (for e_oeminfo).
  UINT16    e_oeminfo;  ///< OEM information; e_oemid specific.
  UINT16    e_res2[10]; ///< Reserved words.
  UINT32    e_lfanew;   ///< File address of new exe header.
} EFI_IMAGE_DOS_HEADER;

而 0x3C則定義了PE Signature的位置,長度為 4 Bytes。以 CpuDxe.efi來說,是在 0x000000B8的位置。可從 0x000000B8的位置看到"PE\0\0"(0x50 0x45 0x00 0x00)的 PE Signature。

而從 0x40到 0xb7的這段空間稱為 DOS Stub,以前為了與 DOS相容而遺留下的產物,現在則直 接全部都填上 0x00。

PE Signature之後是 COFF File Header,在 EDK2中的定義如下:

///
/// COFF File Header (Object and Image).
///
typedef struct {
  UINT16    Machine;
  UINT16    NumberOfSections;
  UINT32    TimeDateStamp;
  UINT32    PointerToSymbolTable;
  UINT32    NumberOfSymbols;
  UINT16    SizeOfOptionalHeader;
  UINT16    Characteristics;
} EFI_IMAGE_FILE_HEADER;

COFF File Header長度為 20 Bytes,將其獨立拿出來看:


OffsetSizeFieldValueDescription
02Machine0x8664代表 x64系統使用。
22NumberOfSections0x0006Section Table 的數量。
44TimeDateStamp0x0
84PointerToSymbolTable0x0
124NumberOfSymbols0x0
162SizeOfOptionalHeader0x00F0Optional Header的 size。
182Characteristics0x2022IMAGE_FILE_EXECUTABLE_IMAGE +
IMAGE_FILE_LARGE_ADDRESS_AWARE +
IMAGE_FILE_DLL

緊接下來則是 Optional Header,其長度可以從 COFF File Header得知為 0xF0。

其分成 3個部分,Standard FieldsWindows Specific FieldsData Directories

Standard Fields
OffsetSizeFieldValueDescription
02Magic0x020BPE32+格式。
21MajorLinkerVersion0x0E
31MinorLinkerVersion0x1D
44SizeOfCode0x0000BAE0code section的 size。
84SizeOfInitializedData0x00001AE0initialized data section的 size。
124SizeOfUninitializedData0x0uninitialized data section的 size。
164AddressOfEntryPoint0x00001268entry point對ImageBase的相對位址。
204BaseOfCode0x000002C0code section對ImageBase的相對位址。

Windows Specific Fields (PE32+)
OffsetSizeFieldValueDescription
248ImageBase0x0
324SectionAlignment0x00000020section在記憶體中為 32 Bytes alignment。
364FileAlignment0x00000020section中的raw data為 32 Bytes alignment。
402MajorOperatingSystemVersion0x0
422MinorOperatingSystemVersion0x0
442MajorImageVersion0x0
462MinorImageVersion0x0
482MajorSubsystemVersion0x0
502MinorSubsystemVersion0x0
524Win32VersionValue0x0
564SizeOfImage0x0000DC40Image的 size,包含所有 headers。
CpuDxe.efi的 size就是0xDC40。
604SizeOfHeaders0x000002C0DOS stub、PE Header和 section headers的 size總和。
644CheckSum0x0
682Subsystem0x000B代表為 IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_ DRIVER。
702DLL Characteristics0x0
728SizeOfStackReserve0x0
808SizeOfStackCommit0x0
888SizeOfHeapReserve0x0
968SizeOfHeapCommit0x0
1044LoaderFlags0x0
1084NumberOfRvaAndSizes0x00000010Data Directories的數量。

Data Directories (PE32+)
OffsetSizeFieldValueDescription
1128Export Table0x0
1208Import Table0x0
1288Resource Table0x0
1368Exception Table0x0
1448Certificate Table0x0
1528Base Relocation Table0x0000DBC0
1608Debug0x0
1688Architecture0x0
1768Global Ptr0x0
1848TLS Table0x0
1928Load Config Table0x0
2008Bound Import0x0
2088IAT0x0
2168Delay Import Descriptor0x0
2248CLR Runtime Header0x0
2328Reserved0x0

Optional Header的下個部份則為 Section Headers,其數量可以從 COFF File Header
NumberOfSections讀出。

Section Header
OffsetSizeFieldDescription
08Name8 Bytes的 ASCII字串,代表此 section的名字。
84VirtualSize此section在記憶體中的 size,如果大於 SizeOfRawData
則後面會填上 0x0。
124VirtualAddress此 section在記憶體中相對於 ImageBase的起始位址。
164SizeOfRawDatainitialized date的 size。
204PointerToRawDatasection raw data的 offset。
244PointerToRelocationsRelocation table的 offset。
284PointerToLinenumbersLine Number table的 offset。
322NumberOfRelocationsRelocatione table的數量。
342NumberOfLinenumbersLine Number table的數量。
364Characteristics32 Bits flag。

CpuDxe.efi可以使用 xpeviewer讀出 6個 sections:


Section NameContent
.textExecutable code
.rodataRead-only initialized data
.dataInitialized data
.xdataException information
.relocImage relocations

2022年7月21日 星期四

Beyond BIOS Note - CH5 UEFI Runtime


Adding manpower to a late software project makes it later.
- Brook's Law

UEFI 提供兩種主要的 service來操作及控制系統資源:

  • Boot Services
  • 提供開機期間相關的系統服務,在 ExitBootServices()被呼叫之後就無法使用。

  • Runtime Services
  • ExitBootServices()之後還可以使用,OS 需要其來完成一些系統資源的操作。


Memory Type

UEFI在 allocate記憶體的時候,會需要指定這段記憶體的 EFI_MEMORY_TYPE,來代表其使用目 的。EFI_MEMORY_TYPE也分成只在 ExitBootServices()前可以使用及在 ExitBootService ()後也 能使用。詳細 EFI_MEMORY_TYPE使用可參考 UEFI Specification。


EFI System Table

EFI System Table 中主要有兩個服務會在 Runtime使用:

  • Runtime Services Table
  • 提供所有 Runtime Services的指標。

  • UEFI Configuration Table
  • 由 GUID/Pointer 配對組成,可以是提供給系統的 function pointer、data或 table, 像是 SMBIOS及 ACPI table的 entry point。


Time Services

Runtime Services其中還有包含 Time Services的部分,讓 OS可以不用透過直接讀取硬體的方式 來取得系統的時間資訊。相關的 function有 GetTime()、SetTime()、GetWakeupTime()和 SetWakeupTime()。


Virtual Memory Services

ExitBootServices()之後,OS會透過 SetVirtualAddressMap()提供其虛擬記憶體的資訊 來將 Runtime Services從實體記憶體定址到虛擬記憶體。ConvertPointer()提供 UEFI的程式本身來轉 換虛擬記憶體。在 SetVirtualAddressMap()轉址前,會先執行註冊EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE的 Event。


Variable Services

Variable的主要作用在於 OS Loader與 firmware之間傳遞資料,對於不同的 platform可能有不同 的實作,但其要能在 reset的時候也可以保存資料。

VariableNameVariableGuid 用來命名不同的 Variable。VariableName通常是人讀得懂的文字 ,也不用擔心其重複,還有 VariableGuid可以用來分別。

有三個主要的 Attributes需要注意:

  • Nonvoliatile
  • 在 System Reset之後 Variable依然可以保留。

  • BootService
  • 代表只有在 ExitBootService()之前可以使用,之後的 GetVariable()及 GetNextVariable()都會無法找到。

  • RuntimeService
  • 有這個代表 BootService也要同時被設定, ExitBootServices()之後也能存取。

只有同時擁有 Nonvoliatile和 RuntimeService的 Variable可以在 ExitBootServices()之後使用 SetVariable()來寫入。而只有 RuntimeService的則是 read-only,原因是記憶體的控制權已經交 給了 OS。


Miscellaneous Services

  • GetNextHighMonotonicCounter
  • ResetSystem
  • UpdateCapsule
  • QueryCapsuleCapabilities

沒什麼想寫的,有想法再回來補充。


2022年7月19日 星期二

[Python] 使用 Python的 venv建立虛擬環境

相關文章:
使用 Virtualenv 開發 Python

先前都是使用 Virtualenv來建立 Python開發的虛擬環境,但在 Python後面的版本,其本身就內 建了虛擬環境的模組 venv。

  • 使用 venv 建立虛擬環境
將路徑切換至需要建立虛擬環境的目錄下,並執行:
python -m venv [env_name]
env_name為所要建立虛擬環境的名稱,執行完後就會在目錄下產生名為 env_name的資料夾

  • 啟動虛擬環境
Windows:
[env_name]\Scripts\activate.bat
Unix like系統: source [env_name]/bin/activate
啟動成功後,視窗中會出現 [env_name]的提示字元,表示正在運行虛擬環境。
接下來,就可以使用 pip來安裝需要的模組,可參考:
套件管理程式 - pip

  • 離開虛擬環境
Windows:
[env_name]\Scripts\deactivate.bat

Reference:
https://docs.python.org/zh-tw/3/tutorial/venv.html

2022年7月18日 星期一

PCI Enumeration - PCI Root Bridge IO Driver

PCI Enumeration是我做 BIOS這幾年還未跨過去的一道坎,也只是片段零碎地在 debug的過
程中摸索,看不清楚全貌。最近看了 "EDK II EFI Driver Writers Guide"的 "PCI Driver
Design Guidelines
"覺得獲益良多,許多觀念在讀這章的過程中重新整理了一次,所以藉由這
機會來重頭 study EDK II中關於整個 PCI Enumeration的程式碼,打鐵趁熱。

Pci Enumeration 在 EDK II主要由 PCI Root Bridge Io DriverPCI Bus Driver以及 PCI Driver
這三種 Driver來完成。這三者的主要工作與概念在"PCI Driver Design Guidelines"寫得很清
楚,就不著墨在這。而 PCI Driver又太多種,所以本篇目前只專注在 PCI Root Bridge Io Driver
及 PCI Bus Driver的程式碼上。

PCI Root Bridge Io Driver

從Entry Point InitializePciHostBridge開始看吧:

  RootBridges = PciHostBridgeGetRootBridges (&RootBridgeCount);
  if ((RootBridges == NULL) || (RootBridgeCount == 0)) {
    return EFI_UNSUPPORTED;
  }

好,我承認第一段就有點卡關,Host BridgeRoot Bridge分別到底是什麼? 先看一下 PCI_HOST_BRIDGE_INSTANCE的結構:

typedef struct {
  UINTN                                               Signature;
  EFI_HANDLE                                          Handle;
  LIST_ENTRY                                          RootBridges;
  BOOLEAN                                             CanRestarted;
  EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL    ResAlloc;
} PCI_HOST_BRIDGE_INSTANCE;

PCI_HOST_BRIDGE_INSTANCE的結構裡含有 RootBridges的 Linked List,代表一個 Host Bridge 可能有一個至多個 Root Bridge。又從後面的註解可以看到,EDK在 PCI Enumeration的情況只考慮系統只會有一個 Host Bridge。 可以理解成 Host Bridge是比 Root Bridge還上層的東西。

  //
  // Most systems in the world including complex servers have only one Host Bridge.
  //
  HostBridge = AllocateZeroPool (sizeof (PCI_HOST_BRIDGE_INSTANCE));
  ASSERT (HostBridge != NULL);

  HostBridge->Signature    = PCI_HOST_BRIDGE_SIGNATURE;
  HostBridge->CanRestarted = TRUE;
  InitializeListHead (&HostBridge->RootBridges);
  ResourceAssigned = FALSE;

翻開 PCI的 spec,可以看到 PCI Bus就是透過 Host Bridge來與系統上的 CPU及 Memory連接的。而到了 PCI Express, Host Bridge的功能就被包含在 Root Complex中。


Root Bridge的詳細描述可以在UEFI spec中找到,其是用以產生實體 PCI Bus的 chipset component。一個 Host Bridge可以有一個或多個 Root Bridge。下列兩個 UEFI spec中提到的比較常見的系統範例。

只有一個 Root Bridge:

多個 Root Bridge:

UEFI spce也有多個 Host Bridge的系統範例,這裡就不貼出來了。在我先前的經驗只有碰過一 個 Host Bridge和一個 Root Bridge的情況,所以這兩個東西才會讓我如此困惑,有時候也會混 在一起說。

另外其中還提到,PCI Segment與 Host Bridge和 Root Bridge是不同的概念。PCI Segment指的 是共用相同 PCI Configuration Space的 PCI Bus之集合,最多能有 256個 Bus。Root Bridge 可能包含整個或是部分的 PCI Segment。Host Bridge若是有多個 Root Bridge,則很有可能有 多個 PCI Segment。

再回到第一段 code,其用意就是要知道系統中有幾個 Root Bridge,其中的方法是去掃 Bus 0 - 255,如果 Bus上有 Device,就表示 Root Bridge存在,而在掃的過程中要去扣除掉 PCI to PCI Bridge所產生出來的 Bus。

當然,如果本來就是知道系統中有多少 Root Bridge,我也是看過直接 hard code的。

接下來 InitializePciHostBridge還會將每個 RB所用到的 MMIO及 IO透過 DXE service回報
給 GCD (Global Coherency Domain) ,讓系統來管理所能使用的 resource。

並在 Root Bridge Handle上安裝 PCI Root Bridge IO Protocol來讓之後的 PCI Bus Driver使用。

2022年7月13日 星期三

Git - 改變目錄名稱的大/小寫

話說有天心血來潮,想要把自己某個 side project的資料夾名稱從大寫的 Config改成小寫的 config。 但手動改成小寫後,git status指令的狀態不會有任何變化,於是查了一下,發現可以使用 git mv 指令來達到我要的效果。


$ git mv ./Config ./config1
$ git mv ./config1 ./config

透過一個過渡的目錄名稱 ./config1來達到我要的效果。還有另一個好處是 git mv之後的檔案都是 git add/rm之後的狀態,接下來直接 commit就行了。

由於 git mv本身就是移動或重新命名檔案或目錄的命令,直接使用:

$ git mv ./Config ./config
Rename from 'config' to 'Config/config' failed. Should I try again? (y/n)

git 以為是要把當前的 ./Config移動到 ./config下,會出現 fail。

而改變檔案名稱的大/小寫是可以直接使用 git mv的:

$ git mv a.txt A.txt

補充:
git的大小寫名稱行為似乎是跟 OS的檔案系統本身是不是 case sensitive有關。
透過 git config能改變 git case sensitive的行為。

2022年7月12日 星期二

Beyond BIOS Note - CH4 Protocols You Should Know


Common sense ain't common.
- Will Rogers

EFI OS Loaders

OS loader是一種特殊的 UEFI Application,用來讓系統從 firmware
環境轉換到 OS環境。

  1. 要確定是從哪被 load,這能讓其從相同位置取得其他檔案。
  2. 要確定 OS被存放在哪。OS可能會在硬碟中的某個 partition,所以 OS Loader
    需要實作或讀取檔案系統的驅動來存取OS partition的檔案。
  3. 需要建立實體記憶體的 memory map回報給 OS Kernel,有些 memory resource是
    OS不能碰的,所以 OS Loader也需要從UEFI API來獲得系統當前的 memory map。
  4. OS 能將 boot path及 boot options以環境變數的形式存在 nonvolatile storage
    中。OS Loader也會使用這些環境變數,並且也需要將某些傳遞給 OS Kernel。
  5. 呼叫 ExitBootService ()後,UEFI Boot Service已經無法再使用,控制權轉交
    OS Kernel,OS Kernel只能使用 UEFI Runtime Service。