1、UEFI概述与开发环境搭建

UEFI(Unified Extensible Firmware Interface,统一可扩展固件接口)是现代计算机系统启动过程中替代传统BIOS的行业标准固件规范。它定义了操作系统与平台固件之间的接口模型,为x86、ARM等架构提供了从加电自检到操作系统引导加载的标准化流程。理解UEFI与BIOS的本质差异、掌握EDK2(EFI Development Kit II)开发环境的搭建方法,是进入UEFI底层开发领域的第一道门槛。本章将从规范体系、环境搭建、编译运行三个维度,帮助读者建立UEFI开发的完整认知框架。

UEFI 开发体系全景图 UEFI 规范分层 EFI 系统表 (gST) 运行时服务 / 启动服务 协议 (Protocol) GUID 标识 / 接口函数 句柄数据库 句柄 / 协议绑定 DXE 阶段 驱动执行环境 PEI 阶段 内存初始化前环境 SEC → PEI → DXE → BDS → RT EDK2 开发环境 编译工具链 GCC / VS / CLANG 构建系统 build / make / nmake 平台描述文件 .dsc / .fdf / .inf 模拟器 (Emulator) NT32 / QEMU / OVMF 调试工具 GDB / WinDbg / Trace 输出产物 .efi / .fd / .fv / .bin 开发流程 ① 编写 .inf / .c / .h 模块描述 + 源码 ② 配置 .dsc 平台文件 声明模块参与构建 ③ 执行 build 命令 编译 + 链接 + 生成EFI ④ 拷贝至启动介质 FAT32 U盘 / 虚拟磁盘 ⑤ Shell 中运行 .efi UEFI Shell 加载执行 ⑥ 调试与验证 日志 / 断点 / 串口输出 规范驱动实现 → 工具链支撑 → 流程化开发

1.1 UEFI与BIOS的本质差异

传统BIOS(Basic Input/Output System)自1981年IBM PC时代延续至今,采用16位实模式运行,代码以汇编语言为主,中断调用方式(INT 13h、INT 10h等)深度绑定x86硬件。其启动流程固定为:加电→BIOS自检(POST)→读取主引导记录(MBR)→加载引导扇区→进入操作系统。这一设计存在三个根本性局限:第一,16位实模式只能访问1MB地址空间,无法利用大容量内存;第二,MBR分区表最大支持2TB磁盘;第三,BIOS固件代码不可扩展,厂商定制化程度低。

UEFI则彻底重构了固件与操作系统之间的交互模型。它运行在32位或64位保护模式下,支持完整的C语言开发环境,通过GUID标识的协议(Protocol)机制实现模块化扩展。UEFI启动流程分为SEC(安全验证)、PEI(EFI前初始化)、DXE(驱动执行环境)、BDS(启动设备选择)、TSL(临时系统加载)、RT(运行时)六个阶段。其中DXE阶段是驱动加载的核心,所有硬件驱动以EFI Driver的形式存在,通过句柄数据库统一管理。

对比维度传统BIOSUEFI
运行模式16位实模式32/64位保护模式
地址空间1MB限制完整物理地址空间
磁盘分区MBR(≤2TB)(支持大容量)
驱动模型固件内置,不可扩展协议驱动,模块化加载
开发语言汇编为主C语言为主
网络支持无原生支持内置TCP/IP协议栈
安全启动不支持Secure Boot签名验证

核心原理:UEFI的本质是定义了一套操作系统与固件之间的接口规范,而非具体的实现代码。它通过EFI System Table(系统表)暴露启动服务和运行时服务,通过Protocol机制实现设备驱动的即插即用。开发者编写的UEFI应用程序(如Shell下的.efi工具)本质上是一个PE32+格式的可执行文件,由UEFI固件直接加载运行。

1.2 UEFI规范体系架构

UEFI规范由UEFI论坛维护,当前最新版本为2.10。规范体系分为四个层次:底层是平台初始化标准(PI规范),定义了SEC、PEI、DXE各阶段的框架;中间层是UEFI核心规范,规定了系统表、启动服务、运行时服务、协议接口等核心内容;上层是应用层,包括UEFI Shell、启动管理器、设备驱动等;最顶层是操作系统加载器(OS Loader),负责将控制权移交给操作系统内核。

在开发实践中,开发者最常接触的是以下核心概念:句柄(Handle)是UEFI中标识设备或驱动实例的抽象对象;协议(Protocol)是接口的集合,通过GUID唯一标识,例如EFI_LOADED_IMAGE_PROTOCOL用于获取加载镜像信息;启动服务(Boot Services)在操作系统接管前可用,包括内存分配、事件处理、协议管理等;运行时服务(Runtime Services)在操作系统运行期间依然可用,如获取时间、变量存储等。

// 一个典型的UEFI应用程序入口函数
#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");
  return EFI_SUCCESS;
}

1.3 EDK2开发环境搭建(Windows/Linux)

EDK2是TianoCore社区维护的开源UEFI开发工具包,也是目前最主流的UEFI固件开发框架。搭建EDK2环境需要准备三部分:源码、编译工具链、构建工具。以下分别介绍Windows和Linux下的完整搭建流程。

1.3.1 Windows环境搭建

  1. 安装Visual Studio:推荐VS2019或VS2022,安装时勾选“使用C++的桌面开发”工作负载,确保包含MSVC编译器和Windows SDK。
  2. 安装Python:EDK2构建脚本依赖Python 3.8+,从python.org下载并添加到PATH。
  3. 安装Git:用于克隆EDK2源码仓库。
  4. 克隆源码:打开命令提示符,执行 git clone https://github.com/tianocore/edk2.git,进入edk2目录后执行 git submodule update --init 更新子模块。
  5. 配置环境变量:创建批处理文件或使用PowerShell设置 EDK_TOOLS_PATH 和 PATH。
  6. 编译BaseTools:在edk2目录下执行 edksetup.bat Rebuild,编译生成构建工具。

1.3.2 Linux环境搭建

# 安装依赖包(Ubuntu/Debian)
sudo apt-get update
sudo apt-get install -y build-essential git uuid-dev nasm python3 python3-pip

# 克隆EDK2源码
git clone https://github.com/tianocore/edk2.git
cd edk2
git submodule update --init

# 编译BaseTools
make -C BaseTools

# 设置环境
source edksetup.sh

经验提醒:Linux下推荐使用GCC5工具链,在 Conf/target.txt 中设置 TOOL_CHAIN_TAG = GCC5。如果编译过程中遇到缺少 nasm 汇编器,务必提前安装,否则编译汇编模块会报错。

1.4 编译运行第一个UEFI应用程序

环境搭建完成后,我们通过一个完整的实战案例来验证开发环境是否正常。本案例将创建一个打印系统表信息的UEFI应用程序,并在模拟器中运行。

1.4.1 创建模块文件

在edk2源码目录下创建 MyHelloPkg 包,包含以下文件结构:

MyHelloPkg/
├── MyHelloPkg.dsc          # 平台描述文件
├── MyHelloPkg.dec          # 包声明文件
└── Hello/
    ├── Hello.inf           # 模块描述文件
    ├── Hello.c             # 源代码
    └── Hello.h             # 头文件(可选)

Hello.inf 文件内容:

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = Hello
  FILE_GUID                      = 8D0E2C1A-2F3A-4B5C-9D8E-7F6A5B4C3D2E
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

[Sources]
  Hello.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiLib
  UefiApplicationEntryPoint
  UefiBootServicesTableLib
  PrintLib

Hello.c 源代码:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/PrintLib.h>

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_GUID  *VendorGuid;
  CHAR16    *VendorName;

  Print (L"=== UEFI Hello Application ===\n");
  Print (L"Firmware Vendor: %s\n", SystemTable->FirmwareVendor);
  Print (L"Firmware Revision: 0x%X\n", SystemTable->FirmwareRevision);
  Print (L"System Table Address: 0x%p\n", SystemTable);

  // 获取运行时服务中的变量
  VendorGuid = &gEfiGlobalVariableGuid;
  VendorName = L"PlatformLang";
  Print (L"Platform Language: ");
  Print (L"%s\n", VendorName);

  return EFI_SUCCESS;
}

1.4.2 配置平台文件

在 MyHelloPkg.dsc 中声明模块:

[Defines]
  PLATFORM_NAME                  = MyHelloPkg
  PLATFORM_GUID                  = 1A2B3C4D-5E6F-4A5B-8C9D-0E1F2A3B4C5D
  PLATFORM_VERSION               = 0.1
  DSC_SPECIFICATION              = 0x00010005
  OUTPUT_DIRECTORY               = Build/MyHelloPkg
  SUPPORTED_ARCHITECTURES        = IA32|X64
  BUILD_TARGETS                  = DEBUG|RELEASE
  SKUID_IDENTIFIER               = DEFAULT

[LibraryClasses]
  UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
  UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
  UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
  PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf

[Components]
  MyHelloPkg/Hello/Hello.inf

1.4.3 编译与运行

在edk2根目录执行以下命令:

# 设置环境(Windows)
edksetup.bat Rebuild
# 或 Linux
source edksetup.sh

# 编译平台
build -p MyHelloPkg/MyHelloPkg.dsc -a X64 -t GCC5 -b DEBUG

# 编译成功后,生成文件位于:
# Build/MyHelloPkg/DEBUG_GCC5/X64/Hello.efi

运行方式有两种:方式一,将 Hello.efi 拷贝到FAT32格式的U盘,插入支持UEFI启动的物理机,进入UEFI Shell后执行 Hello.efi;方式二,使用QEMU模拟器配合OVMF固件快速验证:

# 安装QEMU和OVMF(Ubuntu)
sudo apt install qemu-system-x86 ovmf

# 创建虚拟磁盘并拷贝EFI文件
qemu-img create -f raw disk.img 64M
mkfs.vfat disk.img
mount -o loop disk.img /mnt
mkdir -p /mnt/EFI/BOOT
cp Build/MyHelloPkg/DEBUG_GCC5/X64/Hello.efi /mnt/EFI/BOOT/BOOTX64.EFI
umount /mnt

# 启动QEMU
qemu-system-x86_64 -bios /usr/share/ovmf/OVMF.fd -hda disk.img -nographic

风险提示:在物理机上运行UEFI应用程序前,务必确认程序不会修改固件变量或执行危险操作。建议先在QEMU模拟器中验证程序行为,再部署到真实硬件。此外,UEFI Shell环境需要固件支持Shell功能,部分商用主板默认不提供Shell入口,需通过启动菜单手动选择。

1.5 常见误区与调试技巧

初学者在搭建UEFI开发环境时经常遇到以下问题:误区一,混淆EDK2与EDK(旧版),EDK2采用全新的构建系统和包结构,不能直接使用EDK的代码;误区二,忽略子模块更新,导致编译时缺少 MdeModulePkg 等关键包;误区三,在Windows下使用MinGW替代VS编译,虽然GCC可以编译,但部分工具链脚本对MSVC有依赖。

调试UEFI程序最有效的手段是串口输出和日志记录。在 Hello.inf 中添加 DebugLib 库,使用 DEBUG ((DEBUG_INFO, "Variable: %s\n", VarName)) 宏输出调试信息。同时,EDK2提供了 EmulatorPkg,可以在Windows/Linux桌面环境下直接模拟UEFI环境运行应用程序,无需真实硬件,非常适合开发初期验证逻辑。

总结复盘:本章完成了UEFI开发的第一块基石。读者应重点掌握:UEFI与BIOS的架构差异决定了开发思维的根本转变——从“中断调用”转向“协议接口”;EDK2环境的搭建核心在于工具链匹配和子模块完整性;第一个UEFI应用程序的编译运行验证了从源码到可执行文件的完整链路。建议读者在QEMU模拟器中反复练习,熟悉 build 命令的常用参数(-a 指定架构、-t 指定工具链、-b 指定构建类型),为后续驱动开发打下坚实基础。