夜班电话响起来的那一刻,我就知道又没好事。客户那边一台裸金属服务器突然失联,控制台登录不进去,机器反复重启,连操作系统都选不出来了。我抱着笔记本和一块小 U 盘赶到机房,插上 IPMI,看到的信息只有“SEL 里有一条 Abnormal Power Cycle”,具体是哪个硬件拉垮了完全没头绪。那一刻我特别希望手头有个能直接在 UEFI 环境里把整台机器过一遍自检的工具,不依赖操作系统,不用等厂商诊断镜像,插上盘就能跑,跑完能告诉我问题出在哪。
后来我花了三个多月,自己写了一个这样的工具:一个基于 UEFI 的整机自检程序,集成了 21 项硬件测试,全程可视化菜单操作,测试结束后一键生成报告。裸金属硬件故障排查本来就是运维的老大难,而大多数免费工具要么单一测内存,要么只能绑某个品牌,要么得先进系统再诊断——系统都起不来了还怎么进。所以这篇我把我为什么写、怎么设计这 21 项测试、底层实现思路、以及实测中踩过的兼容性大坑,一次性说清楚。适合的数据中心运维、服务器测试同学,还有那些平时喜欢折腾二手服务器的朋友,看完至少能少走几个星期弯路。
1. 裸金属排障的“最后一公里”,为什么我选择在 UEFI 下做自检
1.1 系统起不来的时候,排障手段其实非常缺乏
裸金属服务器和云主机最大的区别就是没有“重置虚机”这个按钮,出了问题得有人去现场。而最常见的故障恰恰是那种让你进不了操作系统的:内存报错导致 Boot 循环、CPU 某个核心挂掉、BIOS 配置被改坏、磁盘控制器识别不到、板载网卡固件异常。这堆问题有一个共同点——Linux 还没起来,journalctl、smartctl、mcelog全都没法用。你唯一能依赖的就是固件 POST 阶段的报错声音、自检日志,以及 IPMI 里那几行极其简陋的 SEL 记录。
真正动手排查的时候,你手里其实没有多少免费工具。MemTest86 免费版确实能用,但它只测内存,而且想一次性跑完四个小时,纯粹碰运气。SMART 类工具需要引导进一个 Live Linux,本来系统就起不来,再引导一个 live 环境又增加了一层不确定性。服务器厂商自带诊断工具不错,比如戴尔的 ePSA、惠普的 UEFI 诊断,但它们只对自家机器友好,你手里若是混着三五家品牌,维护一堆启动盘就很痛苦。我当时的想法就一个:我需要一个不挑品牌、不挑 CPU 厂商、能在 UEFI 阶段把所有关键硬件快速过一遍的工具,最好绿色免费,扔 U 盘上就能跑。
1.2 为什么选 UEFI 而不是 Linux live CD 或 PXE
去年还有朋友问我,为什么不直接把一个精简的 Linux 内核放 U 盘里启动,然后跑脚本?这个问题真问到点子上了。Linux live 环境确实能干很多事,但有一个致命短板:如果故障本身发生在 CPU 内存控制器、PCIe 枚举或者 ACPI 表阶段,Linux 内核还没起来就已经失败了。你看到的只有一个黑屏或者 kernel panic,然后什么都测不了。而且 live CD 还要把根文件系统整个加载到内存,等它加载完,一次排障的黄金时间已经过去十几分钟。
UEFI 应用跟这些不一样。它是纯固件阶段运行的 PE32+ 可执行程序,直接和 UEFI 固件暴露的 Protocol 打交道。内存初始化已经完成,PCIe 总线已经枚举,存储和网络控制器的硬件资源也已经分配好,但操作系统还没介入。这个阶段正好卡在“硬件能不能通过固件基础自检”和“操作系统能不能接管”之间,非常适合做硬件健康验证。更关键的是,UEFI 应用程序体积很小,几百 KB,放到 FAT32 U盘上就能被引导,不需要镜像,不需要加载复杂驱动,哪怕故障机是完全裸的状态也能跑起来。
当然,我也不是没考虑过 PXE 批量部署诊断系统。但 PXE 需要 DHCP 服务器、TFTP、HTTP 镜像,还要确保故障机的网卡能正常初始化。说实话,网卡本身也是最容易坏的部件之一,你拿 PXE 去诊断网卡,一旦网卡故障整个方案就死循环了。U 盘启动就没有这个问题,一个 USB 口一个 FAT32 分区,物理接触永远比网络链路可靠。
2. 21 项测试从哪来:既不是拍脑袋,也不能太贪心
2.1 测试项全名单与分类
写工具最容易犯的毛病就是想全都测,结果每项都是浅尝辄止。我给自己定的原则很简单:每一项测试都必须对应一类真实见过的故障,测完之后要么能判 PASS,要么能指出具体怀疑对象。最后定下来的 21 项,分成五组。
| 编号 | 测试项 | 分类 | 主要检测目标 |
|---|---|---|---|
| 1 | UEFI 固件版本与厂商信息 | 基础信息 | 固件异常、版本管理 |
| 2 | SMBIOS 系统配置读取 | 基础信息 | 系统型号、序列号、UUID 有效性 |
| 3 | CPU 型号、核心/线程识别 | 基础信息 | CPU 识别错乱、ES 工程片 |
| 4 | CPU 基础算术与浮点运算 | CPU | ALU/FPU 稳定性、热量导致的计算错乱 |
| 5 | CPU 缓存与频率检测 | CPU | 缓存失效、降频异常 |
| 6 | 内存容量与 SPD 信息读取 | 内存 | 内存条 SPD 损坏、容量识别错误 |
| 7 | 内存快速 Pattern 读写测试 | 内存 | 数据线/存储单元故障 |
| 8 | 内存长时间压力扫描(可选) | 内存 | 高温下内存稳定性 |
| 9 | 主板芯片组与 SATA 控制器识别 | 存储 | 控制器丢失、固件错误 |
| 10 | NVMe/SATA/USB 存储枚举 | 存储 | 设备掉盘、控制器异常 |
| 11 | 磁盘 SMART 信息读取 | 存储 | SSD/HDD 健康度预警 |
| 12 | 磁盘固定模式读写校验(非破坏性) | 存储 | 寻道、扇区读取异常 |
| 13 | 网络控制器与 MAC 地址读取 | 网络 | 网卡编号错误、固件丢失 |
| 14 | 网卡链路自协商测试 | 网络 | PHY 芯片故障、物理链路断开 |
| 15 | PCIe 设备枚举与错误记录 | 外设 | 插槽接触不良、PCIe AER 错误 |
| 16 | 串口控制器与环回测试 | 外设 | 板载串口芯片失效 |
| 17 | USB 控制器基础枚举测试 | 外设 | USB 控制器供电异常 |
| 18 | GPU/VGA 设备基础识别 | 外设 | 显卡不识别、PCIe 电源问题 |
| 19 | 温度传感器读取(CPU/板载/环境) | 环境 | 散热失效、传感器开路 |
| 20 | 风扇转速读取与 PWM 控制验证 | 环境 | 风扇堵转、PWM 控制异常 |
| 21 | RTC 时钟与掉电保存测试 | 环境 | CMOS 电池失效、RTC 走时不准 |
2.2 每项背后的故障模型:为什么值得写进工具
很多人看到第 4 项“CPU 基础运算测试”觉得太简单了,跑个加法和乘法能说明什么?实际上我见过不止一次 CPU 在 UEFI 阶段计算结果就出错的情况,通常是散热器没装好导致高频下计算错误,系统进到一半就崩溃。这种问题用 memtest 测不出来,用 mcelog 又看不到,但一个几百毫秒的浮点重复计算就能让它现形。
内存测试更是重头戏。第 7 项快速 Pattern 读写,不是单纯反复读写一遍完事,而是用地址线反转、数据线 Walking 1/0、随机 Pattern 组合去扫。这样能同时覆盖内存颗粒的数据单元故障和地址译码故障。为了在排障现场快速得到结论,默认只跑两遍,大约 5 分钟就能完成 32GB 内存的基本扫描。如果跑出来有问题,再触发第 8 项长时间压力扫描,循环使用同一种 Pattern 持续加热内存条,把那些只在高温下才暴露的假负载问题揪出来。
第 5 项“CPU 缓存与频率检测”,其实是从超频失败的折腾经历里总结出来的。以前经常遇到一台机器“平时能用,一跑高并发就崩”,进系统看频率也不对,后来才发现 UEFI 默认设置把 BCLK 拉偏了。所以在工具里专门加了频率识别,如果读取到的当前频率和 CPU 标称频率差得太多,直接标 WARN,比进系统之后再对日志要直观得多。
存储测试这边我特意强调“非破坏性”。第 12 项固定模式读写校验,只会读取磁盘上已有的数据扇区,并写入临时缓存做校验,不会往盘上写数据。实在需要写入验证,我会单独在内置的“高级菜单”里告诉你这一步会破坏指定磁盘数据,默认不启用。排障工具第一原则就是别让二次故障扩大。
2.3 被刻意排除的测试项
21 项听起来很多,实际上我还砍了不少。比如 GPU 3D 压力测试,这个在服务器排障场景真的没有意义,板载显卡只要能出画面就够了,跑深度学习卡的机器一般都有独立阵列诊断工具。再比如长时间硬盘烧机测试,那不是排障该干的事,那是产线老化应该干的事,我只保留能快速判断“这块盘是不是坏盘”的测试。还有网络层协议栈测试,比如 DHCP 获取、HTTP 连通性,我特意没有加——因为 UEFI 底下的网络栈本来就很简陋,容易误报,更重要的是排障阶段根本不需要这么多层协议,链路层通不代表驱动层通,链路层不通那网卡基本可以换了。
3. 无操作系统环境的可视化:UEFI 图形界面从零搭起来
3.1 先别急着写界面,UEFI 程序的运行环境得先弄清楚
我第一次写 UEFI 程序的时候,以为这玩意儿跟在 Linux 底下写个 ncurses 程序差不多,结果被协议的概念教育了一整个星期。UEFI 程序不是普通可执行文件,它是由固件加载到内存里的 PE32+ 文件,入口函数接收一个EFI_SYSTEM_TABLE指针,所有功能都要通过这个系统表去查各种 Protocol。比如你想画点东西,得先通过LocateProtocol去拿EFI_GRAPHICS_OUTPUT_PROTOCOL(GOP),拿到之后才能访问 framebuffer,往像素里填颜色。
开发环境我用的是 EDK2,这也是目前唯一主流的选择。编译目标选X64,生成.efi文件,放到 U 盘的EFI/BOOT/bootx64.efi路径下,开机固件就会自动识别并引导。整个过程我没有直接拿真机调试,而是在开发机上用 QEMU 配合 OVMF(开源的 UEFI 固件)做模拟,把界面、按键、文件读写逻辑都验证到八九成再拿到真机上跑。没有这套模拟环境,调试效率至少低五倍。
3.2 用 GOP 画出一屏可交互界面
UEFI 下做界面有两个层级:低层是拿到 GOP 直接操作 framebuffer,高层是用ConOut这个字符输出 Protocol 打印文字。ConOut确实省事,但字体大小、颜色控制都有限,而且不同固件默认字体差异很大,做不出现代一点的进度条。我最后选择直接用 GOP 画界面,自己维护一个 800x600 或 1024x768 的逻辑分辨率缓冲区。
具体做法其实不神秘:先根据系统支持的最高分辨率初始化 GOP,然后用一组绘图函数去填充矩形、画边框、绘制字符。字符部分是把一个 8x16 的点阵字库嵌进程序里,虽然一开始麻烦,但换来的是在任何机器上都能显示一致的界面。菜单、按钮、测试列表、进度条全是这几个基础函数拼出来的。这个方案唯一的缺点是分辨率切换可能需要重启才生效,所以界面上特意加了一个“按空格切换内置 800x600 缩放”的兼容模式。
3.3 键盘交互、进度显示与超时控制
交互逻辑用的是最简单的状态机。主菜单有三个选项:全项自检、单项测试、查看上次报告。用户通过上下方向键选择,回车确认,ESC 返回。UEFI 底下的键盘输入接口是SimpleTextInputEx,轮询按键队列,每次屏幕刷新时检测有没有新按键。这里有个坑:xHCI 控制器管理的 USB 键盘在没有驱动的时候可能不可用,所以我在初始化阶段会先把 USB Host Controller 的 Protocol 加载好,不然插着 USB 键盘也会冻在菜单界面。
进度显示这一块,我一开始犯了一个错误——按测试数量平均分配百分比。结果 CPU 测试只要 2 秒,内存压力测试要跑 1 小时,进度条走得毫无意义。后来改成每项测试单独设置预期权重,比如内存压力测试权重是 40,CPU 测试权重只有 3,总进度按已完项目实际耗时动态计算。同时每项测试右边会显示当前状态:等待中、运行中、通过、失败、跳过。这样用户在机房现场扫一眼就知道现在卡在哪一步。
超时控制也经历过一次教训。网络链路自协商测试依赖 PHY 芯片响应,在坏网卡上可能会一直等待,固件底层自协商超时时间不定。我在工具里给所有涉及硬件等待的测试都加了一级兜底超时,比如等待连拉·链路上秒最高 8 秒,读不到传感器值 1 秒后放弃,不会让整台机器卡死在一个测试项上。关键的失败项可以选择“停止后续测试”或“跳过继续”,后来发现“跳过继续”才是大多数运维的选择——他们想在一次重启里拿到尽量多的信息,而不是修好一个问题重启再来一遍。
4. 一键出报告:这个功能让我改了 6 个版本
4.1 报告给谁看?决定用纯文本还是 HTML
“一键出报告”听起来很简单,但设计成什么样才算好用,我是改了六个版本才定型的。第一版只是把测试结果一行行打印到屏幕,结果现场一断电全没。第二版把结果写到 U 盘 TXT 文件,可用性是有了,但字段排版很难看。第三版加大了对齐和缩进。第四版加了 CSV 输出,方便我自己用脚本统计多台机器的测试结果。第五版有人建议做 HTML,我试了一下,UEFI 下要拼大量转义字符串实在太痛,而且真正在处理故障的人只想快速 grep 关键字,谁要一个打不开的 HTML。第六版最终定型为纯文本对齐格式 + 同名 CSV,一个给人看,一个给机器分析。
你看我最后生成的报告头长这样:
============================================================ UEFI Hardware Diagnostic Report Generate Time : 2025-06-18 03:22:41 Machine : PowerEdge R630 / S/N: 9X8K0V3 UEFI Version : 2.12.2205 CPU : Intel Xeon E5-2680 v4 @2.40GHz (28 threads) Memory : 8 x 16GB DDR4-2400 ECC (Total 128GB) ============================================================ [1] UEFI Firmware Info : PASS [2] SMBIOS Config : PASS | Serial=9X8K0V3 UUID=... [3] CPU Model/Threads : PASS | 2.40GHz / 28 threads ...为什么要刻意把固件版本和机器序列号放最前面?因为现场拍照传回办公室的时候,第一眼就得知道是哪台机器、什么固件版本。没有这个信息,后面所有测试数据都会变成一个没有坐标的孤点。
4.2 FAT32 存储与日志防丢失
报告最终写入的介质就是启动 U 盘本身,文件系统必须是 FAT32。纯 UEFI 环境对 NTFS 是不认的,Windows 默认格式化 U 盘如果是 exFAT 或 NTFS,插上去连读都读不到。还有个小细节:每个测试项的结果不是等全部测试完了才写,而是每完成一项就立刻追加写入一个.log文件,最后再汇总成最终报告。这样就算测试中途意外断电,你已经跑完的那 15 项结果都还在。这个设计是我有一次在测试中把故障机的电源排插踢了之后痛定思痛补上的。
报告文件名也要防止覆盖。同一台机器如果在一天内测两次,第二次报告不应覆盖第一次。我在文件名里加上日期和时区偏移,比如diag_report_20250618_032241.txt,后面的 CSV 用同一个时间戳前缀,方便配套归档。如果 U 盘根目录已经存在同名文件,会在时间戳后加_1再写一个副本,绝不直接覆盖。
4.3 一个失败用例如何被完整记录
报告里出现 FAIL 或 WARN 时,需要记录的东西比我一开始想象的多得多。内存测试失败不光要写“Memory test: FAIL”,还要把失败的内存条插槽号、物理地址、数据 Pattern、期待值和实际值都写进日志。磁盘读取失败需要记录设备路径和 LBA 扇区号。网卡自协商失败需要记录 PHY 的 link status 寄存器和已有的连接速度。信息越完整,现场拿着报告的人就越容易直接定位问题,不用再掏螺丝刀反复拔插测试。
某些测试一旦失败,后续测试已经没意义。比如内存总线都报错的时候,接下去跑压力测试只会继续刷屏报错。工具会在关键测试失败后弹出一个选择:继续全检、跳过同类、立即终止并生成报告。绝大多数情况下,我在现场会选“立即终止”然后拍张照发给硬件供应商,已经够了。
5. 从三台 Supermicro、一台 HPE 到一台自组服务器:兼容性实测记录
5.1 UEFI 实现存在“自由发挥”的空间
我一直以为 UEFI 标准定得那么细,不同品牌的实现应该差不多。真开始拿真机测试才发现,标准只是最低共识,各家固件在SMBIOS 表、ACPI 表、PCIe 配置空间和传感器读取接口上的自由发挥程度远超想象。Supermicro 的板子有很完整的 IPMI KCS 接口,通过 BMC 可以拿到传感器和 SEL;HPE 则在自己的 Redfish RESTful 接口之外,UEFI 阶段的传感器读取走的是私有 HII 变量,直接读 ACPI 表经常拿到空指针。好在这类差异都有现成的 Protocol 可以去适配,只是需要为不同平台写不同的分支。
自组服务器平台也很典型,比如消费级 Z790 主板的 ACPI 传感器实现跟厂商服务器板卡就不是一套思路。工具在初始化传感器时先枚举可用接口,然后按顺序尝试:IPMI/KCS、ACPI thermal zone、超级 I/O 芯片直读。哪个能出数据就先用哪个,全部失败就把这项标记成 N/A 并记录原因——这总比报一个假的 0 摄氏温度好得多。
5.2 我遇到过的 5 个平台相关坑
- GOP 分辨率差异:某些老服务器的 GOP 驱动只支持 800x600,强行切 1920x1080 会花屏或黑屏。最终代码会在启动时查询所有支持的模式,选第一个不低于 800x600 且 BPP 为 32 的模式;如果连 32 位色都没有,直接切成 8 位色模式,画出来的界面丑但能看清。
- SMBIOS 表未对齐:有一台 HPE 机器上,固件返回的 SMBIOS 2 Entry Point 在内存中的地址不是标准要求的 16 字节对齐,按文档偏移读会错位。后来在解析前加了地址掩码修正,依然不兼容就把原始表导出来分析。
- UEFI Shell 版本影响启动脚本:我一开始想用
startup.nsh做自动启动,结果 UEFI Shell 2.2 和 3.0 对 echo 和注释的解析方式有差异,某些机器上直接不执行。后来放弃脚本,把 EFI 程序直接放到默认引导路径EFI/BOOT/bootx64.efi,固件开机就会自动找,绕开 Shell 的解析。 - NVMe 盘在 UEFI 阶段 SMART 读取困难:有的 NVMe 控制器没有实现标准的 ATA pass-through 接口,SMART 指令发过去直接超时。这个没办法完全绕过,只能识别到是 NVMe 协议栈的问题,在报告里提示“SMART not supported by this NVMe driver”,然后手动触发设备自检命令。
- 传感器值返回值未初始化:某台自组服务器在读取温度传感器时,PCIe 配置空间里对应的地址还没有被电压管理模块启动,读回来全是 0xFF。这会误报成 429 摄氏度,直接把风扇拉满。我后来加了数据有效性检查:传感器值必须落在厂家预设合理范围内才显示,否则标 N/A 并附加原始寄存器值。
5.3 处理兼容性的原则:宁降级不拒绝
从这些坑里总结出一个核心原则:UEFI 工具不需要在所有机器上都完美,但不能因为兼容性问题整体拒绝运行。图形界面初始化失败我就回退到字符界面;传感器读取失败就标 N/A,但前面 20 项测试照常跑。报告里每一项都带着数据来源接口,方便用户和固件厂商做比对。对运维来说,一个“部分结果缺失但稳定可跑”的工具,远远强过一个“在某些机器上直接黑屏”的工具。这也是为什么我在工具里增加了一个“兼容模式”开关,把它做成启动后 3 秒默认不按键进入全自动测试;如果需要图形界面,再在开机菜单里手动按一下进去。
6. 手把手跑一遍完整流程:从启动盘制作到报告解读
6.1 制作启动 U 盘与启动项配置
我这里直接把步骤给到可以直接抄作业的程度:
- 准备一个 2GB 以上 U 盘,格式化为 FAT32。Windows 下右键格式化选 FAT32;Linux 下用
mkfs.fat /dev/sdX1。 - 把编译好的
bootx64.efi文件放到 U 盘的EFI/BOOT/bootx64.efi位置。 - 开机进入 Boot Manager(一般是 F11、F12、F2 之类),选择带有“UEFI”前缀的 U 盘启动项,注意别选“Legacy”或“BIOS”开头的那个。
- 如果主板不支持 UEFI 启动,那这个工具真没法跑,因为它是纯 UEFI 应用程序。遇到这种情况还是老老实实用传统方法,或者先刷固件让平台支持 UEFI。
我这里顺便回复一下很多人在搜的问题:UEFI 引导 U 盘用 FAT32 还是 NTFS?标准 UEFI 固件原生支持的是 FAT 系列,FAT32 是兼容性最好的选择。NTFS 需要单独的第三方驱动,不建议放在启动路径里。exFAT 同样不是标准支持项,老老实实 FAT32 就对了。
6.2 测试中的交互与耗时
工具启动后会先做一次 2 秒的硬件枚举,枚举期间会显示检测进度条,然后进入主界面。全项测试默认顺序是按照从“基础信息”到“传感环境”的排障逻辑进行的:先把机器身份搞清楚,再测计算和存储,最后测外部接口和散热。用户在菜单里选择“全项自检”,确认后程序开始跑,每一项右上角会实时显示当时状态。
在常见配置下,基础信息 5 项一共用时约 3 秒,CPU 运算与缓存测试约 20 秒,快速内存 Pattern 按 128GB 内存大约 8 分钟(如果启用长期压力扫描就完全取决于测试时长选项),磁盘 SMART 和非破坏性读取校验按磁盘数量 2~4 分钟,网络与外设测试约 40 秒,传感器和 RTC 约 5 秒。整套快速模式大概 12 分钟到 15 分钟。如果遇到坏盘,磁盘读取校验可能会超时,每块坏盘最多等待 2 分钟就跳过并写 WARN,不会因为一块盘拖住整个流程。
跑完最后一项,程序会在界面中央提示“测试完成,按 Enter 键生成报告”,同时自动把实时日志写到 U 盘。这一步我们称之为“一键出报告”,实际上测试完成后不按键也会在 10 秒后自动生成。如果现场太暗,直接拔 U 盘去办公室看报告也可以,日志已经写进去了。
6.3 报告字段怎么读:逐块拆解
报告拿到手,我一般先看几个关键字段:
- 基本信息里的
Memory行,如果容量小于物理插入总容量,优先怀疑某根内存槽没认到。 - CPU 运算如果是 FAIL,先查散热器、再查 BIOS 频率设置。
- SMART 状态如果出现
Raw_Read_Error_Rate或Reallocated_Sector_Ct的数值比正常基线明显偏高,那块盘可以直接排队换。 - 网卡链路自协商失败,但没有接网线,那你需要知道工具检测的是 PHY 是否 link,不是物理网线是否插入。这时候报告里会有一个
PHY Link State: Down的提示,接上网线如果还是 Down,才怀疑网卡芯片。 - 风扇报告如果显示 PWM 达到了 100% 但转速读数还是 0,大概率是风扇堵转或者转速信号线断了。
- RTC 测试主要看两件事:当前时间和 UTC 参考时间的误差,以及写入一个测试值后断电 3 秒重开后是否保留。如果丢失,CMOS 电池基本废了。
看到 FAIL 不要慌,并不是每个 FAIL 都得换硬件。内存测试报错可以先把报错插槽内存条换一根;PCIe 枚举错误有可能是显卡供电线松动,重新插一次再跑一遍就好。报告的意义不是判别死刑,而是缩小怀疑范围。
7. 如果你也想写一个 UEFI 工具,这些教训请收好
7.1 开发环境与参考资源
整个工具基于 EDK2 开发,编译环境我是在 Ubuntu 上用 GCC 交叉编译的。EDK2 的代码结构对新手不太友好,但搞清楚OvmfPkg(QEMU 用的 OVMF 固件)和MdeModulePkg里的示例之后,上手就快了。我自己的建议调试路径是:先编译出 QEMU 能跑的 OVMF,再用 QEMU 引导你的.efi文件,在真机上调试前至少先在 OVMF 里把界面和文件读写跑通。
用到的核心 Protocol 其实就这几个:EFI_GRAPHICS_OUTPUT_PROTOCOL(GOP 画界面)、EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL(键盘)、EFI_SIMPLE_FILE_SYSTEM_PROTOCOL(读写 U 盘)、EFI_PCI_IO_PROTOCOL(访问 PCIe 配置空间)、EFI_ATA_PASS_THRU_PROTOCOL或EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL(读磁盘 SMART)。每一个 Protocol 在 UEFI Specification 里都有详细的 C 结构定义,直接查 EDK2 头文件比查文档更直观。
7.2 六个“早知道就好了”的坑
- 不要用 C 库的全局变量存 GOP 指针。UEFI 程序的生存周期和普通应用程序不同,ExitBootServices 前后内存映射会变,在图形输出和键盘读取期间,所有资源都要通过 SystemTable 或你自己启动时定位到的 Protocol 引用。如果存了全局指针,重启后很可能会变成野指针。
- 字符串编码必须统一 UTF-16。UEFI 内部字符串是 UTF-16LE,传给文件系统协议时也会用这种编码。我在写日志文件的时候一开始直接
AsciiPrint,结果中文全部乱码,后来干脆所有对外输出统一转成 UTF-8 再落盘,界面内部保留 UTF-16 显示。 - 内存分配类型会影响可用地址。UEFI 启动服务的
AllocatePool和AllocatePages在分配大内存和 DMA 缓冲区时行为不同。读磁盘做校验时我踩到过分配了 64KB 以上页面导致物理地址不连续的坑,换成AllocateMaxAddress才老实。 - UEFI 下没有常规的睡眠延时。你想让程序“睡 1 秒”,调用
gBS->Stall(1000000)即可,单位是微秒。这个调用是忙等,会占用 CPU,但测试工具无所谓。千万别直接调 C 标准库的sleep(),那东西在 EDK2 里没有实现。 - 写文件时要先打开根目录再创建文件,而不是直接给绝对路径。
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL的OpenVolume拿到根目录后,所有文件操作都以“相对于根目录的路径”完成。我刚开始总想构造一个完整路径让它自己找,结果一直返回 Not Found。 - UEFI 的并发能力极其有限。理论上你可以用 Timer Protocol 做多线程,但实际测试中一个死循环 CPU 测试就会卡住键盘扫描。我后来把 CPU 压力测试拆成小段执行,每段之间扫描一次键盘,把“计算中”的硬阻塞问题解决掉。
7.3 关于免费开放的说明与后续计划
这个工具我准备完全免费开源,不会有“免费版限制项功能”“付费解锁完整报告”这种套路。原因是它本身就产生于我自己的排障需求,真正能给它提 issue 的人都是在一线被烂硬件折腾过的人。我会把源码、编译好的bootx64.efi以及一份简单的 README 打包放到公开仓库,项目的名字暂定叫UEFI-HWDiag,大家直接搜就能找到。后续计划里我最想做的是加一个 IPMI/BMC 联动:测试结束后把报告摘要直接推送到 BMC 的 SEL 里,这样就算 U 盘遗失也能从管理口拉出来。另外,传感器和风扇的数据来自各家私有接口,需要更多真实机器来“喂”适配表,所以我特别欢迎朋友们在 GitHub issue 里提交你的机器型号和报告里的诊断数据。
搞这个工具花了我三个多月,但每一次在一台起不来的裸金属服务器前把 U 盘插进去、跑完 21 项、拿到那份报告,我都觉得值回票价。如果你也在一台反复重启的服务器前发过呆,应该能明白我说的是什么。