-
-
[原创]NVIDIA FECS 上下文切换固件逆向笔记:9797 条指令全注释(AD102/Ada)
-
发表于: 2天前 282
-
一、这是什么
NVIDIA 的 GPU 里跑着一个小单片机(Falcon 核),专职当"上下文切换管家"——把引擎的几万个寄存器状态在内存和引擎之间搬进搬出。本文逆向的就是它的固件:ctxsw_ucode/ada/ad102/net/NETA_fecs_ucode_img.h(公开驱动源码包里以 C 数组内嵌)。
规模:IMEM 30,715 字节 / 9,797 条指令;DMEM 5,656 字节。全部指令已逐条注释(98.2% 带中文语义),结构化 C 15,396 行 / 170 个函数。产物见附件。
二、固件从哪来
抽出两个二进制段:
fecs_inst.bin30,715 B:IMEM 指令区,覆盖0x0–0x77FBfecs_data.bin5,656 B:DMEM 初值,非零仅0x0–0x9F0,开头是构建戳"Nov 19 2021 19:36:12"
版本锁 MAJORV=0x58703102 与驱动账本一致。反汇编用 envytools envydis(fuc6),先拿驱动包里的 ACR 语料(官方 objdump + 裸镜像 2246 条)逐字节校准后才信任全量输出。
三、体系结构:睡-醒-干活-再睡
入口 0x0:栈顶 0x2800 → lcall main(0x7747) → 返回即停机。main 是经典嵌入式循环:
init(0x2BA3)
for(;;) {
sleep $p2 ; 低功耗等待;p2 = "三环全空"
raw = iord(0x18700) ; 命令寄存器
D[0x12D4] = (raw>>16)&0x1F
lcall 0x1FC9 ; ① 主分派器:排空环 B
lcall 0x4656 ; ② 周期任务 A:排空环 C
lcall 0x7292 ; ③ 周期任务 B:排空环 A(ctxsw 在这里)
p2 = 三对读写指针全部相等 ; 没活才干睡
}
四、中断模型:每引擎一位 + 一台分拣机
中断处理器(0x1D81)读 I[0x200]:
- bit2 = 主机消息:从硬件队列
I[0x1A00]/I[0x1900]取 (命令,参数),经分拣器 sub_1D12 路由 - bit8+n = 第 n 个引擎的事件位:
bit0 → 环A(0x10000,0)← 这就是上下文切换事件- bit1 → 环B(0x4) bit2 → 环C(0x10002) bit3 → 引擎错误上报(码 0x20/0x22) bit6/7 → 环B
三条内部环是固件自己的软件队列,16 槽 × 8B =(命令,参数),指针带环绕位:
| 环 | 数组 | 读/写指针 | 生产者 | 消费者 | 满码 |
|---|---|---|---|---|---|
| A | 0x132C | 0x136C / 0x1370 | sub_60B | 周期任务B 7292 | 0x10 |
| B | 0x1374 | 0x13B4 / 0x13B8 | sub_681 | 主分派器 1FC9 | 0x12 |
| C | 0x15B8 | 0x15F8 / 0x15FC | sub_1C96 | 周期任务A 4656 | 0x13 |
五、ctxsw 的触发条件(反汇编为证)
事件 → 环A(0x10000) → 周期任务B:
0x747F: cmp b32 $r10 $r9 ; 命令 == 0x10000 ?
0x7485: lbra 0x76E1 ; → lcall 0x70E5 判定
0x70E5 判定核心(原文):
0x70F8 iord $r11 I[0x2C000] ; 现役上下文描述符(bit31=有效)
0x70FF iord $r1 I[0x2C100] ; 请求的上下文描述符
0x7123 cmp 两个地址部分
0x7125 bra e 0x7162 ; 相同 → 旁路(不切)
0x7138 lcall 0x64AD ; ① 保存/准备
0x713C iowr I[0x2C000] $r1 ; ★ 提交:现役 ← 请求
0x7154 lcall 0x5850 ; ② 恢复/验证
切换四条件:① 现役描述符有效 ② 请求描述符有效 ③ 地址不同 ④ 门控 D[0x13F0] 未锁定。
地址相同走旁路(0x7162):清 0x2DC00、外部写 BAR0 0x4041B4、置 D[0x12BC]——"无需切换"的回执。
六、切换执行:编程搬运机构 + XD 块搬移
两个千行级函数是左右手(0x64AD / 0x5850,骨架同模):
- 忙标志
I[0x21200]写 0x40000 / 0x80000;通道选择I[0x1AC00]写 1 / 8 - 轨迹端口
I[0x20A00]打签名 0xC0FFEEB0 / 0xC0FFEEB1(固件作者的彩蛋) - 用引擎号(
I[0x2DE00]&0x3F)拼命令字,向I[0x22A00]下发单元复位/使能序列(0x37_08 / 0x32_0D / 0x36_09 …) - 0x5850 调 0x1196"状态存储编程器"(全固件只被调 1 次,带幂等缓存),建 DMEM 0x1528 处 12 字节/引擎的状态表,写全局描述符 0x418810 / 0x419848 / 0x419E00(BAR0 地址)
- 块数据走 XD 搬移单元(sub_2DE1):设
$xdbase/$xtargets,xdld/xdst选方向,xdwait等完成 - 结束前三处影子对比,不符报诊断码 0x32 / 0x38 / 0x40
诊断通道 sub_5C0:错误码写 I[0x20600] → 触发 I[0x30700]=1 → 等 I[0x30600] ack;码 0x24/0x48/0x49 = 致命停机。
七、与驱动账本对上了
同目录 NETA_ctx_state_store.h(2MB)就是固件要执行的"搬运清单"。全量解析:52 段,合计 5,463 个 32 位字 ≈ 22KB/上下文。
- 按层次:GPC 2264 / SYS 1273 / FBP 817 / TPC 258+119 / PPC 86 …
- 按功能:性能监视 PM 占 63%(PMMGPC 1476 + PMMSYS 804 + PMMFBP 804 + 控制路由)
- 每实例 9 种寄存器共 66 字,实例数 GPC 22 / SYS 12 / FBP 12 —— 与微码遍历的"引擎 0x8000、槽 0x200"步进一一对应
最硬的交叉验证:微码写 BAR0 0x405830,在账本里查到它就是 NV_PGRAPH_PRI_DS_TGA_CONSTRAINTLOGIC_BETA——微码的"引擎空间"= BAR0 寄存器空间,一字不差。而 0x418810 / 0x418EA0 / 0x419xxx 等地址不在账本里——它们是 ctxsw 硬件自身的控制寄存器,不是上下文状态。
八、产物(见附件压缩包)
fecs_annotated.html/.txt:全部 9,797 条指令逐条注释(函数据调用目标切分,端口/DMEM/函数名全标注)fecs_doc.html:图文版完整笔记(含 4 张 SVG 结构图,浏览器打开)fecs_inst.asm:原始反汇编;fecs_inst.c/fecs_structured.c:C 转译(平铺/结构化)- 工具脚本:extract.py(抽段)asm2c.py(转C)cfstruct.py(结构化)annotate.py(逐条注释)dis.py / scan.py
九、还没坐实的(欢迎接力)
- 12 字节状态表三字段与账本逐字段对齐
I[0x22A00]命令流逐条语义- 0x64AD / 0x5850 谁是 save 谁是 restore 的决定性证据
- 上层触发场景(GSP 固件侧)
本文全部结论以反汇编实测为准,推断处已标注;工具链与地址均可回查。欢迎讨论指正。
[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)