3. UEFI Shell环境搭建

要开发 UEFI Shell 脚本,第一步不是写脚本,而是先让一个可交互的 UEFI Shell 跑起来。UEFI Shell 脚本依赖 UEFI 固件提供的运行环境、文件系统协议和 Shell 内置命令,因此环境搭建的质量直接决定后续调试效率。本节围绕四条主线展开:QEMU/OVMF 虚拟环境、真实机 UEFI Shell、EDK2 模拟环境,以及启动 UEFI Shell 的多种方式。

一、核心原理:UEFI Shell 运行在什么之上

UEFI Shell 本身是一个 UEFI 应用程序(Shell.efi),它由固件加载并执行,运行在 UEFI Boot Services 之上。它能够访问块设备、简单文件系统、控制台输入输出等协议,因此脚本可以操作 fs0:、fs1: 这类映射卷,也可以调用 memmap、dh、devtree 等命令观察系统状态。理解这一点后,环境搭建的本质就清晰了:准备一个能加载并运行 Shell.efi 的 UEFI 固件环境,并让脚本文件能被 Shell 访问到。

UEFI Shell 环境搭建的四条路径与启动链 QEMU + OVMF 虚拟固件,快速迭代 真实机 UEFI Shell 贴近硬件,验证兼容 EDK2 模拟环境 源码级调试,可断点 启动方式选择 U盘 / 硬盘 / 网络 / 内置 UEFI 固件(OVMF / 主板固件 / 模拟固件) 提供 Boot Services、协议与设备访问能力 加载 Shell.efi 从 fs0:、内置固件或网络路径加载 UEFI Shell 交互界面 执行 .nsh 脚本、调用内置命令、访问映射卷 startup.nsh / 自定义脚本 fs0:\efi\boot\bootx64.efi

二、QEMU/OVMF:最常用的虚拟环境

QEMU 配合 OVMF(Open Virtual Machine Firmware)是学习 UEFI Shell 脚本的首选。OVMF 是 EDK2 构建出的开源 UEFI 固件,QEMU 提供虚拟硬件,两者组合可以在几秒内启动一个完整的 UEFI 环境。典型做法是准备一个 FAT 格式的虚拟磁盘镜像,把 Shell.efi 和脚本放进去,然后让 QEMU 从该磁盘启动。

# 创建 64MB FAT 镜像并挂载
dd if=/dev/zero of=uefi.img bs=1M count=64
mkfs.vfat uefi.img
mkdir -p mnt
sudo mount -o loop uefi.img mnt
sudo mkdir -p mnt/efi/boot
sudo cp Shell.efi mnt/efi/boot/bootx64.efi
sudo cp startup.nsh mnt/
sudo umount mnt

# 启动 QEMU + OVMF
qemu-system-x86_64 \
  -machine q35 \
  -m 1024 \
  -drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \
  -drive if=pflash,format=raw,file=OVMF_VARS.fd \
  -drive format=raw,file=uefi.img \
  -nographic

其中 OVMF_CODE.fd 是固件代码,OVMF_VARS.fd 保存 UEFI 变量,二者分离可以避免每次启动都重置启动项。-nographic 把串口重定向到终端,适合在服务器或 CI 环境中使用。若需要图形界面,可去掉该参数并添加 -vnc :1 或使用 GTK 显示。

建议把 OVMF_VARS.fd 复制一份作为模板,每次实验前重新拷贝,避免启动项被污染导致 Shell 无法自动进入。

三、真实机 UEFI Shell 与 EDK2 模拟环境

真实机环境更贴近最终部署场景。常见做法是把 FAT32 格式的 U 盘制作成可启动盘,目录结构为 \EFI\BOOT\BOOTX64.EFI,其中 BOOTX64.EFI 就是 Shell.efi 的重命名副本。开机时按 F2/F12/Esc 进入固件设置或启动菜单,选择该 U 盘即可进入 Shell。真实机的优势是能验证脚本在真实硬件、真实固件实现下的行为,例如某些主板对 fs0: 映射顺序、变量存储、网络协议栈的支持差异。

EDK2 模拟环境则面向源码级调试。通过 EmulatorPkg 或 OvmfPkg 构建,可以在宿主操作系统上以进程方式运行 UEFI 固件,配合 GDB 或 WinDbg 对 Shell 脚本调用的底层协议进行断点跟踪。它适合排查脚本执行失败、协议返回错误码等深层问题,但搭建成本高于 QEMU/OVMF。

环境类型 搭建成本 调试能力 适用场景
QEMU + OVMF 低 串口日志、GDB 可选 日常脚本开发、快速验证
真实机 UEFI Shell 中 依赖固件自带调试手段 硬件兼容性验证、最终部署
EDK2 模拟环境 高 源码级断点、协议跟踪 底层问题定位、固件开发

四、启动 UEFI Shell 的多种方式

进入 Shell 的方式并不唯一,常见有以下几种:

  • 固件内置 Shell:部分主板固件自带 Shell,开机按特定按键或从启动菜单选择即可进入,无需外部介质。
  • U 盘启动:把 Shell.efi 放到 \EFI\BOOT\BOOTX64.EFI,从启动菜单选择 U 盘。
  • 硬盘 ESP 分区:在系统 ESP 分区中放置 Shell.efi,通过 bcfg 或固件启动项添加条目。
  • 网络启动:通过 PXE 或 HTTP Boot 加载 Shell.efi,适合批量部署与远程维护。
  • QEMU 直接指定:使用 -kernel Shell.efi 或把 Shell.efi 作为启动镜像加载。

无论哪种方式,进入 Shell 后第一件事通常是确认卷映射与文件可见性:

Shell> map -r
Shell> fs0:
FS0:\> ls
FS0:\> startup.nsh

map -r 会重新扫描并刷新文件系统映射,fs0: 切换到第一个可访问卷,ls 查看内容,最后执行脚本。若脚本没有自动运行,检查 startup.nsh 是否位于当前卷根目录,以及固件是否启用了自动执行。

常见误区:把 Shell.efi 直接放在 U 盘根目录而非 \EFI\BOOT\ 下,导致固件找不到启动文件;或者镜像未使用 FAT 格式,UEFI 固件无法识别文件系统。另一个高频问题是 OVMF_VARS.fd 被写坏后启动项丢失,表现为直接进入固件设置界面而非 Shell。

五、复盘与建议

环境搭建的稳定性比速度更重要。建议固定一套 QEMU/OVMF 启动脚本,把镜像制作、变量文件拷贝、QEMU 启动封装成一条命令,减少重复操作带来的变量。真实机验证时,优先使用独立 U 盘,避免误改系统 ESP 分区。EDK2 模拟环境仅在需要源码级调试时启用,日常脚本开发不必引入过高复杂度。环境就绪后,再进入 Shell 脚本语法与命令的学习,才能把精力集中在脚本逻辑本身。