2、UEFI开发环境搭建:EDK2的下载与编译、模拟器(QEMU)的使用、开发工具链配置

说实话,搭建开发环境这件事,看着简单,但坑是真不少。我见过太多人卡在环境配置上,代码还没写一行,先被编译错误折磨得想放弃。这一章,我就带你一步步把环境搭起来,顺便聊聊我这些年踩过的坑。

2.1 整体环境概览

先看看我们要搭什么。说白了,就是三样东西:EDK2源码、交叉编译工具链、QEMU模拟器。它们的关系是这样的:

核心流程: 编写UEFI驱动/应用 → EDK2编译系统 → 生成.efi文件 → QEMU加载运行 → 调试验证

我习惯在Linux下做开发,Ubuntu 20.04/22.04都行。Windows也能搞,但说实话,Linux的体验好太多,尤其是后面调试的时候。

UEFI开发环境整体架构 EDK2 源码仓库 UEFI规范参考实现 工具链 (GCC/VS) 交叉编译环境 QEMU 模拟器 OVMF + 虚拟硬件 Build 编译系统 (build命令 + .dsc/.fdf) 解析模块依赖 → 调用编译器 → 链接生成 .efi 输出:UEFI驱动/应用 (.efi) PE32+ 格式,可被UEFI固件加载 提示:EDK2 + 工具链 + QEMU 三者缺一不可,建议先全部装好再开始写代码

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代码了。别急,先把今天的内容消化掉,动手编译一遍。只有亲手跑通,才算真正掌握。