ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

EDK II编译实战:从环境搭建到OVMF固件生成的完整指南

EDK II编译实战:从环境搭建到OVMF固件生成的完整指南 1. 先把EDK II的构建链条讲明白——它编的不是一个普通程序2021年8月14日我在本地执行了人生中第一条真正意义上的EDK II完整编译命令。那天原本的计划很简单给一个模拟器场景准备UEFI固件环境顺便研究一下UEFI驱动加载的时序问题。没想到光是把环境跑通这一步就花了差不多一整天。回头复盘问题不出在代码上而出在不了解EDK II的构建逻辑上。很多第一次接触EDK II的朋友会把它当成普通的开源工程以为无非是装依赖、跑configure、make三步。实际上EDK II的构建体系完全是另一套玩法它没有CMake没有autotools甚至没有一个全局的Makefile。它依靠的是Tianocore项目自定义的一套构建工具链由Python脚本build.py驱动配合DSC、FDF、DEC、INF这几类描述文件最终把一堆C语言模块打包成固件卷FV或UEFI可执行文件EFI。用生活类比解释一下普通应用程序的编译就像是做一道菜源码是食材编译器是锅Makefile是菜谱最终出锅的是一盘菜一个exe或二进制。而EDK II的编译更像是在筹备一整套宴会——不只是做一道菜而是要统筹冷盘、热菜、汤品、甜点的制作顺序决定它们怎么装盘、怎么摆桌、桌布怎么铺。对应到固件领域就是你需要同时组织多个模块的编译并且决定了它们在固件存储空间里的布局方式、加载顺序、甚至地址归属。具体来说EDK II工程里最常见的四类文件DSCPlatform Description File定义整个平台要编译哪些模块相当于宴会菜单。FDFFlash Description File定义固件镜像的Flash布局哪个模块放在哪个偏移、哪个区域放FD/FV相当于摆桌方案。DECPackage Declaration File声明一个Package对外提供的头文件路径、协议GUID、PPI GUID、PCD等项目相当于对外接口文档。INFModule Information File描述单个模块的源文件、依赖库、编译选项相当于单个菜品的备料清单。这套设计的好处是模块之间通过GUID和协议进行松耦合平台开发者不需要改动每个模块就能重新组合整份固件。代价就是构建流程的学习曲线比传统开源项目陡峭得多——如果你不懂DSC和FDF的配合关系编译报错的时候会完全不知从哪查起。所以2021年8月14日那天的编译表面上看是跑通了一个构建流程本质上是搞懂了UEFI固件构建的逻辑骨架。这篇文章就把我当天走通的完整链路以及中间踩过的坑从头到尾拆开讲一遍。2. 动手编译之前先卡掉九成新手的两个前置条件2.1 Python版本不是装了就完事EDK II的BaseTools和build脚本从很早开始就依赖Python。2021年这个时间节点项目已经全面转向Python 3不再支持Python 2。但真正让人栽跟头的不是装没装Python而是版本兼容性。我当时的系统里默认装着Python 3.9一开始以为没问题结果运行时build.py报了一堆莫名其妙的异常。后来查资料发现EDK II官方对Python版本支持有几个隐含要求建议使用3.6.x到3.8.x之间的版本推荐3.8.x为主流测试版本同时要求Python的安装路径不要包含空格比如C:\Program Files\Python39这种就属于危险路径否则后续脚本拼接路径时可能出问题。最稳妥的方案是使用官方推荐范围内的版本比如Python 3.8.10。另外建议把Python安装到C:\Python38这种简洁路径并将C:\Python38和C:\Python38\Scripts加入PATH。注意在Windows环境下EDK II的BaseTools脚本可能同时调用32位和64位Python的兼容性问题所以还要避免只安装Windows Store版本的Python。用官方python.org安装包最省心。2.2 编译工具链Windows选VSLinux选GCC工具链的选择直接影响target.txt里的TOOL_CHAIN_TAG配置。2021年这个时间点主流的组合是操作系统推荐工具链TOOL_CHAIN_TAG说明WindowsVisual Studio 2019VS2019最常用编译Nt32Pkg、OvmfPkg都稳定WindowsVisual Studio 2017VS2017旧项目常用但新版本EDK II建议直接上VS2019WindowsMinGW GCCGCC5能编译但偶尔会遇到链接器兼容问题LinuxGCC 5.x以上GCC5EDK II对GCC的版本标识沿用GCC5这个tag不表示只能用5.x在Windows上我强烈建议用Visual Studio 2019而且是完整安装C桌面开发组件。只装了VS Code和轻量编译器的人会卡在第一个环节edksetup.bat运行后BaseTools需要调用nmake和cl.exe这些命令在普通CMD里并不存在必须先进入Developer PowerShell for VS 2019或手动执行vcvars64.bat才能让命令行识别到它们。如果你已经在普通CMD里执行了edksetup.bat大概率会看到这样的错误nmake 不是内部或外部命令也不是可运行的程序或批处理文件。这不是EDK II的问题是环境变量没加载。我踩过的坑是每次都打开普通CMD然后手动set PATH导致一会儿能用一会儿不能用。正确做法是从开始菜单直接启动x64 Native Tools Command Prompt for VS 2019在这个终端里再执行EDK II的编译命令。Linux环境相对简单安装gcc、make、uuid-dev、bison、flex、python3等基础包即可。不过Linux下编译OvmfPkg时如果缺少iaslIntel ACPI编译器也会报错需要额外安装acpica-tools。3. 从git clone到第一条有效build命令的完整走查3.1 拉取源码并切换稳定版本2021年8月EDK II的主分支还处于活跃开发状态。为了减少莫名其妙的问题我建议不要直接clone master分支而是checkout当时的稳定tag。我记得当时的最新stable是edk2-stable202105再往后是edk2-stable202108。稳妥一点用stable tag。git clone https://github.com/tianocore/edk2.git cd edk2 git checkout edk2-stable202105进入目录后先更新子模块。这一步很多新手容易漏因为克隆下来的仓库里MdeModulePkg、MdePkg等核心包的某些依赖是以submodule形式引用的。如果不更新编译到一半会报找不到头文件。git submodule update --init --recursive子模块下载量和仓库本身差不多网络不稳的时候容易中断。我当时中断过两次解决办法是反复执行同一条命令直到全部拉取完成。3.2 edksetup.bat到底做了什么Windows下cd到edk2根目录后执行edksetup.bat Rebuild这里有个容易忽略的细节加不加Rebuild参数差别很大。不加时如果已经生成过BaseTools脚本会直接复用加了Rebuild会强制重新编译BaseTools生成build.exe、GenFw、GenFds等一批工具软件。重要提示第一次执行时务必加Rebuild。我自己就吃过亏。某次改了Python版本后偷懒没加Rebuild结果BaseTools里的二进制还是旧版本Python编译的后面build命令各种串错。edksetup.bat的作用可以拆解为三步设置EDK_TOOLS_PATH、EDK_TOOLS_BIN等环境变量检测Python环境并用它去编译BaseTools源码若conf目录不存在则从BaseTools/Conf目录复制生成一套本地配置包括target.txt、tools_def.txt、build_rule.txt。3.3 修改target.txt的最小必要配置conf目录下的target.txt是每次编译最常改的文件。它相当于全局默认构建参数。打开后需要重点修改以下几行ACTIVE_PLATFORM Nt32Pkg/Nt32Pkg.dsc TARGET DEBUG TARGET_ARCH IA32 TOOL_CHAIN_TAG VS2019逐行解释ACTIVE_PLATFORM就是指定你要编哪个平台的DSC文件。Nt32Pkg是一个跑在Windows用户态模拟器上的平台包适合初学者验证整个构建流程。TARGET选DEBUG会附加调试信息和优化选项对应DEBUG优化等级是/Od生成固件体积更大但便于调试RELEASE则开启优化。TARGET_ARCHIA32是因为Nt32Pkg是32位模拟环境。如果你编OvmfPkg的X64版本这里要改成X64。TOOL_CHAIN_TAG必须和tools_def.txt里定义的工具链名称完全一致大小写都不能错。3.4 执行build命令与输出解读配置好target.txt之后直接运行build -n 8-n参数指定并行编译线程数建议不要超过CPU物理核心数乱拉满反而容易导致内存爆掉。我机器是6核12线程用-n 8实测稳定。编译成功时最后输出的关键信息类似- Build end time: 2021-08-14 16:22:33 - Build end time: 2021-08-14 16:22:33 Build Summary: Nt32Pkg ... Done中间会有大量Generating和Compiling的日志第一次跑不要嫌多这里面藏着后续排查问题的重要线索。编译产物默认在Build/Nt32IA32/DEBUG_VS2019/IA32/目录下其中包含了各模块的.efi文件以及模拟器运行目录所需要的文件集合。这里顺便提一个和热搜词编译原理相关的话题EDK II的DSC/INF解析过程本质上就是一种领域专用语言DSL的词法与语法分析。build.py读取DSC时会逐个token解析模块GUID、PCD赋值、库实例再根据依赖关系拓扑排序。如果你学过编译原理的词法分析和递归下降解析看它的日志会特别有感觉。这也是为什么很多固件老兵建议大学生别把编译原理当理论课——你迟早会在真实工程里遇到一个自定义格式解析的场景。4. 从Nt32Pkg到OvmfPkg把编译目标从模拟器拉回真实固件4.1 为什么Nt32Pkg不够用编完Nt32Pkg并成功运行后我一度以为EDK II编译就这回事了。实际并不是。Nt32Pkg跑在Windows进程里模拟的是一个有限的UEFI环境它不产出真正的固件镜像FD文件也不能像真实BIOS那样引导操作系统。它适合学UEFI驱动开发、调试模块逻辑但如果你想在QEMU里跑一台支持UEFI启动的虚拟机Nt32Pkg帮不上忙。这时候需要编译OvmfPkg。OVMFOpen Virtual Machine Firmware是专门为QEMU/KVM虚拟机设计的UEFI固件产物是一份完整的OVMF.fd可以直接当虚拟机的BIOS镜像加载。4.2 修改target.txt切换平台和架构把target.txt改成ACTIVE_PLATFORM OvmfPkg/OvmfPkgX64.dsc TARGET DEBUG TARGET_ARCH X64 TOOL_CHAIN_TAG VS2019这里有一处需要特别留意OvmfPkgX64.dsc的全名不能想当然写成OvmfPkg/X64.dsc而是OvmfPkg目录下明确存在的一个文件。如果不放心可以直接去OvmfPkg目录下ls一眼确认。重新执行build -n 8这次编译的模块量明显比Nt32Pkg多涉及SecCore、PeiCore、DxeCore、BdsDxe、Shell等一大串组件耗时也会长很多。我第一次编大约花了20分钟后来加内存条后提速明显。4.3 用QEMU验证OVMF固件编译成功后在Build/OvmfX64/DEBUG_VS2019/FV/目录下能看到OVMF.fd、OVMF_CODE.fd、OVMF_VARS.fd等文件。为了验证产物能不能跑我装了QEMU然后执行qemu-system-x86_64 -bios Build/OvmfX64/DEBUG_VS2019/FV/OVMF.fd -m 2048 -cpu qemu64如果固件正常QEMU窗口会进入UEFI Shell或者直接进入UEFI交互菜单输出UEFI Interactive Shell字样。这一跑通才算从编译成功走到了固件可用。这个环节也要顺带解释一下为什么同样一份EDK II源码能编译出Nt32模拟器用的EFI程序也能编译出OVMF固件答案就在DSC和FDF的差异上。Nt32Pkg.dsc确定的是跑在Windows进程里的模块集合Nt32.fdf则规定了一个模拟Flash布局OvmfPkg.dsc和OvmfPkg.fdf则描述了一套符合物理闪存格式的固件卷。模块代码本身可以高度复用平台的差异全由描述文件表达。这就是EDK II解耦思想的核心体现。4.4 实体机固件和OVMF编译的认知差别编译过OVMF之后再去看那些做实体板卡UEFI移植的人你就知道他们每天在调的都是什么了。OVMF虽然是虚拟机固件但它的模块划分、Flash布局、变量存储区域设计跟真实BIOS的套路基本一致。做完OvmfPkg的编译再去看Intel的MinPlatform、或者各种SoC厂商的UEFI代码包你至少能认得目录结构和构建入口在哪里。5. 我把常见报错挨个踩了一遍原因与排查链路5.1 nmake 不是内部或外部命令这个问题几乎人人都会遇到本质是工具链环境未加载。build.py在调用TOOL_CHAIN_TAG对应的封装命令时会尝试执行nmake然而普通CMD里没有VS的路径。排查链路确认是不是在VS开发者终端里执行的命令。在开始菜单搜索x64 Native Tools Command Prompt for VS 2019启动后再cd到edk2目录执行build。确认tools_def.txt中对应工具链是否存在且名称一致。如果tools_def.txt里没定义VS2019会报Toolchain [VS2019] is not installed而不是nmake相关错误这反过来也能帮你区分问题方向。确认edksetup.bat设置的环境变量在当前终端存在。执行echo %EDK_TOOLS_PATH%如果是空值说明你换了一个终端窗口之前的配置丢了。5.2 Python版本不匹配导致的BaseTools编译失败BaseTools在Windows下的编译实际上是先用nmake调用C编译器把C语言工具编出来再用Python脚本串起来。如果你Python版本太高比如3.11或者太旧比如2.7在运行edksetup.bat Rebuild时就会在编译脚本环节报错。我遇到的情况是系统默认Python是3.9edksetup.bat执行到某一步时提示找不到某个模块。查到最后发现是Python的include目录和EDK II源码里的C头文件互相调用出了版本兼容问题。换成Python 3.8.10后这个问题没有再出现过。排查建议在edk2根目录执行python --version确认版本如果系统存在多版本Python用py -3.8或python3.8显式指定。5.3 conf目录的坏缓存问题EDK II会缓存一些构建中间信息在conf目录下。如果你改了ACTIVE_PLATFORM但build时读到的还是上一个平台的配置多半是conf下的缓存没刷新。删除Build目录和conf下的中间文件是最常见的修复手段。build -p OvmfPkg/OvmfPkgX64.dsc -a X64 -t VS2019 -b DEBUG -n 8如果这个命令报错build.py: error: Conf directory was not created说明edksetup.bat还没有执行过。顺序一定不能反。5.4 多线程编译导致的内存飙升EDK II编译时-n参数调整的是并行任务数但每个任务都可能同时占用相当大的内存尤其是链接阶段。我一开始图省事在8GB内存的旧机器上直接用-n 16结果编译到大概60%的时候系统彻底卡死只能强制重启。此后我的策略是内存16GB以下的机器用-n 416GB以上的机器用-n 8。如果需要快速验证单个模块也可以不加-n参数直接单线程编译速度慢点但绝对稳定。5.5 路径过长导致的文件找不到Windows默认路径长度限制是260字符EDK II的嵌套目录结构非常深很容易触到上限。尤其是Build目录下的中间文件路径动辄就是一两百个字符。解决办法有两种把edk2仓库放在一个很浅的路径下比如C:\edk2而不是C:\Users\你的名字\Documents\Projects\firmware\edk2这种深路径。开启Windows长路径支持。Win10 1607以上版本可以在注册表或组策略中启用LongPathsEnabled但考虑到兼容性我建议直接用浅路径方案省事且不依赖系统版本。5.6 提示缺少iaslACPI编译器如果你编译OvmfPkg时缺iasl日志里会看到类似GenFw: error: cant find iasl的字样。这是EDK II中的ACPI源文件编译依赖。Windows下需要下载Intel ACPI Source Language编译器并加入PATH或者用VS自带的Linux下用包管理器安装sudo apt install acpica-tools装完之后不用重新edksetup直接再build即可。6. 一个值得保留的编译习惯从能编过到编得明白现在很多人编译EDK II直接抄一条build命令就开跑。如果顺利当然好一旦出问题就懵。我个人的经验是每次编译前先花两分钟在脑子里过一遍这次编译的目标是什么。如果你要开发一个UEFI驱动编译目标是一个特定模块。这种场景不一定需要全平台编译可以用build命令指定模块build -p MdeModulePkg/MdeModulePkg.dsc -m MdeModulePkg/Universal/Disk/Gpt.c -a X64 -t VS2019 -b DEBUG这种按模块编译的方式通常十几秒到几十秒就能出结果比每次全量编译整个平台高效一个量级。日常调试和验证驱动逻辑时强烈推荐。如果你要做固件集成目标才是整个平台镜像。这时候编译时间可以按分钟计适合配合自动化脚本持续集成。要注意的一点是修改一个模块的INF或源码后build工具会做增量编译不会每次全量重编。这个增量逻辑有时候也会坑人你觉得改了文件但实际没编进去。对策是改完文件后看一眼日志里有没有对应模块的Compiling行。再补充一个实用技巧EDK II支持生成编译数据库文件类似compile_commands.json。用起来非常顺手IDE的代码跳转和静态检查都能直接基于它工作。我自己在2021年8月14日那天沉淀下来的习惯后来一直沿用到现在每次编译成功后都在Build目录外单独存一份target.txt和build命令的记录。别小看这个动作固件开发项目周期长几个月后再回来看同一套源代码配置参数早就忘了。把这些信息固化下来比什么知识库都实在。EDK II的编译体系确实比普通开源项目繁琐但也正因为这套DSC/FDF/INF的抽象UEFI世界才能做到一套代码多种平台。熬过第一次编译后面的路就顺了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进