4、UEFI Shell环境:Shell基础命令、Shell脚本编写、设备路径与映射

UEFI Shell,说白了就是你在固件层面的“命令行”。没有操作系统,没有图形界面,你面对的就是一个Shell>提示符。很多工程师觉得这东西过时了,其实不然。我在调试早期启动阶段的Bug时,Shell往往是唯一能用的工具。你想想看,系统连Windows都没加载,你怎么看内存?怎么改变量?怎么确认设备有没有被识别?

这一章,我们就来把Shell环境彻底搞明白。我会从基础命令讲起,再到脚本编写,最后聊聊设备路径和映射——这部分是很多人的盲区,但恰恰是UEFI调试的核心。

4.1 进入UEFI Shell的几种方式

先说说怎么进去。不同厂商的BIOS入口不太一样,但原理相通。

方式 操作步骤 适用场景
BIOS内置Shell 启动时按F2/Del进入BIOS,找到"Boot to Shell"或类似选项 大多数消费级主板
UEFI Shell.efi文件 将Shell.efi放在FAT32格式的U盘,从U盘启动 定制固件、嵌入式开发
固件调试串口 通过串口重定向进入Shell 服务器、开发板调试
Boot Manager菜单 启动时按F12选择"UEFI Shell" 部分品牌机

我的建议: 手边常备一个FAT32格式的U盘,里面放好Shell.efi和几个常用工具。我在项目现场遇到过BIOS里根本没有Shell入口的情况,全靠这个U盘救命。

4.2 Shell基础命令——够用就行

Shell命令不需要全记住,常用的就那么十几个。我列个清单,你照着练一遍就熟了。

4.2.1 文件与目录操作

Shell> fs0:                    # 切换到第一个文件系统
Shell> ls                      # 列出当前目录内容
Shell> ls -l                   # 详细列表(带大小、属性)
Shell> cd \EFI\BOOT            # 切换目录(注意是反斜杠)
Shell> dir                     # 和ls一样,DOS习惯
Shell> type startup.nsh        # 查看文件内容
Shell> edit startup.nsh        # 编辑文件(简易文本编辑器)
Shell> cp file1 file2          # 复制文件
Shell> rm file1                # 删除文件
Shell> mkdir mydir             # 创建目录

注意: UEFI Shell的路径分隔符是反斜杠\,不是Linux的斜杠/。我刚开始时总搞混,结果命令一直报错。另外,文件名不区分大小写,但建议保持统一风格。

4.2.2 系统信息与诊断

Shell> memmap                  # 查看内存映射
Shell> drivers                 # 列出所有加载的驱动
Shell> dh                      # 显示设备句柄
Shell> devtree                 # 设备树(非常有用)
Shell> bcfg boot dump          # 查看启动项列表
Shell> reset                   # 重启系统

memmap这个命令我几乎每次调试都会用。它告诉你哪些内存区域是常规内存,哪些是保留的,哪些是ACPI的。有一次客户说系统在某个地址写数据会崩溃,我一查memmap,发现那块区域被标记为MMIO——根本就不是普通内存。

4.2.3 环境变量操作

Shell> set                     # 列出所有环境变量
Shell> set MyVar HelloWorld    # 设置变量
Shell> set MyVar               # 查看变量值
Shell> set -d MyVar            # 删除变量

环境变量在Shell脚本里特别重要。比如你可以用set Lasterror来检查上一条命令的执行结果。

4.3 Shell脚本编写——自动化你的调试流程

手动敲命令太慢了。遇到重复性工作,写个脚本一劳永逸。

4.3.1 脚本文件格式

Shell脚本就是普通的文本文件,扩展名是.nsh。启动时Shell会自动执行startup.nsh,这个机制很实用。

# 这是一个简单的脚本示例
# 文件名为 test.nsh

echo "=== 系统信息收集脚本 ==="
echo "内存映射:"
memmap
echo ""
echo "设备树:"
devtree
echo ""
echo "启动项:"
bcfg boot dump
echo "脚本执行完毕"

4.3.2 变量与参数

# 使用参数
echo "第一个参数: %1"
echo "第二个参数: %2"
echo "所有参数: %*"

# 变量赋值与使用
set MyPath fs0:\EFI\BOOT
echo "路径是: %MyPath%"
cd %MyPath%

关键点: Shell脚本里变量引用用%变量名%,不是$变量名。这个和DOS批处理一样,和Linux Shell不一样。我见过不少工程师在这里栽跟头。

4.3.3 控制流

Shell脚本支持基本的条件判断和循环,虽然功能有限,但够用。

# if-else 判断
if %1 == "debug" then
  echo "进入调试模式"
  set DebugMode 1
else
  echo "正常模式"
  set DebugMode 0
endif

# for 循环
for %a run (1 2 3 4 5)
  echo "当前计数: %a"
endfor

# goto 跳转(不推荐,但有时候不得不用)
goto :Start
:End
echo "结束"
goto :EOF
:Start
echo "开始"
goto :End

我的习惯: 写脚本时先画个简单的流程图,再动手写。Shell脚本的调试手段有限,出错了只能靠echo打印信息。我曾经写过一个30行的脚本,因为少写了一个endif,排查了半小时。

4.4 设备路径与映射——理解UEFI的“盘符”逻辑

这部分是很多人的知识盲区。为什么有时候fs0:能访问,有时候不行?为什么U盘插上去不显示?

4.4.1 设备路径是什么

设备路径是UEFI用来唯一标识一个设备的方式。它是一串结构化的数据,描述了设备在系统中的位置。

# 查看设备路径
Shell> dh -d

# 输出示例
# 设备路径: PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0x12345678,0x800,0x10000)

这个路径的意思是:从PCI根桥开始,经过PCI设备0x1F功能2,再到SATA端口0,最后是硬盘的第一个分区。每个节点都对应一个硬件层次。

4.4.2 映射——从设备路径到盘符

Shell用fs0:、fs1:这样的映射来简化操作。你可以用map命令查看所有映射。

Shell> map

# 输出示例
# Mapping table
#   fs0: :PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0x12345678,0x800,0x10000)
#   fs1: :PciRoot(0x0)/Pci(0x14,0x0)/Usb(0x0,0x0)/HD(1,MBR,0x87654321,0x800,0x10000)
#   blk0: :PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)
#   blk1: :PciRoot(0x0)/Pci(0x14,0x0)/Usb(0x0,0x0)

fs代表文件系统(File System),blk代表块设备(Block Device)。只有格式化为FAT12/16/32的设备才能挂载为fs。

避坑指南: 我曾经遇到过一个问题——U盘插上去,map命令能看到blk1:,但就是没有fs1:。查了半天,发现U盘是NTFS格式的。UEFI Shell只认FAT系列文件系统。NTFS、exFAT都不行。解决办法:把U盘重新格式化为FAT32。

4.4.3 手动创建映射

有时候系统没有自动映射,你可以手动创建。

# 手动映射块设备到文件系统
Shell> map -r fs0: blk1:

# 或者用设备路径映射
Shell> map fs2: PciRoot(0x0)/Pci(0x14,0x0)/Usb(0x0,0x0)/HD(1,MBR,0x87654321,0x800,0x10000)

4.4.4 设备路径的典型结构

路径节点 含义 示例
PciRoot PCI根桥 PciRoot(0x0)
Pci PCI设备 Pci(0x1F,0x2)
Sata SATA端口 Sata(0x0,0xFFFF,0x0)
Usb USB设备 Usb(0x0,0x0)
HD 硬盘分区 HD(1,MBR,0x12345678,0x800,0x10000)
CDROM 光驱 CDROM(0x0)
MAC 网卡MAC地址 MAC(001122334455)

4.5 实战:用Shell排查启动问题

讲个真实案例。有一次,一台服务器启动后直接进入Shell,不加载操作系统。客户很着急。

我的排查步骤:

  1. 先看启动项:bcfg boot dump — 发现启动项列表是空的。
  2. 检查设备:map — 能看到fs0:,是系统盘。
  3. 查看文件:ls fs0:\EFI\BOOT — 发现bootx64.efi存在。
  4. 手动启动:fs0:\EFI\BOOT\bootx64.efi — 系统正常启动了。

问题找到了:启动项丢失,但引导文件还在。修复方法是用bcfg boot add命令把启动项加回去。

# 添加启动项
Shell> bcfg boot add 0 fs0:\EFI\BOOT\bootx64.efi "Windows Boot Manager"

重启后,一切正常。整个过程不到10分钟,全靠Shell。

4.6 本章知识体系

下面这张图帮你理清本章的核心脉络:

UEFI Shell 知识体系 进入 Shell 基础命令 脚本编写 设备路径与映射 文件操作 系统诊断 环境变量 .nsh 文件格式 变量与参数 控制流 (if/for/goto) 路径结构解析 map 映射管理 手动创建映射 实战:排查启动问题 查看启动项 → 检查设备 → 手动启动 → 修复 核心原则 Shell 是 UEFI 固件层的调试利器,掌握它等于掌握了系统启动的“最后一公里”

总结一下: UEFI Shell不是摆设,它是你排查启动问题的最强工具。基础命令要熟练到肌肉记忆,脚本能帮你自动化重复工作,设备路径和映射是理解UEFI设备模型的关键。这三块掌握了,你就能在固件层游刃有余。