- 固件
- 操作系统
- 驱动开发
- 嵌入式
【免费下载链接】edk2
EDK II
导读
本文围绕 EDK2 仓库中 EmbeddedPkg/Scripts/LauterbachT32 目录下的 TRACE32(T32)调试脚本展开,讲解如何在 Lauterbach TRACE32 调试器环境中为 UEFI 固件加载符号:既能在 DXE 阶段利用系统表(System Table)与调试信息表自动发现并加载各 DXE 模块的调试符号,也能在 SEC/PEI 阶段通过显式指定 Firmware Volume 基址手动加载符号。读完本文,你将掌握do EfiLoadDxe、do EfiLoadFv两条核心命令的用法、配套脚本(EFI.CMM、T32.CMM、EfiProcessPeImage.cmm、EfiProcessTeImage.cmm)的底层实现原理,以及如何将脚本封装为工具栏按钮以提升日常调试效率。
一、脚本集概览:为 UEFI 固件调试准备的 TRACE32 工具链
EmbeddedPkg/Scripts/LauterbachT32目录为 EDK2 固件开发者提供了一整套与 Lauterbach TRACE32 调试器配合使用的脚本,文件清单如下:
| 文件 | 作用 |
|---|---|
| Readme.md | 使用说明,定义 DXE 阶段与 SEC/PEI 阶段两种调试流程 |
| EfiLoadDxe.cmm | 根据 EFI 系统表地址自动扫描 Debug Image Info Table 并逐个加载模块符号 |
| EfiLoadFv.cmm | 根据 Firmware Volume 基址解析 FFS 文件与 Section,为 SEC/PEI 镜像加载符号 |
| EfiProcessPeImage.cmm | 解析 PE32 镜像头,提取 DWARF/CodeView 调试信息并执行data.load.elf |
| EfiProcessTeImage.cmm | 处理 TE(Terse Executable)镜像,修正去除 PE 头后的 RVA 偏移后加载符号 |
| EFI.CMM | EFI 调试环境的启动配置脚本,注册 Reset/Load Symbols 等工具栏按钮 |
| T32.CMM | TRACE32 全局启动脚本模板,可拷贝到C:\T32目录,负责定位工作目录并调用 EFI 配置脚本 |
从脚本的版权注释可以看出,这套脚本最早由 Hewlett-Packard 于 2011 年贡献,2024 年由 Ampere Computing 更新(见 EfiLoadDxe.cmm),属于 EDK2 中跨厂商沉淀的调试基础设施。
两种调试阶段的划分
Readme 明确将调试流程分为两个阶段,这也是后续所有脚本的设计依据:
- DXE 阶段:系统已启动到 DXE 核心初始化完成,此时内存中已存在 System Table 和 Debug Information Table,因此可以自动探测调试信息表的位置并加载符号;
- SEC/PEI 阶段:镜像尚未进入 DXE 环境,不存在可自动发现的调试信息表,因此必须手动传入存放 SEC/PEI 代码的内存映射 Firmware Volume 地址。
下面分别展开这两条路径。
二、DXE 阶段调试:EfiLoadDxe 自动符号加载
2.1 使用方式
按 Readme 的说明,让系统启动到 DXE 核心初始化完成的时刻(System Table 与 Debug Information Table 已驻留内存),然后在 TRACE32 的命令区执行:
do EfiLoadDxe "0xGST_ADDRESS"其中GST_ADDRESS是EFI_SYSTEM_TABLE(全局系统表)的地址,可通过调试器中的全局变量gST查看。也可以像 Readme 提到的那样,把该命令绑定到工具栏按钮,实现"一键加载全部 DXE 符号"。
2.2 底层原理:从系统表到调试信息表
EfiLoadDxe.cmm的核心逻辑在FindDebugInfo子程序中(EfiLoadDxe.cmm),它完全不依赖编译器符号表,而是直接从内存中的 EFI 数据结构出发:
读取系统表字段:脚本对传入的系统表地址
&SystemTable做两个固定偏移读取——+0x68处读取NumberOfTableEntries(配置表项数),+0x70处读取ConfigurationTable数组指针。对照 UefiSpec.h 中EFI_SYSTEM_TABLE的结构定义(Hdr、FirmwareVendor、ConsoleInHandle、ConIn、ConOut、StdErr、RuntimeServices、BootServices、NumberOfTableEntries、ConfigurationTable),这两个偏移正是 64 位架构下系统表末尾两个字段的位置。搜索调试信息表 GUID:脚本逐项遍历
EFI_CONFIGURATION_TABLE(每项 0x18 字节:16 字节 GUID + 8 字节指针),按小端序拆分为四个 DWORD 与固定值比对:匹配值 对应 GUID 字节序 0x49152E7749152e77-… 0x47641ADA…-1ada-4764-… 0xFE7AA2B7…-b7a2-7afe-fed9-5e8b 0x8B5ED9FE… 该 GUID 正是 UEFI 规范定义的
EFI_DEBUG_IMAGE_INFO_TABLE_GUID,其完整定义位于 MdePkg/Include/Guid/DebugImageInfoTable.h。解析调试信息表头:命中后从配置表项的
+0x10处读取表指针&dbghdr,随后读取&dbghdr+4得到TableSize(条目数)、&dbghdr+8得到EfiDebugImageInfoTable数组指针。这与 DebugImageInfoTable.h 中EFI_DEBUG_IMAGE_INFO_TABLE_HEADER的字段布局完全一致(UpdateStatus、TableSize、EfiDebugImageInfoTable)。遍历并加载每个镜像:对数组中的每一项,读取其
ImageInfoType,若为0x01(EFI_DEBUG_IMAGE_INFO_TYPE_NORMAL,定义见 DebugImageInfoTable.h),则从+8处取LoadedImageProtocolInstance指针,再从+0x40偏移读取ImageBase(对应EFI_LOADED_IMAGE_PROTOCOL结构中的ImageBase字段),最后调用do ~~~~/EfiProcessPeImage.cmm "&imagebase"处理该 PE 镜像。
TRACE32 脚本中的
~~~~前缀表示"当前脚本所在的目录",因此EfiLoadDxe.cmm无论被放在哪个路径,都能正确找到同目录下的EfiProcessPeImage.cmm。
2.3 DXE 符号加载调用链
整个 DXE 阶段符号加载形成一条清晰的调用链:
do EfiLoadDxe "0x<gST>" └─ FindDebugInfo: 系统表 → 配置表 → Debug Image Info Table └─ do EfiProcessPeImage <ImageBase> (每个模块) └─ data.load.elf <ELF路径> <基址> /NOCODE /NOCLEAR其中data.load.elf是 TRACE32 加载 ELF 符号的命令,/NOCODE /NOCLEAR参数表示只加载符号而不烧写代码、不清空已有内容,适合目标已在内存中运行时的纯符号附着场景。
三、SEC/PEI 阶段调试:EfiLoadFv 手动指定 Firmware Volume
3.1 为什么需要手动指定地址
Readme 明确指出:SEC/PEI 镜像没有可自动探测的途径,因为这些镜像驻留在内存映射的 Firmware Volume(FV)中,而 FV 尚未被 DXE 核心的配置表机制登记。因此必须把存放 SEC/PEI 代码的 FV 基址显式告诉调试器:
do EfiLoadFv \<addr\>其中\<addr\>是包含 SEC 或 PEI 代码的 Firmware Volume 基址。为了使用方便,Readme 建议封装一个板级脚本(例如MyBoardLoadSec.cmm),内部固定调用EfiLoadFv传入该板子的 FV 地址,再把这个脚本映射到 T32 菜单或工具栏按钮实现快捷访问——这一"封装 + 按钮"的模式正是 T32.CMM 与 EFI.CMM 中menu.rp / toolbar / toolitem机制所演示的用法。
3.2 FV 解析流程
EfiLoadFv.cmm(EmbeddedPkg/Scripts/LauterbachT32/EfiLoadFv.cmm)按 Firmware Volume 的规范布局逐层解析:
- 校验 FV 签名:读取
&fvbase+0x28处的 4 字节,必须等于0x4856465F(即 ASCII"_FVH",EFI_FIRMWARE_VOLUME_HEADER.Signature),否则打印"FV does not have proper signature, exiting"并退出; - 读取 FV 长度与首文件偏移:
&fvbase+0x20处是FvLength,&fvbase+0x30的低 16 位是HeaderLength,首个 FFS 文件就从fvbase + HeaderLength开始; - 遍历 FFS 文件:每个 FFS 文件头的
+0x14处为"大小 + 类型"组合字段(低 24 位是文件大小,高 8 位是文件类型),文件按8 字节边界对齐;当读到大小为0xFFFFFF的哨兵值时结束遍历; - 遍历 Section:FFS 文件数据从
+0x18处开始(FFS 文件头长度),每个 Section 头同样是"低 24 位大小 + 高 8 位类型",Section 按4 字节边界对齐; - 按类型分发:
EFI_SECTION_PE32(类型0x10)调用EfiProcessPeImage,EFI_SECTION_TE(类型0x12)调用EfiProcessTeImage,其他类型打印"unknown section type"。
这一解析路径与 EDK2 固件卷规范一致:FV → FFS 文件 → Section → PE32/TE 镜像,只是把固件代码在 PEI 阶段的遍历逻辑用 TRACE32 脚本重新实现了一遍,属于典型的"用调试器脚本复刻固件引导逻辑"的做法。
四、镜像调试信息提取:EfiProcessPeImage 与 EfiProcessTeImage
无论是 DXE 阶段的自动加载还是 SEC/PEI 阶段的手动加载,最终都要落到对单个镜像的处理上,由两个"处理器"脚本完成。
4.1 EfiProcessPeImage:解析 PE32 镜像
EfiProcessPeImage.cmm 的执行流程:
- 定位 PE 头:通过 DOS 头
+0x3C处的e_lfanew找到"PE\0\0"签名位置(&filehdrstart); - 读取调试目录 RVA:在脚本约定的 PE 头偏移
0xf10处读取调试目录(Debug Directory)的 RVA,若为 0 则打印"no debug dir"并结束; - 校验调试类型:读取调试目录项
+0xc处的Type字段,只接受0x02(CodeView)与0xdf两种取值,否则提示"debug type is not dwarf"; - 读取调试数据 RVA 与 DWARF 签名:调试目录项
+0x14处是AddressOfRawData(调试数据的 RVA),在该位置读取签名:0x66727764(小端 ASCII"dwrf")→ 路径偏移0xc;0x3031424E(小端 ASCII"NB10",即 CodeView NB10 记录)→ 路径偏移0x10; 两种签名分别对应 DWARF 与 CodeView 两种调试信息格式;
- 读取 ELF 路径并计算加载基址:从调试数据中读出 ELF 符号文件的路径字符串
&elfpath;基址取BaseOfCode与BaseOfData中较小且非零者(&baseofcode、&baseofdata分别取自 PE 头+0x28、+0x2c处的 RVA 加镜像基址),因为符号文件可能按代码段或数据段地址排布; - 加载符号:
data.load.elf &elfpath &elfbase /NOCODE /NOCLEAR,并包裹ON ERROR GOSUB / ON error容错,保证单个镜像失败不影响整个遍历。
4.2 EfiProcessTeImage:处理去除 PE 头的 TE 镜像
TE(Terse Executable)镜像为节省空间去掉了部分 PE 头,因此所有 RVA 都需要减去被剥离的头部字节数。EfiProcessTeImage.cmm 的处理要点:
- 从 TE 头
+0x4处的高 16 位读出StrippedSize(TE 头中记录被剥离的 PE 头大小),再减去 0x28(TE 头自身 40 字节)得到实际需修正的偏移量&strippedsize; - 调试目录 RVA 从
+0x20读取后同样减去&strippedsize修正; - 符号加载基址取
&imgstart + BaseOfCode(&imgstart+0xc) - StrippedSize(注释说明 TE 镜像假定符号以代码段基址为准); - 其余 DWARF/CodeView 签名校验、路径读取与
data.load.elf /NOCODE /NOCLEAR逻辑与 PE32 版本一致。
两个脚本一前一后覆盖了 EDK2 固件中最常见的两种可执行镜像格式,且与 EfiLoadFv.cmm 中0x10(PE32)/0x12(TE)两种 Section 类型一一对应。
五、环境配置:EFI.CMM 与 T32.CMM 的接线方式
5.1 EFI.CMM:EFI 调试会话的工具栏入口
EFI.CMM 是面向 EFI 调试会话的配置脚本,主要动作:
radix hex:将输入/显示进制切换为十六进制;- 通过
menu.rp向工具栏注册三个工具项:Reset Target(快捷键RS)→sys.ResetTarget;Load EFI DXE Symbols(快捷键DX)→do EfiLoadDxe;Load EFI Runtime Symbols(快捷键RT)→do EfiLoadRuntimeDxe;
system.config.debugaccessport 0、system.config.corebase 0x80001000、system.attach:配置调试访问端口、设置核基址并附着目标;break.sel.program onchip:选择片内程序断点;setup.var %hex.on / %decimal.OFF:变量显示强制十六进制。
需要说明的是:从脚本调用看,EfiLoadRuntimeDxe被期望用于运行时(Runtime)驱动符号加载,但该脚本并未随本目录一同发布(目录中只有EfiLoadDxe.cmm等五个 .cmm 文件),属于留待开发者按 EfiLoadDxe.cmm 模式自行补充的扩展点。
5.2 T32.CMM:TRACE32 全局启动脚本
T32.CMM 是 TRACE32 的默认启动程序模板,注释要求拷贝到C:\T32目录使用,要点包括:
GLOBAL &wcdir,并预设&wcdir="D:\bios",注释明确提示必须修改为实际的工作目录;- 注册 Source/List、Memory Dump、Register、Watch、Stack、Breakpoints、Symbols、System Settings 等常用调试窗口的工具栏按钮;
- 按语言加载
~~/t32<语言>.men菜单文件; autostore , history bookmark:记录并恢复历史书签;- 最后
chdir &wcdir\Platform\T32_Scripts切换到脚本目录并do EFI执行 EFI 配置脚本——这就是 EFI.CMM 被接入 TRACE32 启动流程的默认路径约定。
六、调试工作流总结与注意事项
把两部分串联起来,一套完整的 TRACE32 调试 EDK2 固件的工作流为:
- 将
T32.CMM部署到 TRACE32 安装目录并修改&wcdir指向本地工作目录; - 启动 TRACE32,由
T32.CMM自动进入 EFI 配置脚本,完成目标附着与工具栏注册; - SEC/PEI 阶段:在命令行执行
do EfiLoadFv <FV基址>,或通过MyBoardLoadSec.cmm封装后点击按钮,为引导早期代码加载符号; - DXE 阶段:等 DXE 核心初始化完成、
gST可用后,执行do EfiLoadDxe "0x<gST>"(或直接点击Load EFI DXE Symbols按钮),自动完成全部 DXE 模块的符号加载; - 之后即可在 TRACE32 中进行源码级单步、断点、变量查看等常规调试。
使用时有几点值得注意:
- 阶段前提:DXE 自动加载依赖
EFI_DEBUG_IMAGE_INFO_TABLE已发布到系统配置表,这是 MdePkg/Include/Guid/DebugImageInfoTable.h 所定义的 GUID 机制在运行时的产物;SEC/PEI 阶段则无此前提,但需要开发者准确掌握 FV 基址; - 架构敏感偏移:脚本中以固定偏移读取系统表、调试信息表、LoadedImageProtocol 等结构(如
+0x68/+0x70/+0x40),这些数值与 64 位架构下的结构布局匹配,移植到 32 位目标时需重新核对结构体偏移; - 符号文件路径:脚本最终依赖镜像调试目录中记录的 ELF 符号文件路径(
data.load.elf),因此构建固件时需确保调试信息指向的目标文件在调试主机上可访问; - 可扩展入口:
EfiLoadFv.cmm的分发逻辑(PE32/TE 类型分发)与EfiProcessPeImage.cmm/EfiProcessTeImage.cmm形成了清晰的插件式结构,若要支持新镜像格式或新的调试信息签名,可以在这三个脚本的基础上扩展。
总而言之,这套脚本把 UEFI 规范的运行时数据结构(系统表、配置表、调试信息表、固件卷、FFS、Section)转化为 TRACE32 可执行的符号加载逻辑,是调试 EDK2 平台固件早期启动流程时一份可直接复用的实战资产。
- 固件
- 操作系统
- 驱动开发
- 嵌入式
【免费下载链接】edk2
EDK II
相关推荐
EDK2 UEFI固件项目针对高通骁龙平台
EDK2 UEFI固件项目针对高通骁龙平台 1. 项目基础介绍及主要编程语言 本项目名为 edk2 msm ,是EDK2(EFI Development Kit
固件嵌入式驱动开发操作系统革命性UEFI固件项目edk2-msm:解锁高通平台多系统启动的终极指南
革命性UEFI固件项目edk2 msm:解锁高通平台多系统启动的终极指南 想要在 高通平台 上实现 多系统启动 吗?edk2 msm项目为你打开了通往多系统启动
固件嵌入式驱动开发操作系统OvmfPkg 平台 CI 指南:使用 edk2 Pytools 本地构建与验证 OVMF 固件
OvmfPkg 平台 CI 指南:使用 edk2 Pytools 本地构建与验证 OVMF 固件 OvmfPkg 是 EDK II 中面向 QEMU/KVM 虚
固件操作系统驱动开发嵌入式
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考