2、UEFI开发环境搭建:EDK2的下载与编译、模拟器(QEMU)的使用、开发工具链配置
说实话,搭建开发环境这件事,看着简单,但坑是真不少。我见过太多人卡在环境配置上,代码还没写一行,先被编译错误折磨得想放弃。这一章,我就带你一步步把环境搭起来,顺便聊聊我这些年踩过的坑。
2.1 整体环境概览
先看看我们要搭什么。说白了,就是三样东西:EDK2源码、交叉编译工具链、QEMU模拟器。它们的关系是这样的:
核心流程: 编写UEFI驱动/应用 → EDK2编译系统 → 生成.efi文件 → QEMU加载运行 → 调试验证
我习惯在Linux下做开发,Ubuntu 20.04/22.04都行。Windows也能搞,但说实话,Linux的体验好太多,尤其是后面调试的时候。
2.2 下载EDK2源码
EDK2是TianoCore项目维护的UEFI参考实现。说白了,它就是UEFI开发的"标准库"加"构建系统"。
我个人习惯用git拉取,这样方便切换分支和查看历史。命令很简单:
git clone https://github.com/tianocore/edk2.git
cd edk2
git submodule update --init --recursive
嗯,这里要注意——submodule一定要更新。EDK2依赖了很多第三方库,比如openssl、brotli这些。不更新submodule,编译到一半就会报错,而且错误信息还不直观。
常见坑: 网络不好的时候,submodule更新容易超时。我建议用代理,或者直接下载release包。如果坚持用git,可以试试 git submodule update --init --recursive --depth 1 减少拉取深度。
版本方面,我推荐用 edk2-stable202311 这个tag。太新的版本可能有兼容性问题,太老的又缺功能。这个版本比较稳,我最近几个项目都在用。
git checkout edk2-stable202311
2.3 安装工具链
工具链的选择取决于你的宿主机。我主要讲Linux下的GCC工具链,这也是最主流的方案。
2.3.1 Linux (Ubuntu) 环境
先装基础依赖:
sudo apt update
sudo apt install build-essential uuid-dev nasm python3 python3-pip
这里有个细节——nasm 是汇编器,UEFI启动代码里有一部分是汇编写的,比如ResetVector。没有nasm,编译会直接挂掉。
然后装GCC交叉编译器。EDK2默认用 gcc-5 系列的工具链,但现代系统上装gcc-5有点麻烦。我建议直接用系统自带的gcc,然后设置环境变量告诉EDK2用哪个版本:
export GCC5_AARCH64_PREFIX=aarch64-linux-gnu-
export GCC5_ARM_PREFIX=arm-linux-gnueabihf-
export GCC5_RISCV64_PREFIX=riscv64-linux-gnu-
如果你只做x86平台,连交叉编译器都不用装。EDK2默认用宿主机的gcc编译x86代码。
2.3.2 Windows 环境
Windows下我推荐用 VS2019/2022 加 Python 3.10+。装好Visual Studio后,记得安装"使用C++的桌面开发"工作负载,还有"Windows SDK"。
我个人不太喜欢Windows下搞UEFI开发,主要是路径问题。EDK2对长路径支持不好,Windows的260字符限制经常触发。如果你非要用Windows,建议把源码放在 C:\edk2 这种短路径下。
| 平台 | 推荐工具链 | 安装方式 | 备注 |
|---|---|---|---|
| Linux x86 | GCC5 (宿主机gcc) | apt install gcc |
最简单,直接编译x86 UEFI |
| Linux ARM交叉编译 | GCC5 ARM | apt install gcc-arm-linux-gnueabihf |
需要设置 GCC5_ARM_PREFIX |
| Windows x86 | VS2019/2022 | Visual Studio Installer | 注意路径长度限制 |
| macOS | Xcode CLT + GCC | xcode-select --install |
macOS用户较少,社区支持一般 |
2.4 编译EDK2基础包
环境搭好了,我们来编译一个最小的UEFI固件——OVMF(Open Virtual Machine Firmware)。OVMF是UEFI固件在QEMU上的实现,说白了就是让虚拟机跑UEFI用的。
先设置环境变量:
cd edk2
source edksetup.sh
这个脚本会设置一堆环境变量,比如 WORKSPACE、PACKAGES_PATH 这些。我第一次用的时候没source,直接build,结果报错说找不到文件。嗯,这个坑我踩过。
然后修改配置文件:
vim Conf/target.txt
关键参数:
- ACTIVE_PLATFORM = OvmfPkg/OvmfPkgX64.dsc
- TARGET = DEBUG
- TARGET_ARCH = X64
- TOOL_CHAIN_TAG = GCC5
开始编译:
build -a X64 -t GCC5 -p OvmfPkg/OvmfPkgX64.dsc -b DEBUG
第一次编译会比较慢,大概10-20分钟,取决于你的机器。EDK2的编译系统会先编译一堆基础库,比如 MdePkg、MdeModulePkg 这些。之后改代码再编译就快多了,因为增量编译只编译改动的部分。
提速技巧: 用 -n 参数指定并行编译线程数,比如 build -n 8。我一般设成CPU核心数的1.5倍。另外,第一次编译后,后续可以用 build --flash-only 只生成固件镜像,省掉重新链接的时间。
编译成功后,在 Build/OvmfX64/DEBUG_GCC5/FV/ 目录下会生成 OVMF.fd 文件。这就是UEFI固件镜像,大概4MB左右。
2.5 QEMU模拟器配置与使用
QEMU是我们调试UEFI程序的主要工具。不用真机,直接在虚拟机里跑,效率高得多。
安装QEMU:
sudo apt install qemu-system-x86
启动OVMF固件:
qemu-system-x86_64 -bios Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd -m 1024 -nographic
你会看到UEFI Shell的界面。这就是UEFI的交互环境,可以执行命令、加载驱动、运行应用。
我个人常用的QEMU参数:
| 参数 | 作用 | 我的习惯 |
|---|---|---|
-bios |
指定UEFI固件文件 | 指向编译好的OVMF.fd |
-m |
虚拟机内存大小 | 1024MB起步,调试复杂应用用2048 |
-nographic |
纯命令行模式 | 调试时用,省资源 |
-drive file=fat:rw:./hda-contents |
挂载本地目录为虚拟磁盘 | 放.efi文件,方便测试 |
-s |
开启GDB调试端口 | 配合 -S 在启动时暂停等待调试器 |
调试技巧: 用 -drive file=fat:rw:./hda-contents 挂载一个本地目录,把你的.efi文件放进去。启动QEMU后,在UEFI Shell里直接 fs0: 切换到虚拟磁盘,然后运行你的程序。这样改代码→编译→复制文件→重启QEMU,整个循环非常快。
2.6 验证环境:编写第一个UEFI应用
环境搭好了,我们来写个"Hello World"验证一下。虽然简单,但能跑通就说明整个链路没问题。
在EDK2源码目录下创建应用:
mkdir -p MyHelloWorld
cd MyHelloWorld
创建 MyHelloWorld.c:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
Print(L"Hello, UEFI World!\n");
Print(L"UEFI开发环境搭建成功!\n");
return EFI_SUCCESS;
}
创建 MyHelloWorld.inf:
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyHelloWorld
FILE_GUID = 12345678-1234-1234-1234-123456789abc
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
MyHelloWorld.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
然后编译:
build -a X64 -t GCC5 -p OvmfPkg/OvmfPkgX64.dsc -b DEBUG -m MyHelloWorld/MyHelloWorld.inf
编译成功后,把生成的 Build/OvmfX64/DEBUG_GCC5/X64/MyHelloWorld.efi 复制到QEMU的虚拟磁盘目录,启动QEMU,在Shell里运行:
fs0:
MyHelloWorld.efi
看到"Hello, UEFI World!"了吗?恭喜,你的UEFI开发环境正式搭好了。
注意: 如果编译报错说找不到 UefiApplicationEntryPoint.h,检查一下你的 .inf 文件里 [LibraryClasses] 有没有写对。我曾经漏掉 UefiApplicationEntryPoint,结果卡了半小时。
2.7 常见问题与避坑指南
这些年帮不少人搭环境,总结几个高频问题:
- 编译报错 "No mapping for ..." —— 大概率是submodule没更新。重新跑
git submodule update --init --recursive。 - QEMU启动黑屏 —— 检查
-bios参数路径是否正确。OVMF.fd文件必须存在且可读。 - UEFI Shell里找不到文件 —— 确认虚拟磁盘挂载正确。用
map -r命令重新扫描设备。 - 编译速度太慢 —— 用
-n参数并行编译,或者只编译你需要的模块,不要全量编译。
我曾经有一次在Windows上编译,路径太深导致编译失败,折腾了一天才发现是260字符限制。从那以后,我所有UEFI项目都放在 C:\uefi 这种短路径下。
环境搭好了,下一步就是真正写UEFI代码了。别急,先把今天的内容消化掉,动手编译一遍。只有亲手跑通,才算真正掌握。