4、编写第一个UEFI程序:HelloWorld模块的创建、编译与运行
说实话,学UEFI开发最让人头疼的是什么?
不是代码有多难,而是你折腾了半天,连个"Hello World"都跑不出来。
我当年刚接触UEFI时,光搭建环境就花了两天。后来才发现,其实核心流程就那么几步。今天咱们就把这层窗户纸捅破。
4.1 准备工作:你需要什么?
在动手之前,先确认你手头有这些工具。我个人习惯用EDK2,这是Intel官方维护的UEFI开发套件,也是业界标准。
| 工具/组件 | 说明 | 获取方式 |
|---|---|---|
| EDK2源码 | UEFI开发框架,包含编译工具链和库 | git clone https://github.com/tianocore/edk2.git |
| Python 3.x | 用于运行编译脚本 | 官网下载或包管理器安装 |
| Visual Studio 2019+ | Windows下的C编译器(Linux用GCC) | Visual Studio官网 |
| QEMU模拟器 | 用于测试UEFI程序,不用真机 | qemu.org下载 |
4.2 创建HelloWorld模块
EDK2里每个模块都有自己的文件夹。咱们先创建一个。
进入EDK2源码目录,找到 MdeModulePkg/Applications 这个路径。这里存放着各种示例应用。我们在这里新建一个文件夹,取名 HelloWorld。
然后在这个文件夹里创建三个文件:
- HelloWorld.c —— 源代码文件
- HelloWorld.inf —— 模块描述文件(告诉编译系统怎么编译)
- HelloWorld.uni —— 字符串资源文件(可选,但建议加上)
4.3 编写源代码
打开 HelloWorld.c,写入以下代码:
#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.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;
}
这段代码很简单。每个UEFI应用的入口函数都是 UefiMain,它接收两个参数:ImageHandle 是模块自身的句柄,SystemTable 是系统表指针——通过它能调用UEFI提供的所有服务。
Print 函数是UEFI版的printf。注意前面的 L 前缀,表示宽字符串。UEFI内部统一使用Unicode编码。
4.4 编写模块描述文件(INF)
INF文件是EDK2的"身份证"。它告诉编译系统:这个模块叫什么、用哪些源文件、依赖哪些库。
创建 HelloWorld.inf,内容如下:
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = HelloWorld
FILE_GUID = 12345678-1234-1234-1234-123456789abc
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
HelloWorld.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
这里有几个字段要注意:
- FILE_GUID:每个UEFI模块必须有一个唯一的GUID。你可以用工具生成,也可以随便写一个(只要不冲突)。
- MODULE_TYPE:这里填
UEFI_APPLICATION,表示这是一个独立的应用,不是驱动。 - LibraryClasses:列出你用了哪些库。
UefiApplicationEntryPoint提供入口函数支持,UefiLib提供Print等常用函数。
4.5 编译HelloWorld
编译前先设置环境变量。打开"x64 Native Tools Command Prompt for VS 2019"(或者你用的VS版本),然后执行:
cd C:\edk2
edksetup.bat
build -p MdeModulePkg\MdeModulePkg.dsc -m MdeModulePkg\Applications\HelloWorld\HelloWorld.inf
解释一下这条命令:
-p指定平台描述文件(DSC),它定义了整个固件的模块组成。-m指定你要编译的模块路径。如果不加-m,会编译整个平台,那得等很久。
编译成功后,你会在 Build\MdeModulePkg\DEBUG_VS2019x64\X64\ 目录下找到 HelloWorld.efi 文件。这就是最终的UEFI可执行程序。
-j 8 参数可以并行编译,速度翻倍。比如 build -j 8 -p ...。我一般开8个线程,老机器别开太多,容易卡死。
4.6 在QEMU中运行
有了 .efi 文件,怎么跑呢?最简单的方式是用QEMU模拟UEFI环境。
首先,你需要一个UEFI固件镜像。EDK2自带了一个叫 OVMF.fd 的文件,它就是UEFI固件的模拟版。在 edk2\Build\OvmfX64\DEBUG_VS2019x64\FV\ 下可以找到。
然后执行:
qemu-system-x86_64 -bios OVMF.fd -hda fat:rw:.\HelloWorld
这条命令的意思是:用OVMF作为BIOS,把当前目录下的 HelloWorld 文件夹模拟成一个硬盘。QEMU启动后,会自动进入UEFI Shell。
在Shell里输入:
fs0:
HelloWorld.efi
屏幕上就会打印出:
Hello, UEFI World!
欢迎来到UEFI开发世界!
看到这行输出,恭喜你——你的第一个UEFI程序跑起来了!
4.7 核心逻辑流程图
下面这张图展示了从源码到运行的完整流程,我把它画成了SVG,方便你理解整个链路:
这张图把整个流程串起来了。你从左上角开始,一步步往下走,最后在右下角看到输出结果。嗯,其实没那么复杂,对吧?
4.8 常见问题与避坑
我整理了几个新手最容易踩的坑,都是我用时间换来的经验:
- 编译报错"找不到头文件":检查INF文件里的
[Packages]段有没有写对。少一个包,编译就过不去。 - QEMU启动后黑屏:大概率是OVMF.fd路径不对,或者没加
-bios参数。我曾经忘了加-bios,结果QEMU用默认的SeaBIOS启动,根本进不了UEFI Shell。 - Shell里找不到.efi文件:检查
-hda fat:rw:后面的路径。路径错了,QEMU就挂载不到你的文件。 - Print输出乱码:字符串前面忘了加
L前缀。UEFI用Unicode,不加L就是ASCII,打印出来全是问号。
.efi 文件放到ESP分区(EFI System Partition)的根目录下。我曾经手滑覆盖了 bootx64.efi,结果系统直接起不来了。建议放到一个子目录里测试。
好了,第一个UEFI程序就讲到这里。你现在应该能自己创建、编译并运行一个HelloWorld了。这个流程虽然简单,但它是后面所有UEFI开发的基础。把这个流程跑通了,后面的驱动、协议、图形界面,都是在这个框架上叠加的。
可可精品资料库