编译 UEFI 程序入门:从 uefi-simple 到第一个 .EFI

整理编译第一个 UEFI .EFI 程序的入门思路:理解 UEFI 程序是什么、为什么推荐从 uefi-simple 开始、需要准备哪些工具,以及编译和测试时常见的坑。

编译第一个 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 程序的入门流程可以这样理解。

第一步,拿到示例项目。

1
git clone https://github.com/pbatard/uefi-simple.git

第二步,选择构建工具链。

如果用 Visual Studio,就按项目里的 Visual Studio 方案构建。
如果用 MinGW/gcc,就按项目提供的 Makefile 或说明走。

第三步,生成 .EFI 文件。

这里最关键的是确认目标架构。常见 PC 一般是 x86_64,也就是 64 位 UEFI 环境。

第四步,把 .EFI 放到可以被 UEFI Shell 访问的位置。

如果用真实机器,通常会准备一个 FAT32 分区或 U 盘。
如果用 QEMU,可以把目录或镜像挂载进去。

第五步,在 UEFI Shell 里运行。

运行效果通常就是一个最小输出,比如打印类似 Hello World 的内容。

最容易卡住的地方

编译 UEFI 程序最容易卡的不是 C 语言本身,而是环境和链接。

常见问题包括:

  • 编译器架构不对
  • 目标格式不对
  • 链接参数不完整
  • 缺少 UEFI 入口点
  • 生成的是普通可执行文件,不是 UEFI 能加载的 .EFI
  • QEMU 或 OVMF 没配置好
  • 真实机器 Secure Boot 阻止运行未签名程序

尤其是链接器报错,经常会让新手误以为代码写错了。
实际上,很多时候是入口函数、子系统、目标架构或链接脚本配置不对。

所以第一阶段不要急着改复杂逻辑。先确保原始示例能编译、能运行,再一点点改输出内容。

测试时为什么推荐 QEMU + OVMF

真实机器测试 UEFI 程序并不是不行,但新手阶段不太方便。

因为你可能需要反复:

  • 编译
  • 拷贝到 U 盘
  • 重启
  • 进入 UEFI Shell
  • 运行
  • 记录报错
  • 回到系统修改

这个循环很慢。

QEMU + OVMF 的好处是可以在操作系统里直接模拟 UEFI 环境。你可以更快地验证 .EFI 是否能被加载,也不容易影响真实机器启动项。

等程序基本跑通,再放到真实机器上测试,会更稳。

新手应该先改哪里

如果你已经用示例项目编译出了第一个 .EFI,下一步不要马上写复杂功能。

建议按这个顺序改:

  1. 先改输出文本,确认重新编译后的程序确实生效。
  2. 再尝试读取 UEFI 提供的简单信息。
  3. 再理解入口函数、输出协议和基本服务。
  4. 最后再考虑文件系统、图形输出、启动项管理等复杂功能。

这样做的好处是每一步都能验证。
如果一上来就改很多东西,出错时很难判断到底是代码问题、编译问题,还是运行环境问题。

和普通 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 的入口、协议和构建方式,会轻松很多。

记录并分享
使用 Hugo 构建
主题 StackJimmy 设计