編譯第一個 UEFI 程式並不輕鬆:環境安裝耗時,連結器錯誤很多,.EFI 程式也不像普通桌面程式那樣有成熟直觀的編輯和執行體驗。
這篇就按入門角度整理一下:如果只是想先編譯出自己的第一個 UEFI 程式,應該從哪裡開始,哪些概念要先搞清楚,哪些坑最容易遇到。
UEFI 程式是什麼
UEFI 程式通常是一個 .EFI 檔案。
它不是 Windows 下雙擊執行的普通 .exe,而是執行在 UEFI 韌體環境裡的 PE/COFF 可執行檔。常見場景包括:
- 啟動管理器
- 硬體初始化工具
- 韌體更新工具
- 開機前診斷工具
- 自訂啟動流程
你在系統啟動早期看到的很多功能,本質上都可能和 UEFI 應用程式、驅動或韌體服務有關。
對初學者來說,先不用急著理解完整韌體開發。第一步只要完成一個目標:編譯出一個能被 UEFI Shell 或模擬器載入的 .EFI 檔案。
為什麼不建議一開始就上 EDK II
UEFI 正式開發經常會遇到 EDK II。
EDK II 功能完整,也更接近真實韌體工程,但它對新手不太友好:
- 工程結構複雜
- 建構系統有學習成本
- 環境變數和工具鏈配置比較多
- 編譯錯誤不容易看懂
- 很容易還沒寫程式碼,就先卡在環境上
如果目標只是「先跑起來一個最小 UEFI 程式」,更適合從輕量範例開始。
pbatard/uefi-simple 就是這類專案。它的定位很直接:提供一個簡單的 UEFI Hello World 範例,讓你先把 .EFI 編譯出來。
uefi-simple 適合用來做什麼
uefi-simple 適合做 UEFI 入門的第一塊踏板。
它主要解決三個問題:
- 給你一個最小可編譯的 UEFI 應用結構
- 幫你避開一開始就接觸大型韌體工程的複雜度
- 讓你能先驗證編譯、連結、執行鏈路是否通
這個專案支援不同建構方式,包括 Visual Studio 2022 和 MinGW/gcc,也可以配合 QEMU 與 OVMF 做測試。
也就是說,你不一定非要準備真實機器反覆重開機測試。先用模擬器把程式跑起來,會安全很多。
入門前要準備哪些東西
最少需要準備幾類工具。
第一類是編譯工具鏈。
如果你在 Windows 上,可以優先考慮:
- Visual Studio 2022
- 或 MinGW/gcc
第二類是 UEFI 執行環境。
可以有兩種選擇:
- 用真實機器的 UEFI Shell 執行
.EFI - 用 QEMU + OVMF 在虛擬環境裡測試
第三類是範例專案。
新手不建議從空目錄開始手寫建構腳本。直接使用 uefi-simple 這類最小範例,可以少踩很多建構系統的坑。
大致流程
一個最小 UEFI 程式的入門流程可以這樣理解。
第一步,取得範例專案。
|
|
第二步,選擇建構工具鏈。
如果用 Visual Studio,就按專案裡的 Visual Studio 方案建構。
如果用 MinGW/gcc,就按專案提供的 Makefile 或說明走。
第三步,產生 .EFI 檔案。
這裡最關鍵的是確認目標架構。常見 PC 一般是 x86_64,也就是 64 位元 UEFI 環境。
第四步,把 .EFI 放到可以被 UEFI Shell 存取的位置。
如果用真實機器,通常會準備一個 FAT32 分割區或 USB 隨身碟。
如果用 QEMU,可以把目錄或映像檔掛載進去。
第五步,在 UEFI Shell 裡執行。
執行效果通常就是一個最小輸出,比如印出類似 Hello World 的內容。
最容易卡住的地方
編譯 UEFI 程式最容易卡的不是 C 語言本身,而是環境和連結。
常見問題包括:
- 編譯器架構不對
- 目標格式不對
- 連結參數不完整
- 缺少 UEFI 進入點
- 產生的是普通可執行檔,不是 UEFI 能載入的
.EFI - QEMU 或 OVMF 沒配置好
- 真實機器 Secure Boot 阻止執行未簽署程式
尤其是連結器錯誤,經常會讓新手誤以為程式碼寫錯了。
實際上,很多時候是進入函式、子系統、目標架構或連結腳本配置不對。
所以第一階段不要急著改複雜邏輯。先確保原始範例能編譯、能執行,再一點點改輸出內容。
測試時為什麼推薦 QEMU + OVMF
真實機器測試 UEFI 程式並不是不行,但新手階段不太方便。
因為你可能需要反覆:
- 編譯
- 複製到 USB 隨身碟
- 重開機
- 進入 UEFI Shell
- 執行
- 記錄錯誤
- 回到系統修改
這個循環很慢。
QEMU + OVMF 的好處是可以在作業系統裡直接模擬 UEFI 環境。你可以更快地驗證 .EFI 是否能被載入,也不容易影響真實機器啟動項。
等程式基本跑通,再放到真實機器上測試,會更穩。
新手應該先改哪裡
如果你已經用範例專案編譯出了第一個 .EFI,下一步不要馬上寫複雜功能。
建議按這個順序改:
- 先改輸出文字,確認重新編譯後的程式確實生效。
- 再嘗試讀取 UEFI 提供的簡單資訊。
- 再理解進入函式、輸出協定和基本服務。
- 最後再考慮檔案系統、圖形輸出、啟動項管理等複雜功能。
這樣做的好處是每一步都能驗證。
如果一上來就改很多東西,出錯時很難判斷到底是程式碼問題、編譯問題,還是執行環境問題。
和普通 C 程式有什麼不同
UEFI 程式雖然可以用 C 寫,但它和普通 C 程式的執行環境完全不同。
普通 C 程式通常執行在作業系統裡,可以依賴標準函式庫、檔案系統、程序模型和系統呼叫。
UEFI 程式執行在作業系統啟動之前,它依賴的是 UEFI 韌體提供的服務。很多你在普通程式裡習慣使用的東西,在這裡並不是天然可用。
所以寫 UEFI 程式時,要先適應幾個變化:
- 進入函式不一樣
- 輸出方式不一樣
- 可用函式庫不一樣
- 記憶體和檔案存取方式不一樣
- 除錯方式不一樣
這也是為什麼推薦先從最小範例開始,而不是直接照普通 C 程式的習慣寫。
一個比較現實的學習路線
如果只是入門,可以按這個路線走:
- 第一步:編譯
uefi-simple - 第二步:用 QEMU + OVMF 跑起來
- 第三步:修改 Hello World 輸出
- 第四步:理解 UEFI Shell 怎麼載入
.EFI - 第五步:學習 UEFI 的進入函式和基本輸出協定
- 第六步:再看 EDK II 或更完整的 UEFI 開發資料
這條路線的重點是先讓回饋閉環跑起來。
只要你能從原始碼產生 .EFI,再在 UEFI 環境裡看到輸出,就已經跨過了最難的第一道門檻。
參考
最後一句
編譯第一個 UEFI 程式,難點通常不在「寫出一段 C 程式碼」,而在把工具鏈、連結格式和執行環境串起來。
先別急著做複雜功能。
從 uefi-simple 這種最小範例開始,先得到一個能執行的 .EFI,再逐步理解 UEFI 的進入點、協定和建構方式,會輕鬆很多。