有別於傳統 BIOS(Basic Input/Output System) 使用組合語言開發,且僅支援 16bit 資料運算不同。 UEFI 使用 C 語言撰寫,可依據平台性能支援 32bit 或 64bit ,並提供低階的作業系統功能,藉以實現精美的 UI(User Interface) 介面或網路開機等功能。在硬體資源的控制上, BIOS 使用中斷 (Interrupt Request, IRQ) 進行管理,而 UEFI 則採用輪詢 (polling) 方式。
提供下列功能
多系統開機流程如下圖範例所示,硬碟中共存在 Windows(W) 與 Linxu(L) 兩個作業系統, MBR 指向 W 的 boot loader 提供了 W 和 L 兩個開機選項。當使用者選擇 L ,則 W 的 boot loader 會將開機管理工作轉移至 L 的 boot loader ,並開始載入 Linux 核心接續開機流程。
又被稱為 Initial RAM Disk ,即為 initrd 系統命令。 boot loader 運作時會同時將 Linux 核心與 initramfs 掛載到記憶體中。 Linxu 核心剛載入至記憶體時,會因為核心沒有編譯硬碟驅動程式,無法讀取硬碟中的資料掛載根目錄下的驅動程式。故需使用 initramfs 模擬 Linux 核心的根目錄,透過此虛擬檔案系統偵測與載入實際的硬體驅動程式,讓核心可透過虛擬檔案系統取得驅動程式,以及載入實際的根目錄檔案系統。
細部的 initramfs 運作說明可以參照 initrd 的 man page 描述,並可以透過 lsinitramfs 命令,顯示包含 Linux 核心開機必要模組的 initramfs 映像檔內容。
需注意的是 initrd.img 檔案是連結檔,實際所使用的映像檔可以使用 ls 命令進行觀看。
Linux 核心完成實際檔案系統載入後便會將 initramfs 釋放,並呼叫 systemd 執行接續的開機流程。 systemd 用於建立軟體執行環境,以啟動作業系統的各項服務
程式碼經過編譯器與連結器輸出為可執行的目的檔 (.axf/.elf),該檔案中存在多個用於儲存程式碼與變數等資訊的區間 (section) 。
區間內容在編譯過程中透過連結器進行分配與處理,下列為常見的區間種類,實際應用上使用者也可依據自身需求定義新的區間。須注意下列的 .data 與 .bss 為全域變數,區域變數只有在執行過程中儲存於 stack 中。
Architecture: 描述系統主要功能及行為 (e.g. ARMv8-A, ARMv9-A),並明確定義下列功能
Micro-architecture: 描述系統的細部規格 (e.g. Cortex-A8, Cortex-A65AE),明確定義下列規格
PE (Processing element): 處理單元 (e.g. ARMv8-A, ARMv9-A), manual 文件中對 ARM 處理器的架構與行為的抽象稱呼 (The behavior of an abstract machine),避免在文件描述上與 micro-architecture 混淆。
Execution State: 定義處理單元的運算環境 (e.x. AArch64 和 AArch32),內容包含下列項目。自 ARMv8-A 架構開始可同時支援 AArch64 和 AArch32 ,AArch64 支援 A64 指令集,可處理 32 或 64-bit 的資料與運算; AArch32 支援 T32 與 A32 指令集,負責執行 32-bit 資料運算,且與 ARMv7-A 相容。 需注意 PE 僅能在處理器重置或 EL 層級變更時更動 execution state 。
Exception: 概念與 interrupt 相似,但 interrupt 在 AArch64 中定義為外部觸發的 exception 。當 exception 出現時, PE 會將 program counter (PC) 切換至 exception handler 進行處理,此動作稱為 take exception 。當 exception handler 執行完畢後會回到原本的程式碼繼續執行,此動作稱為 return from exception 。
EL (Exception Level):例外層級,共有四個層級,數字越大優先權 (privilege) 越高,此優先會影響可存取的記憶體 (memory privilege) 及可使用的處理器資源,需注意不同的 micro-architecture 支援的 EL 層級有所差異,使用前請查閱 TRM (Technical Reference Manual) 文件確認細節。
Memory Privilege: 透過軟體建立 MMU (memory management unit) 的記憶體存取權限對應表,在 EL0 層級需有 unprivileged access permission 才可存取、在 EL1 、EL2 與 EL3 下需有 priviledge access permission 才可存取。
EL 層級所使用的 execution state 由下一個更高 EL 層級的暫存器決定, execution State 可在處理器重置或 EL 層級變更時變更,但需注意 EL 層級變更時切換有下列限制,藉此實現可在 AArch64 上運作 AArch32 ,但無法在 AArch32 下運作 AArch64 。
EL 層級切換僅在下列五種狀態下發生:
Secutiry States: 安全狀態,分為 secure state 與 non-secure state 兩種狀態,安全狀態必須透過 EL3 層級切換,且不同架構的 EL 層級可支援的安全狀態有所差異。以 ARMv8-A 為例, EL3 層級固定為 secure state ;但在 ARMv9-A 中,若有實作 RME (Realm Management Extension) 則可同時支援兩種安全狀態。
RME (Realm Management Extension): 於 ARMv9-A 架構推出的新功能,新增下列兩個種安全狀態,且 EL3 僅支援 root state ,以實現 TrustZone 機制。
Processor State (PSTATE)
有別於 ARMv7 使用 CPSR (Current Program Status Register) 儲存當前 CPU 狀態,ARMv8 使用 PSTATE (Processor State) 儲存相關資訊,此暫存器的細部定義如下,需注意 EL0 僅可存取 N, Z, C, V ,而其他 Exception Level 則可存取所有 PSTAT 內容。
Secure Configuration Register, SCR_EL3
PE 的 secure state (安全狀態)紀錄於 64bits 長度的 SCR_EL3 暫存器中,其 bit[0] NS 用於標示當下的 Exception level 運作於哪個 secure state。
成大資工 Wiki - ARMv8
回顧 ARM 架構
Learn the architecture - Introducing the Arm architecture
Arm Architecture Reference Manual for A-profile architecture
透過硬體資源的控制,將執行環境分割為 secure processing environment (SPE) 和 non secure processing environment (NSPE) 。 SPE 提供需要安全保護的服務 (e.g. 韌體更新、資料加解密); NSPE 提供一般使用者的應用程式執行環境。兩者之間的資料交換需透過特定的 API 來傳輸,達到資料的操作權限控管。
ATF 通常具備下列五種元件 (component) ,這五種元件由低至高同時代表著系統啟動時的順序。
SMC (Secure Monitor Call): Normal world 與 Secure world 對 BL31 發送的呼叫訊號,使用寫入特定暫存器數值進行實作。
ERET (Exception Return): BL31 對於 SMC 回傳的執行結果。
Secure-EL1 Payload: 泛指在 Secure-EL1 層級下運作的軟體,包含但不限於 Trusted OS 。 Secure-EL1 Dispatcher 負責初始化 Secure-EL1 Payload 和處理 Secure world 與 EL1 或 EL2 層級下的 Normal world 之間的溝通。溝通方式有:
PSCI (Power System Control Interface): 提供系統控制 CPU 下列運作狀態 控制介面。
SCMI (System Control and Management Interface): 控制系統時脈、電源等的 API 介面。
Boot up procedure
實際的啟動流程與細節,不同的晶片流程會有所差異,故實際的流程需參考晶片供應商提供的資料。
SCP (System Control Processor) firmware: 負責底層硬體系統 (low-levle hardware system) 的電源啟動時序、硬體系統初始化、系統時脈管理和處理 OS Power Managment (OSPM) 的命令。
AP (Application Processor) firmware: 使平台在 SCP 完成初始化後,接續處理系統開機流程,載入 Secure world image (e.g. Secure partition maanger, Trusted OS) ,使系統可運作 Rich Operating System (ROS) 。
CPU 啟動流程
Understanding ARM Trusted Firmware using QEMU
Secure Boot using Trusted Firmware-M
How ARM Systems are Booted: An Introduction to the ARM Boot Flow
ARM Developer - SCP/AP Firmware
Arm Power State Coordination Interface
疑問
QEMU 模擬器可提供多樣平台和系統的模擬環境,省去硬體購置與架設成本,提升系統開發與實驗的便利性。以下為在 Ubuntu 22.04 上編譯並安裝 QEMU 9.0.0 ,首先確認安裝環境:
下載 QEMU 原始碼進行編譯與安裝,參考 QEMU 官網提供的安裝流程,在安裝 QEMU 前需先安裝下列軟體:
在目標資料夾下使用 Git 下載最新版的 QEMU 原始碼。
在該資料夾中建立 build 資料夾,執行下列步驟進行編譯, QEMU 要編譯的檔案很多會跑好一陣子(以 9.0.0 版本為例,要編譯與連結的項目多達 9307 項)。
執行 configure 時出現下列錯誤,系統提示未安裝 python3-vene 套件:
解決辦法:安裝 python3-venv
重新執行 configure 過程又出現失敗,系統提示找不到 Flex 程式。 Flex (fast lexical analyzer generator) 是一種詞法分析程式,通常與 bison 一起運作,缺什麼補什麼,就把兩個程式一起安裝吧!
解決辦法:安裝 Flex 與 bison
完成編譯後使用 make install 安裝 QEMU
安裝完成後,可使用下列命令確認 QEMU 是否安裝成功,若安裝成功會顯示當前的版本資訊。
或是輸入 qemu- 後連續按下兩次 tab 鍵,若出現下面的 qemu 輸出,代表安裝成功。
上述顯示資訊代表目前 QEMU 可支援的 CPU 種類,細部 Micro-architecture 支援清單可透過下列命令顯示。若清單上沒有想要的 Micro-architecture ,則可以檢查是否有新的 QEMU 版本被釋出。
疑問:
在 QEMU 中建立 Armv8-A 環境,並使用 TrustedFirmware-A (TF-A) 載入 non-secure OS。
Toolchain 版本應盡可能與最小需求相同,否則不保證可以正常運作。 ATF 對於 Armv7-A 與 Armv8-A 裝置支援下列所有 Cross-compiler ;但對於 AArch32 與 AArch64 則需使用 arm-none-eabi 與 aarch64-none-elf 。
Program | Min supported version | Note |
---|---|---|
Arm Compiler | 6.18 | cross-compiler |
Arm GNU Compiler | 13.2 | cross-compiler |
Clang/LLVM | 11.0.0 | cross-compiler |
Device Tree Compiler | 1.4.7 | cross-compiler |
GNU make | 3.81 | |
mbed TLS | 3.6.0 | For Trusted Board Boot and Measured Boot. |
OpenSSL | 1.0.0 | For cert_create, encrypt_fw, and fiptool tools. |
QCBOR | 1.2 | For DICE Protection Environment. |
Node.js | 16 | For building TF-A documentation. |
Poetry | 1.3.2 | For building TF-A documentation. |
Sphinx | 2.4.4 | For building TF-A documentation. |
若不撰寫文件,實際所需的安裝程式:
使用 apt install 安裝 aarch64-linux-gnu cross compiler :
由版本資訊可發現 aarch64-linux-gnu-gcc 版本為 11.4.0 ,後續實測此版本可以正常編譯。
直接至 Arm GNU Toolchain Downloads 網頁中下載對應平台的壓縮檔,解壓縮後可於其 bin 資料夾中看到 cross compiler 的執行檔。執行編譯前需指定編譯器執行檔所處的路徑 path-to-aarch64-gcc
:
建立 Normal world 開機所需的 UEFI 映像檔,參照下列命令,至 EDK2 Github 下載相關原始碼並進行編譯:
實際操作過程在 make -C BaseTools 失敗,看起來是編譯 EDK2 時出現問題。EDK2 為用於建立 UEFI 應用的開發環境, EDK2 BaseTools 則為使用 Python 建立相關功能的版本。
依據 GNUmakefile 的錯誤提示,其在 Tests 資料夾中,因無法使用 python 命令而中斷。 Ubuntu 自 20.04 版本開始,內建 python 3 而非 python 。透過安裝 python-is-python3 ,使用 python 3 假冒 python ,提供 python 命令。
安裝python-is-python3 後,便可完成 make -C BaseTools 命令。接續執行剩餘步驟,在 build 階段又出現問題:
根據系統提示看起來缺少 isal 命令,該命令可藉由安裝 acpica-tools 取得,其為 Intel ACPI Component Architecture (ACPICA) 編譯器,用於將 ACPI Source Language (ASL) 檔案編譯為 ACPI Machine Language (AML) 檔案。透過下列命令進行 ACPICA 安裝與驗證:
重新執行編譯,若獲得下列系統提示代表編譯成功,映像檔 QEMU_EFI.fd 位於 <edk2 路徑>/Build/ArmVirtQemuKernel-AARCH64/DEBUG_GCC5/FV/ 資料夾中。
建立 Normal world 開機所需的虛擬檔案系統 (initramfs) 映像檔,透過 Git 下載 Buildroot 原始碼並進行編譯,需注意最後的編譯需要一段時間。
編譯完成後可在 output/images 資料夾下得到 rootfs.cpio.gz 檔案。
建立 Normal world 下運作的 Linux 映像檔,首先使用 git clone 下載最新的 Linux 核心,並進入該資料夾中。
第一次編譯 Linux 核心前,可先執行 make mrproper 將編譯過程的目標檔案以及設定檔移除,避免編譯過程使用錯誤的設定檔。後續重新編譯時,因不需刪除設定檔,只需使用 make clean 清除先前編譯到的 .o 檔案。
在進行 Linux 核心編譯前需設定核心功能配置,配置設定檔放在 arch/<平台名稱>/configs 資料夾內,若不使用現成的設定檔,編譯過程需手動選擇要如何配置相關設定,如果看不懂也可以使用 ? 觀看說明 (極度不建議這個方法,設定數量多到會懷疑人生)。另外一種設定方式,可使用 make menuconfig 以 GUI 介面設定核心配置。
在這邊選擇使用默認的設定 defconfig 進行編譯,編譯完成的映像檔會儲存於 arch/<平台名稱>/boot 資料夾中。
編譯過程出現 fatal error ,內容為找不到 openssl/bio.h 檔案,雖然先前建立 toolchain 時有安裝 openssl ,但在 /usr/include 路徑下並沒有該檔案。
解決方法為安裝 OpenSSL 的開發套件,安裝完成後即可在 /usr/include 路徑下找到 bio.h 檔案,重新執行 Linux 核心編譯便可成功完成。並在 linux/arch/arm64/boot/ 路徑下獲得 Image Linux 核心映像檔。
TF-A 是基於 ARM Trusted Firmware 的一個分支,其應用於 ARMv8-A 架構,專案原始碼可透過 GitHub 取得,下載完成後開啟 arm-trusted-firmware 資料夾。
編譯 TF-A 前需執行 cross compiler 設定,因目標是在 QEMU 中模擬 ARMv8-A 64bit 系統,故選用 AArch64 cross compiler 進行編譯,PLAT
選項設定為 qemu ,若此選項未設定則會默認為使用 FVP (Fixed Virtual Platform) 。
完成 TF-A 編譯後,移動當前路徑至 build/qemu/release 資料夾,可在資料夾中看到編譯完成的映像檔:
將方才編譯完成的 rootfs.cpio.gz 、 QEMU_EFI.fd 和 Linux 核心 (Image) 映像檔複製到此資料夾內,須注意下列檔案名稱需要修改:
除了 bl1.bin 檔需特別標示在選項中外, QEMU 啟動時會自動載入其餘的映像檔。
使用到的 QEMU 命令選項說明如下:
當系統成功完成開機流程時,將看到下列登入訊息,輸入 root 後即可進入 normal world 下運作的 Linux 系統。
若要退出 QEMU ,可透過 ctrl + a 後再按下 x 鍵,或是直接將該終端機關閉即可。
目標為使用 GDB 分析 TF-A 開機流程,在此之前可以先使用 TF-A 提供的 Memory Layout 工具,了解 Bootloader 的記憶體使用情形。
每個 bootloader (BL) 映像檔依據資料內容可分為 PROGBITS 和 NOBITS 兩大區域。除了 BL31 的 NOBITS 可由使用者另外定義於映像檔中的擺放位置外,所有的 PROGBITS 會被放置在映像檔的起始位置,接續為 NOBITS 區域以節省映像檔大小,細部的擺放位置會在 linker script 中描述。
當 BL31 的 NOBITS 要擺放在非緊鄰 PROGBITS 區域後,需設定下列參數:
所有的 BL 映像檔必須具備下列條件:
BL1 儲存於 ROM 區間,運作時直接從 ROM fetch 指令進處理器中執行,然而其 .data 區間的變數在運作時須在可讀寫記憶體中宣告與使用,在記憶體中的存放位址是從該記憶體的最高位址往回儲存。
不同的運算平台的記憶體空間配置可能會有所不同,當前的 TF-A 韌體版本尚未支援映像檔動態載入 (Dynamic Image Loading) ,開發人員須依據平台規格設定映像檔在執行時期的記憶體擺放位置 (link map) ,避免映像檔的使用空間重疊問題。
BL 映像檔的 link map 存放於 <TF-A PATH>/build/<platform></platform>/<build-type>/bl<x>/ 資料夾下的 bl<x>.map 檔案中, <x> 代表 BL階段代號 (e.g. bl1, bl2, bl31)。以下列 bl1.map 內容為例,對 BL1 使用的 RAM 與 ROM 記憶體區間進行描述。可看到 BL1 所使用的 RAM 區間為 0x0000_0000_0e0e_e000 開始到 0x0000_0000_0e0f_6000 。
透過此工具可以將 TF-A 的記憶體配置資訊進行彙整與視覺化,但在使用前需安裝相關的 Python 套件。
注意須在 TF-A 資料夾下執行 memory 安裝,否則系統會回報下列錯誤訊息。
安裝完成後可透過 –help 參數確認是否正常運作。
在 TF-A 資料夾下,使用 run memory 命令顯示 ELF 檔案的 memory layout 情形。需注意如未設定平台參數 -p , poetry 將會默認找尋並分析 FVP 平台的 ELF 檔案內容。從下列的執行結果可以看到,先前建立的 BL1 、 BL2 和 BL31 映像檔的記憶體配置情形。
如果只是要觀察記憶體的使用概況,則可使用 -f 參數獲得更精簡的資訊。由下列資訊可看出 BL1 儲存於 Address 0x0000_0000 ~ 0x0000_5A00 之間的 Trusted ROM 中,並於執行過程將資量放至於 0x0E0E_E000 ~ 0x0E0F_6000 的 Trusted RAM 中; BL2 則是使用 0x0E06_B000 ~ 0x0E07900 的 Trusted RAM; BL31 使用 0x0E0A_0000 ~ 0x0E0D_8000 之間的 Trusted RAM 中。
映像檔中的區間資訊,可以使用 -t 參數以樹狀結構顯示。
如同使用 gcc 編譯時需要加入 -g 選項,為了要讓 GDB 可以追蹤 TF-A bootloader 的執行流程,需在 TF-A 編譯時建立可被 GDB 追蹤的 symbol 。參考 Build TrustedFirmware-A (TF-A) 章節中的描述,於 make 命令增加 DEBUG 選項。
編譯完成後,可在 build/qemu/debug 資料夾中看到 bootloader 映像檔。同樣將 rootfs.cpio.gz 、 QEMU_EFI.fd 和 Linux 核心映像檔複製到該資料夾中,並修改為指定的檔案名稱。
由於 GDB 通常只支援 host 電腦的 CPU 架構除錯,故需先確認當前的 GDB 是否支援 aarch64 架構。在 GDB 中使用 set architecture 命令搭配 tab 鍵,列出 GDB 支援的 CPU 種類,從顯示結果可看出目前 GDB 並不支援 aarch64 ,需安裝支援多種 CPU 架構的 gdb-multiarch 。
gdb-multiarch 安裝方式:
完成安裝後透過 gdb-multiarch 命令開啟,使用 set architecture 命令搭配 tab 鍵,可以看到其支援的 CPU 架構數量明顯多了不少,並可以看到 aarch64 在支援的清單中。
QEMU 使用 -s 選項可透過 TCP port 1234 連接 GDB 進行除錯,而透過 -S 可要求 QEMU 等待 GDB 連線後才開始執行。開啟兩個終端機,一邊執行新增 -s 和 -S 選項的 qemu 命令 。
另外一邊執行 gdb-multiarch 命令,使用 -ex 選項於啟動 gdb 時執行 file 命令載入被追蹤檔案,載入檔案的路徑可使用執行 gdb 時的位置資料夾開始的相對路徑,或是直接使用絕對路徑。
亦可在進入 gdb 後使用 file 命令載入被追蹤檔案。
請注意 file 命令僅能載入一個可追蹤檔案,要同時追蹤 bl1 以外的 bootloader 行為,可以使用 add-symbol-file 命令載入其他 elf 檔。
完成被追蹤檔案載入後,使用 TCP port 1234 與 QEMU 連接。
可將上述命令撰寫為 scrip file ,進入GDB後使用 source 命令載入,省去實驗過程重複輸入指令的時間。需注意部分的中斷條件會拉慢 QEMU 模擬速度,可待接近該中斷位置時再加入進行觀察。
Run BL1 from Trusted ROM
Load BL1 data to Trusted RAM
Load BL2 Image to Trusted RAM
Run BL2 from Trusted RAM
Load BL31 Image to Trusted RAM
Load BL33 Image to Non-Trusted RAM
Handoff to BL31 by SMC
Run BL31 from Trusted RAM
Run BL33 from Non-Trusted RAM
為了能觀察完整的 ARM trusted firmware 的運作,選用 OP-TEE (Open Portable Trusted Execution Environment) 作為 TF-A 中沒有包含到的 BL32 Bootloader。
OP-TEE 是一個基於 ARM TrustZone 硬體隔離運算機制技術進行實作的信任運算環境 (Trusted Execution Environment, TEE) ,建立滿足下列三個目標的 TEE Internal Core API :
OP-TEE 專案分為三大部分:
參考 OP-TEE 的 Prerequisties 網頁描述,安裝編譯與運作所需的程式。
接續安裝下載 OP-TEE 專案所需的 Google Repo 工具,官方建議安裝方法如下,但實際執行會遇到系統提示權限不足的情況。
替代方法可直接透過 apt get install 安裝,但此版本較舊,在後續使用上系統會自動偵測,並提示有新的版本可使用。
建立 OP-TEE 資料夾,並在該資料夾下使用 repo 命令下載 OP-TEE 原始碼。 -m
選項用於指定執行的平台,選擇 qemu_v8.xml
在 QEMU 中模擬 ARMv8-A 平台; -b
選項指定下載的專案版本,例如希望下載的版本為 3.12 版時,選項設為 -b 3.12.0
,若未指定則會自動下載最新版本。下載過程若出現網路異常造成下載不成功,重新再執行一次即可。
完成原始碼下載後,進入 build 資料夾中,執行 OP-TEE 的 tool chain 編譯,此流程會花費一點時間,可以透過 -j 選項配置投入編譯的 CPU 數量。
使用 make
命令編譯 OP-TEE ,加上 DEBUG=1
選項可獲得 Debug 模式版本。
編譯完成後,輸出的映像檔散布在不同的資料夾下:
TF-A 映像檔放至於 [optee 路徑]/trusted-firmware-a/build/qemu/release
資料夾:
BL32 映像檔案放在 [optee 路徑]/optee_os/out/arm/core
資料夾:
BL33 映像檔放在 optee/u-boot
資料夾中:
Secure world OS 映像檔放在 [optee 路徑]/linux/arch/arm64/boot
資料夾:
Secure world initramfs 映像檔放在 [optee 路徑]/out-br/images
資料夾:
Normal world OS 和 initramfs 映像檔放在 [optee 路徑]/out/bin
資料夾中:
執行 make check
命令可對編譯完成的 OP-TEE 進行健全性測試 (xtest),此測試包含驗證是否相容於 ARM Trusted Zone 功能,並實際在 QEMU 上執行以 OP-TEE 作為 BL32 的 TF-A 開機流程,確認系統是否可正常運作。
健全性測試成功將會看到下列提示訊息。
完成測試後,可以在 optee/out/bin
資料夾中看到 serial0.log
和 serial1.log
兩個檔案,前者紀錄測試過程的命令資訊,由此 log 檔可以看到測試方式是在 QEMU 中使用 semi-hosting 的方式載入映像檔,並使用 serail1.log 紀錄 QEMU 的執行狀態。
make run
命令為編譯與執行 OP-TEE ,過程中會將 QEMU 運作所需的映像檔以 symbolic link 方式連結於 optee/out/bin
資料夾中,並建立 secure world 與 normal world 的 console 視窗,顯示 OP-TEE 運作過程中的系統資訊。
完成後透過
建立 console 視窗的方式,是使用 gnome-terminal 與 nc 命令搭配 [optee 路徑]\build\soc_term.py
的 python 程式達成 。
若完成編譯後只是要單純執行 OP-TEE 模擬,則可使用 make only-run
命令開啟 QEMU 並載入 TF-A 與 OP-TEE 。
雖然 OP-TEE 專案中可以輕鬆完成完整的 TF-A 模擬,但仔細觀察發現專案中的 TF-A 韌體版本較舊,下面就來看看如何只編譯 OP-TEE ,並整合到最新版本的 TF-A 中,將所有的 bootloader 打包為單一的映像檔。
為方便接續的觀察, BL32 編譯過程中加入 DEBUG=1
和 CFG_CORE_ASLR=n
選項, 避免 ASLR(Address Space Layout Randomization) 產生隨機的虛擬記憶體位址,在每次開機時將程式碼與映像檔載入到不同的虛擬記憶體位址上。編譯後可供 GDB 追蹤的 elf 檔為 'tee.elf' ,放置在 [optee 路徑]/optee_os/out/arm/core/
路徑下。
編譯完成後將輸出的下列映像檔複製到 TF-A 的資料夾內,執行 TF-A 的編譯並將 BL32 一同打包為 FIP(Firmware Image Package) ,由於 OP-TEE 的映像檔在未特別配置下會使用 GICv3 ,而 TF-A 默認使用 GICv2 ,故編譯時需加入 QEMU_USE_GIC_DRIVER=QEMU_GICV3
選項。
TF-A 編譯完成後於 [TF-A 路徑]/build/qemu/debug 資料夾下輸出 fip.bin ,並獲得下列提示:
移動到 build/qemu/debug/ 資料夾中,透過 dd 命令將編譯產生的 bl1.bin 和 fip.bin 合成為 flash.bin 。
透過 [TF-A 路徑]/tools/fiptools 下的 fiptools 工具,可分析打包好的映像檔中的資訊。
然而使用 poetry 進行分析時,卻發現無法僅能顯示片段的 BL32(tee) 的資訊,猜測應該是 poetry 只能完整顯示 secure SRAM 與 secure ROM 的使用資訊。
TF-A 記憶體配置定義於 [TF-A 路徑]/plat/qemu/qemu/include/platform_def.h 中,可以看到有別於 BL1、BL2 和 BL33 載入到 secure SRAM ,BL32 將會被載入到 secure DRAM 中。
啟動 QEMU 前需先建立 secure world 與 normal world 的 console 視窗,並在 QEMU 的命令中新增 -serail
選項, -bios
選項設定為以 flash.bin 作為開機的映像檔。
使用 GDB 分析時,需要注意雖然把所有 bootloader 透過 FIP 打包為單一映像檔,仍要以 add-symbol-file 命令載入要觀察的 bootloader elf 檔。分析的過程中會需要開啟下列四個 console 視窗:
增加 OP-TEE 後的 TF-A 運作資訊顯示在 normal world 視窗中,可以觀察到 BL2 階段載入中更多的映像檔,映像檔 ID 定義在 tbbr_img_def_exp.h 中。
從 TF-A 顯示的訊息,可看到在 BL2 階段出現轉交控制權到 BL32 的提示,以 Handoof to BL32
作為關鍵字進行搜尋,發現該提示資訊定義於 qemu_bl2_setup.c
,並由 bl2_load_images
函式透過 while 進行呼叫,沒有真的轉交控制權到 BL32 。
實際將控制權轉交至 BL32 的時間點發生在 BL31 階段, BL31 執行 BL32 初始化的過程中,會透過 opteed_setup
函式將 BL32 的進入點函式 opteed_init
指定到 bl32_init
, 在該函式中呼叫 opteed_enter_sp
將控制權轉移到於 S—EL1 層級下運作的 BL32 ,執行完成後再回到 bl31_main 函式中。
BL32 交回控制權給 BL31 後,會以 timer interrupt 方式定期中斷 Normal World OS ,並在 S-EL1 層級下執行 periodic_callback
函式。可在 secure world 視窗中看到下列系統提示:
完整的啟動流程描述如下,圖片頗傷眼,但這是目前試到可以用 Graphviz 畫出的完整流程,之後完全熟悉 Graphviz 後再來重新畫一張。
Run BL1 from Trusted ROM
Load BL1 data to Trusted SRAM
Load BL2 Image to Trusted SRAM
Run BL2 from Trusted SRAM
Load BL31 Image to Trusted SRAM
Load BL32 Image to Trusted DRAM
Load BL32 EXTRA1 Image to Trusted DRAM
Load BL33 Image to Non-Trusted RAM
Handoff to BL31 by SMC
Run BL31 from Trusted SRAM
Run BL32 from Trusted SRAM
Run BL33 from Non-Trusted RAM