EDK II EmulatorPkg 实战指南:在 Linux 与 Windows 上构建、运行与调试 UEFI 模拟环境

原创2026-10-03 13:54:471,359 阅读
文章标签:固件嵌入式驱动开发

EDK II EmulatorPkg 实战指南:在 Linux 与 Windows 上构建、运行与调试 UEFI 模拟环境

本篇指南围绕 EDK II 仓库中的 EmulatorPkg 展开,讲解如何在无法运行完整 UEFI 固件的环境中(例如 Linux、macOS、Windows 等宿主操作系统上,以 OS 进程承载 UEFI 模拟环境)构建并启动一个完整的 UEFI 模拟器。读者将掌握基于 build 命令行与 build.sh 脚本的构建/运行方法、EmulatorPkg 平台描述(DSC/DEC/FDF)的关键配置项,以及从 Host 主程序到 SEC/PEI/DXE 各阶段的模拟启动链路与源码级调试手段。

EmulatorPkg 是什么

EmulatorPkg 是 EDK II 提供的一套"以 OS 进程模拟 UEFI 环境"的平台包。正如 EmulatorPkg/Readme.md 的 Overview 所述,它适用于"无法提供完整 UEFI 兼容环境"的场景,典型形态就是在操作系统上以一个普通应用进程承载整个 UEFI 模拟环境:UEFI 的固件卷(FV)被映射为进程地址空间中的一块区域,串口、磁盘、网络、图形输出等外设则分别由宿主机上的 tty、磁盘镜像/主机目录、网卡和 X11/Windows 窗口来模拟。

这种设计的直接收益是:在开发真实平台之前,可以先在模拟环境中调试单个 UEFI 模块。EmulatorPkg 的 DSC 文件开篇也明确说明了这一点——该平台可用来在创建真实平台前调试各个模块,同时它本身也是一个 DSC 编写范例(见 EmulatorPkg/EmulatorPkg.dsc 头注释)。

从源码结构看,模拟器的宿主侧分为两套实现:

Unix 版 Host 通过 X11(X11GraphicsWindow.c)、Pthreads(Pthreads.c)、POSIX 文件系统(PosixFileSystem.c)、块设备(BlockIo.c)以及 Linux/Berkeley 包过滤器实现 UEFI 设备的后端模拟。

支持平台与状态

根据 EmulatorPkg/Readme.md 的 Status 章节,EmulatorPkg 可以在以下环境中构建并运行:

  • POSIX 类环境 + X Window(Linux、OS X);
  • Windows 环境(Win10 已验证,Win8 未验证)。

在 EmulatorPkg/EmulatorPkg.dsc 末尾还内置了一张"操作系统/编译器兼容矩阵"(OS/Compiler Compatibility Matrix),比 Readme 更细致,是选择工具链的直接依据:

OS / 编译器 VS2019 / VS2022 / VS2026 CLANGPDB CLANGDWARF GCC / GCCNOLTO XCODE5
Windows / VS IA32/X64 — — — —
Windows / LLVM + VS — IA32/X64 — — —
Windows / LLVM + MSYS2 — — X64 — —
Windows / LLVM + MINGW — — IA32/X64 — —
Linux / LLVM — — IA32/X64 — —
Linux / GCC — — — IA32/X64 —
macOS / XCODE5 — — — — IA32/X64

DSC 中通过 !if / !error 对上述矩阵做了硬性校验:例如 Windows 下禁止 GCC/GCCNOLTO 与 CLANGDWARF,Linux 下禁止 VS20xx 与 CLANGPDB 工具链,构建时会直接报错退出。配套说明还给出了一些环境要求(来自 DSC 注释,属构建前提,请以实际环境为准):

  • Windows/LLVM 方案要求 LLVM 20.1.8 或更高版本(Visual Studio 自带的 LLVM 版本不满足要求);
  • Linux/GCC 方案建议 GCC 13 或更高(build.sh 中还有一条硬性检查:GCC 版本低于 8 直接退出并提示 "EmulatorPkg requires GCC8 or later");
  • 以 Ubuntu 24.04 构建 IA32/X64 双架构时,需要安装的依赖大致包括 build-essential uuid-dev lcov、gcc-13 系列、llvm-20 clang-20 lld-20 libclang-rt-20-dev、gcc-multilib g++-multilib libx11-dev libxext-dev 以及 libc6-i386 等 32 位兼容库。

构建 EmulatorPkg

使用 build 命令构建

EmulatorPkg/Readme.md 给出了四种最常用的构建组合,逐一继承如下。

Windows 下 32 位模拟器:

build -p EmulatorPkg\EmulatorPkg.dsc -t VS2026 -a IA32

Windows 下 64 位模拟器:

build -p EmulatorPkg\EmulatorPkg.dsc -t VS2026 -a X64

Linux 下 32 位模拟器:

build -p EmulatorPkg\EmulatorPkg.dsc -t GCC -a IA32

Linux 下 64 位模拟器:

build -p EmulatorPkg\EmulatorPkg.dsc -t GCC -a X64

参数含义:-p 指定平台描述文件(DSC),-t 指定工具链标签(Windows 为 VS2026,Linux 为 GCC),-a 指定目标架构(IA32 或 X64)。EmulatorPkg 声明支持的架构集合为 X64|IA32,构建目标为 DEBUG|RELEASE|NOOPT(见 EmulatorPkg/EmulatorPkg.dsc 的 [Defines] 段),因此也可以追加 -b RELEASE 或 -b NOOPT 选择其它构建目标。

DSC 中还预置了若干开关(DEFINE),可按需通过 -D 覆盖,例如:

  • NETWORK_SNP_ENABLE、NETWORK_IP6_ENABLE、NETWORK_TLS_ENABLE、NETWORK_HTTP_BOOT_ENABLE、NETWORK_HTTP_ENABLE、NETWORK_ISCSI_ENABLE:网络栈各组件,默认均为 FALSE;
  • SECURE_BOOT_ENABLE:默认 FALSE,置 TRUE 时平台会引入 AuthVariableLib、SecureBootConfigDxe 等安全启动组件,并切换 FDF 中变量区签名(gEfiAuthenticatedVariableGuid);
  • REDFISH_ENABLE:默认 FALSE,置 TRUE 时引入 Redfish 相关库与 RedfishPlatformConfig 应用(见 EmulatorPkg/Application/RedfishPlatformConfig)。

使用 build.sh 简化构建与运行

在支持 bash 的 POSIX 环境(Linux/macOS)下,EmulatorPkg/build.sh 封装了从工作区初始化、BaseTools 编译到构建/运行/清理的全流程。Readme 中的基本用法:

$ EmulatorPkg/build.sh        # 构建 + 运行
$ EmulatorPkg/build.sh run    # 仅运行(需先构建)

脚本默认构建架构与宿主机器架构一致:脚本通过 uname/uname -m 自动检测(x86_64 主机对应 X64,i386/i686 对应 IA32)。在 X64 主机上要构建并运行 IA32 模式,用 -a IA32:

$ EmulatorPkg/build.sh -a IA32
$ EmulatorPkg/build.sh -a IA32 run

从脚本源码(build.sh)可以确认更多受支持的参数:

参数 作用 默认值
-a <ARCH> 指定目标架构 IA32/X64 自动检测宿主架构
-b <TARGET> 指定构建目标 DEBUG/RELEASE/NOOPT DEBUG
-t <TOOLCHAIN> 指定工具链标签 Linux 为 GCC,macOS 为 XCODE5/XCLANG
run 只运行模拟器(Linux 下经 GDB 启动,macOS 下经 LLDB 启动) —
clean 清理构建产物 —
cleanall 清理 BaseTools 与 ShellPkg 等全部产物 —
其它参数 透传给 build(如 -D NETWORK_ENABLE=TRUE) —

脚本同时维护了几个平台级默认值:默认禁用网络(-D NETWORK_ENABLE=FALSE),默认构建 Build/Emulator$(ARCH)/DEBUG_$(TOOLCHAIN)/$(ARCH)/ 输出目录,并在 build/GenFv 不存在或 $EDK_TOOLS_PATH/Source/C/bin 缺失时自动执行 make -C BaseTools 编译基础工具。

运行模拟器

直接运行宿主程序

构建完成后,可执行文件位于输出目录下,Readme 给出的运行命令如下。

Windows 32 位模拟器:

cd Build\EmulatorIA32\DEBUG_VS2026\IA32\ && WinHost.exe

Windows 64 位模拟器:

cd Build\EmulatorX64\DEBUG_VS2026\X64\ && WinHost.exe

Linux 32 位模拟器:

cd Build/EmulatorIA32/DEBUG_GCC/IA32/ && ./Host

Linux 64 位模拟器:

cd Build/EmulatorX64/DEBUG_GCC/X64/ && ./Host

注意输出目录名由 DSC 中的 OUTPUT_DIRECTORY = Build/Emulator$(ARCH) 决定,目录中间的 DEBUG_$(TOOLCHAIN) 段会随 -b 与 -t 的取值变化(例如 RELEASE_GCC、DEBUG_VS2026)。

启动后,宿主进程会按 Host.c 的 main() 逻辑完成模拟环境初始化:解析 PcdEmuMemorySize 与 PcdEmuFirmwareVolume 等 PCD 配置、分配模拟内存与固件卷、构造 EMU_THUNK_PPI 并安装到 PPI 列表,然后跳转到 SEC 模块的 _ModuleEntryPoint()(EmulatorPkg/Sec/Sec.c),由 SEC 在恢复固件卷中查找 PEI Core 并转移控制权——即"OS 进程 -> SEC -> PEI Core -> DXE Core -> BDS -> UEFI Shell"的完整模拟启动链。

运行后能看到什么

模拟器启动后会弹出一个标题为 "GOP Window" 的图形窗口(窗口标题由 PcdEmuGop 配置),内部运行完整的 UEFI 图形栈(GraphicsConsole、SetupBrowser、UiApp 等)。平台把当前工作目录映射为 UEFI 文件系统(fs0),并把 FAT 驱动(FatPkg/EnhancedFatDxe)、Shell(ShellPkg/Application/Shell)编入固件卷,因此进入后可直接在 EFI Shell 中执行 ls fs0:、运行 HelloWorld 应用(MdeModulePkg/Application/HelloWorld 被默认编入)等操作,体验与真实 UEFI 平台一致。

平台配置:DSC 与 DEC 中的关键 PCD

EmulatorPkg 的模拟行为几乎全部由 gEmulatorPkgTokenSpaceGuid 下的 PCD 控制,默认值定义在 EmulatorPkg/EmulatorPkg.dec 与 EmulatorPkg/EmulatorPkg.dsc 的 [PcdsFixedAtBuild] 段。下表汇总了最常用的配置项:

PCD 默认值 含义
PcdEmuMemorySize L"64!64" 模拟内存大小,! 分隔多个内存区段,如 64!64 表示两块 64 MB 内存
PcdEmuFirmwareVolume L"../FV/FV_RECOVERY.fd" 恢复固件卷 FD 文件路径(DEC 中默认值含 ..\\Fv\\Fv_Recovery.fd,DSC 构建期覆盖为 ../FV/FV_RECOVERY.fd)
PcdEmuFirmwareFdSize 0x002a0000 固件卷 FD 大小(FDF 实际布局以 EmulatorPkg/EmulatorPkg.fdf 为准)
PcdEmuFirmwareBlockSize 0x10000 固件卷擦除块大小(64 KB)
PcdEmuBootMode 0(DSC)/1(DEC) 模拟启动模式,对应 BOOT_WITH_FULL_CONFIGURATION(0x00) 等 PI 启动模式宏
PcdEmuApCount L"1"(DSC)/L"0"(DEC) 模拟的 AP(应用处理器)数量,0 表示单处理器模式
PcdEmuVirtualDisk L"disk.dmg:FW" 模拟磁盘(镜像文件或设备节点),格式见下文
PcdEmuGop L"GOP Window" GOP 图形窗口标题
PcdEmuFileSystem L"." 映射为主机文件系统的目录
PcdEmuSerialPort L"/dev/ttyS0" 模拟串口对应的主机设备
PcdEmuNetworkInterface L"en0" 模拟网卡对应的主机网卡
PcdEmuCpuModel / PcdEmuCpuSpeed L"Intel(R) Processor Model" / L"3000" SMBIOS 中上报的 CPU 型号与主频(MHz)
PcdEmulatorLazyLoadSymbols FALSE(DSC)/TRUE(DEC) 是否仅在断点/GDB 入口处懒加载固件符号
PcdDefaultTerminalType 1 默认终端类型(0-PCANSI,1-VT100,2-VT100+,3-UTF8,4-TTYTERM)

其中 PcdEmuVirtualDisk 的取值语法在 EmulatorPkg/EmulatorPkg.dec 中有详细注释,可归纳为:

filename[:[R|F][O|W]][:BlockSize]
  • filename:可以是磁盘镜像文件,也可以是设备节点(如 /dev/disk1);
  • 介质类型:R 表示可移动介质,F 表示固定介质;
  • 写属性:O 表示写保护,W 表示可写;缺省为固定介质、可写;
  • BlockSize:仅对文件生效,缺省 512;例如 ISO CD 镜像应使用 2048。DSC 注释中还给出示例 L"diag.dmg:RO:2048" 用于 CD-ROM/DVD 场景;
  • 设备的大小与块大小由设备自身决定,不可通过该参数覆盖;
  • 在 macOS 上可使用 Disk Utility 创建 .dmg 文件并按磁盘方式挂载使用。

模拟器启动时由 Host 主程序解析这些字符串配置(见 Host.c 中读取 PcdEmuMemorySize/PcdEmuFirmwareVolume 并填充 gSystemMemory/gFdInfo 数组的逻辑),因此修改 DSC 中对应 PCD 并重新构建即可调整模拟内存、磁盘与网络。

固件卷布局:FDF 中的 FD 与 FV 划分

EmulatorPkg/EmulatorPkg.fdf 定义了名为 Fv_Recovery 的模拟固件卷(FLASH 设备)布局。FD 基地址由 PcdEmuFdBaseAddress 指定(0x102000000),整体划分为以下区域:

偏移 大小 用途
0x00000000 0x00c80000 恢复固件卷 FvRecovery(存放 PEI/DXE 全部模块)
0x00c80000 0x0000c000 NV 变量存储区(EFI_FIRMWARE_VOLUME_HEADER + VARIABLE_STORE_HEADER)
0x00c8c000 0x00002000 NV 事件日志区(Event Log)
0x00c8e000 0x00002000 容错写(FTW)工作区,含 FTW Working Block Header
0x00c90000 0x00010000 容错写(FTW)备用区

FDF 中通过 APRIORI PEI 与 APRIORI DXE 指定了 PEI/DXE 阶段的先行加载顺序(PEI 段依次加载 ReportStatusCodeRouter、StatusCodeHandler、PCD;DXE 段依次加载 PCD 与 Metronome),随后按 PEI 模块、DXE 模块、UEFI 驱动/应用的顺序列出全部 INF。模拟平台的外设驱动集中在这里,包括:

此外还有 EmulatorPkg/CpuRuntimeDxe、EmulatorPkg/TimerDxe、EmulatorPkg/RealTimeClockRuntimeDxe、EmulatorPkg/ResetRuntimeDxe、EmulatorPkg/FvbServicesRuntimeDxe、EmulatorPkg/PlatformSmbiosDxe 等模拟平台专属的 CPU、定时器、RTC、复位、FVB 与 SMBIOS 实现。变量、分区、文件系统等通用组件则直接复用 MdeModulePkg/FatPkg 的标准模块。

模拟机制与启动链路(源码视角)

从 EmulatorPkg/Sec/Sec.c 可以看到,模拟器的 SEC 是一个"寄生"在 OS 应用之上的 PEI Core 入口桩:宿主进程按 PEI Entry Point API 调用它,SEC 将宿主传入的 PPI 列表与自身的 mSecTemporaryRamSupportPpi 合并,然后在 BootFirmwareVolumeBase 指向的 FV 中用 PeiServicesFfsFindNextFile 查找 EFI_FV_FILETYPE_PEI_CORE 文件、取 PE32 段并 PeCoffLoaderGetEntryPoint 得到入口后跳转。SEC 打印的 "SEC Has Started\n" 是模拟器早期启动的第一条调试信息。

Unix 宿主侧的关键转换层是 Gasket(EmulatorPkg/Unix/Host/X64/Gasket.S 与 EmulatorPkg/Unix/Host/Ia32/Gasket.S),其职责是"在 Emu x86_64 ABI 与 EFI X64 ABI 之间转换"(IA32 版还负责 macOS 上的 16 字节栈对齐),即让 OS 调用约定与 UEFI 调用约定在进程内互通;SwitchStack.S/SwitchStack.c 则负责栈切换。这些源码可以从 HOST_APPLICATION 模块类型与 Host.inf 的 [Sources.X64]/[Sources.IA32] 段中得到印证。

PEI 阶段除标准 PEI Core 外,还包含一批模拟平台专属 PEIM:BootModePei(上报启动模式)、AutoScanPei(自动探测内存/FD)、FirmwareVolumePei(定位恢复 FV)、FlashMapPei(构建闪存映射 HOB)、ThunkPpiToProtocolPei(把 Host Thunk PPI 转成 DXE 期可用的协议),模块清单见 EmulatorPkg/EmulatorPkg.dsc 的 [Components] 段。

调试模拟器

模拟器最大的优势在于可以借助宿主机的成熟调试器对固件代码进行源码级调试。Unix 宿主下的 run 目标默认挂接 GDB:

  • Linux:build.sh run 实际执行 /usr/bin/gdb Build/.../Host -q -cd=... -x EmulatorPkg/Unix/GdbRun.sh(见 build.sh);
  • macOS:走 /usr/bin/lldb 并导入 EmulatorPkg/Unix/lldbefi.py 的 LLDB 脚本。

EmulatorPkg/Unix/GdbRun.sh 是 GDB 启动脚本,它做了几件事:预置 $_exitcode 并在 hook-stop 中检测程序退出后自动 quit;定义 AddFirmwareSymbolFile/RemoveFirmwareSymbolFile 宏按需加载/卸载固件符号文件;在 SecGdbScriptBreak 断点处 source Host.gdb。配合 PcdEmulatorLazyLoadSymbols(DSC 中默认关闭、DEC 中默认开启),可以控制固件符号是全部提前加载还是按断点懒加载,从而平衡调试体验与启动速度。Host 进程本身也会在启动时生成 Host.gdb 符号脚本(见 Host.c 中创建 gGdbWorkingFileName 的逻辑)。

对于 macOS 的 Xcode 用户,EmulatorPkg/Unix/Xcode 下提供了 32/64 位 Xcode 工程(含 XcodeBuild.sh 脚本),可直接在 IDE 内以断点脚本方式接入上述 GDB 流程。

使用 Platform CI(Pytools)构建

除传统 build 流程外,EmulatorPkg 还提供了一套基于 Pytools 的自动化构建入口,配置与用法见 EmulatorPkg/PlatformCI/ReadMe.md。Platform CI 支持四种固件构建配置:

配置名 架构 DSC 文件 附加标志
IA32 IA32 EmulatorPkg.dsc 无
X64 X64 EmulatorPkg.dsc 无
IA32 Full IA32 EmulatorPkg.dsc SECURE_BOOT_ENABLE=TRUE
X64 Full X64 EmulatorPkg.dsc SECURE_BOOT_ENABLE=TRUE

主要构建步骤(在仓库根目录执行):

# 1. 初始化子模块
stuart_setup -c EmulatorPkg/PlatformCI/PlatformBuild.py TOOL_CHAIN_TAG=<TOOL_CHAIN_TAG> -a <TARGET_ARCH>

# 2. 拉取外部依赖(ext_deps 变化时执行)
stuart_update -c EmulatorPkg/PlatformCI/PlatformBuild.py TOOL_CHAIN_TAG=<TOOL_CHAIN_TAG> -a <TARGET_ARCH>

# 3. 按需编译 BaseTools
python BaseTools/Edk2ToolsBuild.py -t <ToolChainTag>

# 4. 编译固件
stuart_build -c EmulatorPkg/PlatformCI/PlatformBuild.py TOOL_CHAIN_TAG=<TOOL_CHAIN_TAG> -a <TARGET_ARCH>

运行模拟器有两种方式:在构建命令末尾追加 --FlashRom(构建完成后自动启动),或使用 --FlashOnly(只启动已构建的模拟器,不重新构建):

stuart_build -c EmulatorPkg/PlatformCI/PlatformBuild.py TOOL_CHAIN_TAG=<TOOL_CHAIN_TAG> -a <TARGET_ARCH> --FlashOnly

Pytools 方式由 EmulatorPkg/PlatformCI/PlatformBuild.py 负责设置 ACTIVE_PLATFORM 与 TARGET_ARCH,因此无需手动修改 Conf/target.txt。向构建传入平台开关时,需在开关名前加 BLD_*_ 前缀并赋予值,例如开启 IP6 网络栈:

stuart_build -c EmulatorPkg/PlatformCI/PlatformBuild.py BLD_*_NETWORK_IP6_ENABLE=1

此外,MAKE_STARTUP_NSH=TRUE 会在 fs0 映射目录生成 startup.nsh,配合 --FlashOnly 可在 CI 中把模拟器跑到 UEFI Shell 后自动执行脚本内容。

常见注意事项

  • 工具链与平台必须匹配:EmulatorPkg 对 OS/编译器组合有硬校验(见 EmulatorPkg/EmulatorPkg.dsc 的兼容矩阵与 !error 检查),跨平台混用工具链会直接构建失败;
  • 32 位构建需要 32 位系统库:在 X64 Linux 上构建 IA32 模拟器需要安装 libc6-dev:i386、libx11-dev:i386、libxext-dev:i386 等 32 位库(Platform CI 文档有对应 apt 安装示例);
  • GCC 版本门槛:build.sh 对 GCC 主版本低于 8 的环境直接拒绝构建,建议使用较新的 GCC 工具链;
  • 先构建再运行:build.sh run 或 --FlashOnly 只负责启动,请确保对应 Build/Emulator$(ARCH)/ 目录下的 Host/WinHost.exe 已由前序构建生成;
  • 网络默认关闭:网络相关 PCD 与 NETWORK_ENABLE 默认均为 FALSE,如需 SNP 网卡模拟需显式开启(如 -D NETWORK_ENABLE=TRUE)并确认 PcdEmuNetworkInterface 指向真实可用的主机网卡(Windows 上为接口索引字符串,如 L"0")。

综上,EmulatorPkg 是 EDK II 中一套开箱即用的 UEFI 模拟平台:掌握 EmulatorPkg/Readme.md 中的构建/运行命令,再结合 EmulatorPkg.dsc 的 PCD 配置与 EmulatorPkg.fdf 的固件卷布局,即可在真实硬件之前完成 UEFI 模块的开发、验证与源码级调试。

登录后查看全文
edk2