方法论
本书中的每个事实都来自对可自由再分发的
libtpu-0.0.40-cp314PyPI wheel(manylinux_2_31_x86_64)中libtpu.so的静态逆向工程:这是一个 781,691,048 字节的 ELF64 shared object,build-id 为89edbbe81c5b328a958fe628a9f2207d。build-id 是明确的锚定;wheel 的包元数据报告版本为0.0.40。另一个 wheel 的每个地址都会不同。
摘要
本页描述整个重建是如何完成的:二进制来自哪里、用什么工具读取、分析产出了什么、本书其余部分中的每个主张在写下之前如何交叉核对、哪些内容无法恢复以及原因、读者如何重复这项工作,以及这样做的法律依据。它是 证据与引用约定 的流程对应页,后者定义了其他每一页使用的标注词汇和引用语法。
整本书源自一个在近百万个函数上重复的动作:把单个 shared object 加载进反汇编器,让它恢复代码和数据,把每个函数反编译为 C,并把一切内容——反汇编、反编译函数体、控制流图、类型信息、交叉引用——序列化为机器可读的 sidecar。没有源代码树、没有调试器、没有运行中的 TPU,也没有内部 artifact 进入该流程。这个 wheel 中的 libtpu.so 未被 strip:它的 .symtab 仍然存在,因此本书中类似 C++ 的标识符是从二进制自身符号表读出的 demangled symbols,而不是重构结果。正是这个事实让一个 745 MB 对象变得可处理:每个函数都预先带名,分析者的工作是恢复行为,而不是名称。
该方法在设计上是对抗性的。符号名是假设,不是事实;名为 ValidateLength 的函数,在其反编译函数体显示长度比较之前,都被视为尚未验证。每个标题级主张都会对照反编译 C 或原始字节重新检查,只有当多个独立指标——函数体、调用者、引用字符串、dispatch-table 位置——一致时,结论才会被信任。下面的 pipeline 是机制;交叉验证纪律把它的输出从转录稿变成参考资料。
为复现此方法论,契约如下:
- 获取路径——精确的 wheel、它包含什么,以及为什么再分发使分析合法。
- 工具及其 pass——IDA Pro 9.x auto-analysis、Hex-Rays decompiler 和 FLIRT library-signature matching,以及其中哪些实际作用于此二进制。
- Sidecar 家族——保存恢复事实的 JSON artifacts,带已验证计数,让重新实现者知道本书背后有什么证据。
- 交叉验证规则——一个主张被信任前需要多个独立指标。
- 已公布下限——decompilation failures、analysis problems 和 firmware walls,它们限定了任何页面可以断言的内容。
| 源 artifact | libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64.whl(PyPI,可自由再分发) |
| 分析的二进制 | libtpu/libtpu.so - 781,691,048 字节,ELF64 DYN,x86-64 |
| Build-id | 89edbbe81c5b328a958fe628a9f2207d(NT_GNU_BUILD_ID,md5/uuid 形式) |
| Stripped? | 否 - .symtab 存在;大多数函数带 demangled C++ 名称 |
| 工具 | IDA Pro 9.x - auto-analysis + Hex-Rays decompiler + FLIRT |
| 恢复的函数 | 884,832(据二进制元数据) |
| 恢复的字符串 | 1,249,324 |
| Switch tables | 33,016 |
| 逐函数 artifact 文件 | context/、decompiled/、disasm/、graphs/ 中各 884,843 个 |
| 已公布限制 | 516 decompilation failures · 7,915 analysis problems |
Pipeline 概览
五个阶段把 wheel 变成 wiki 页面。每个阶段的输出都是下一阶段的输入;纪律体现在第四阶段。
| 阶段 | 输入 | 动作 | 输出 |
|---|---|---|---|
| 获取 | PyPI wheel 名称 | 下载、解压、定位 libtpu.so、记录 build-id + hashes | 一个锚定的 781,691,048 字节 ELF |
| 分析 | ELF | IDA 9.x auto-analysis:代码/数据恢复、函数边界、xrefs、类型传播 | 一个含 884,832 个函数的 IDB |
| 提取 | IDB | Hex-Rays 反编译 + 将每个函数和表序列化为 JSON sidecars 和逐函数文件 | sidecar 家族 + 884,843x4 个逐函数 artifacts |
| 交叉验证 | Sidecars + 函数体 | 对每个主张,要求反编译函数体或原始字节支持它;要求多个指标一致 | 一个带地址锚点的主张 |
| 写作 | 已验证主张 | 综合为重新实现级页面;标记缺口 | 一个 wiki 页面 |
注意 - 前三个阶段是机械的,并且在相同 wheel 和相同 IDA 版本下逐字节复现。最后两个阶段是分析判断。
获取
被分析的对象不是泄露或提取的内部构建。它是已发布 Python wheel libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64.whl 内的 libtpu.so,也就是 pip install libtpu 获取来提供 TPU PJRT plugin 的同一 artifact。wheel 是普通 ZIP archive;解压后得到一个 libtpu/ 包目录,其 payload 就是本书描述的 shared object。
wheel 目录恰好包含六个 payload 文件,全部属于分析表面:
| 文件 | 大小 | 作用 |
|---|---|---|
libtpu.so | 781,691,048 B | PJRT plugin - 主要分析目标,本书主题 |
sdk.so | 22,541,240 B | 伴随 shared object,并行分析(sibling extraction) |
__init__.py | 1,131 B | 定位并加载 plugin 的 Python entry shim |
LICENSE | 229 B | wheel 的 license file |
THIRD_PARTY_NOTICES.txt | 731,537 B | 捆绑的 third-party attributions - 确认静态链接的开源组件 |
SDK_THIRD_PARTY_NOTICES.txt | 103,306 B | sdk.so 的同类文件 |
被分析文件的身份以三种方式锚定,因此任何读者都能确认自己持有相同字节:大小(781,691,048)、从 NT_GNU_BUILD_ID note 读取的 GNU build-id 89edbbe81c5b328a958fe628a9f2207d,以及文件的 SHA-256。readelf -h 报告它是面向 Advanced Micro Devices X86-64 的 ELF64 DYN(shared object),readelf -S 显示活跃 .symtab——这是未 strip 断言的基础。
易错点 - build-id 采用内核的 md5/uuid 形式,而不是更常见的 SHA-1 形式;它是 16 字节 digest,
file将其报告为BuildID[md5/uuid]。不要在这里期待 40 个十六进制字符的 SHA-1 build-id。上面的 32 个十六进制字符值是正确的。注意 -
libtpu.so保留完整.symtab,并由file报告为not stripped。因此本书中的每个具名函数都是直接从二进制读取的 demangled symbol,不是重构结果;这也是为什么恢复名称从来不是瓶颈,恢复行为才是。
工具与提取
分析工具
一个工具产生了每个原始事实:IDA Pro 9.x,以三种能力运行。
- Auto-analysis 遍历 ELF,恢复 section/segment 布局,识别函数边界,跟随控制流,构建全局交叉引用数据库,并在恢复的代码中传播类型。在此二进制上,它解析出 884,832 个函数和 33,016 个 switch/jump tables。
- Hex-Rays decompiler 把每个函数从 x86-64 提升为类 C 的伪代码函数体。这些函数体——而不是原始反汇编——是行为主张的主要证据,因为
switch或 guard compare 在 C 中可读,而在一屏mov/cmp/jne中并不直观。 - FLIRT(Fast Library Identification and Recognition Technology)匹配已知 library routines 的字节模式签名,因此静态链接的
memcpy或 libstdc++ helper 会被标记为对应名称,而不是从头重新分析。
注意 - FLIRT 在此二进制的主要提取 pass 上贡献了零个识别匹配(元数据记录
flirt_matches: 0)。这是预期结果,不是缺陷:该二进制已经因幸存的.symtab拥有丰富符号,library routines 到来时已经预先命名,FLIRT 没有剩余可添加内容。
Sidecar 家族
Auto-analysis 和反编译不是交付物;它们序列化后的输出才是。每个函数和结构化表都会导出到一组 JSON sidecars,外加四类逐函数 artifact 文件(反编译 C、原始反汇编、context bundle 和控制流图)。本书针对这些 sidecars 写成,而不是针对实时 IDA 会话,因此每一页都可从磁盘文件复现。
Sidecar 家族如下,并列出已验证的存在性及每项携带的计数或大小:
| Sidecar | 保存内容 | 已验证范围 |
|---|---|---|
functions | 每个恢复函数一条记录(地址、大小、名称) | 884,832 条记录 |
names | name/symbol table 表面 | ~847 MB |
strings | 每个恢复的 string literal | 1,249,324 个字符串 |
segments | ELF segment / section 布局 | 55 个 segments |
data_tables | 恢复的静态数据表 | ~114 MB |
switches | Jump/switch dispatch tables | 33,016 张表 |
rtti | C++ RTTI:type-info、vtables、class hierarchy | ~65 MB |
fixups | Relocations / address fixups | ~120 MB |
xrefs | 全局交叉引用图(code + data edges) | ~41 GB |
enums | 恢复的 enumeration types | 存在(小) |
structures | 恢复的 struct/class layouts | 存在(~290 KB) |
frames | 逐函数 stack-frame layouts | ~745 MB |
entries | 导出 / entry-point symbols | 存在 |
imports | Imported / external symbols | 存在 |
prototypes | 外部提供的 prototypes(此处为空) | 空 [] |
metadata | 提取 manifest(计数、hashes、mode) | 存在 |
problems | IDA 标记的 analysis problems | 7,915 条记录 |
| per-function | decompiled/*.c、disasm/*、context/*、graphs/* | 各 884,843 个文件 |
注意 - 逐函数 artifact 计数(884,843)略高于元数据中的 function-record 计数(884,832)。目录计数包含少量 thunk/alias/data-stub entries,它们获得 artifact 文件,但不计为完整函数记录。当页面引用函数计数时,引用 884,832;当引用 artifact 覆盖率时,逐函数目录为每个已分析 entry 保存一个文件。
注意 - 主要 pass 的提取 manifest 记录
decompiled: 0和ctree: 0。这是两阶段提取的 artifact,并不表示没有任何内容被反编译。第一 pass 以 fast mode 运行(边界、名称、表、xrefs),并有意推迟 Hex-Rays;反编译函数体和控制流树由后续拆分 pass 产生,得到 884,843 个逐函数decompiled/*.c文件,以及其中 884,332 个的 control-flow-tree 覆盖(511 个函数缺口)。只检查 manifest 的decompiled计数的读者会得出错误结论:函数体确实存在于磁盘上。
交叉验证纪律
带符号的二进制很有诱惑力:像 xla::tpu::sparse_core::lowering_util::GetPadValue 这样的名称读起来像文档。它不是。名称记录的是原作者如何命名该函数,这是很强的线索,但不是已验证行为;demangled C++ 名称也经常在证明它们的代码变化后继续存在。因此,本书中的每个主张都会对照比名称更直接一层的证据重新检查。
最强证据是反编译函数体(或二进制中逐字节精确的表)确实包含所声称的构造——switch、guard compare、常量、vtable slot。没有单行直接说明角色时,结论依赖多个一致的独立指标:函数的调用者、它引用的字符串、它在 dispatch table 中的位置、它的 frame 形状。单个未经佐证的指标——一个有暗示性的字符串、一个 xref、一个暗示未见行为的名称——只记录为线索,而不是基础。
交叉检查有意使用不同 sidecars,以确保指标真正独立:
- 函数体 vs. 名称——读取
decompiled/*.c文件;确认或搁置名称。 - 调用者 vs. 角色——
xrefs图显示谁调用函数以及如何调用;没有 length-shaped caller 的 "validator" 可疑。 - 字符串 vs. 行为——引用
"request too large"的函数佐证 rejection path;strings和data_tablessidecars 提供这些信息。 - Dispatch 位置 vs. 用途——
switches和rttisidecars 把函数放入表或 vtable,从而约束它可能是什么。 - 原始字节 vs. decompiler——当 Hex-Rays 不确定时(它会用自身 warning 标记),
disasm/*文件和原始字节是裁判。
如果某个值是从结构推断而不是直接从二进制读取,> **注意 -** 或 > **易错点 -** 标注会精确标记哪一步是推断。这是本书的核心诚实机制:读者总能看出一个陈述有多直接地被支持。
怪癖 - 最重的交叉验证负担落在最有符号的函数上,而不是最少符号的函数上。例如 600 字符的 demangled C++ 模板名(如
pxc::mnemonics::ProtoToEnvMiscGenerated...家族)具体到让人感觉权威,但这些模板密集函数恰恰是 IDA 分析会绊倒的地方(见下文)。最丰富的名称和最弱的分析经常同时出现;这套纪律正是为了捕捉这个陷阱。
限制:无法恢复的内容
每一页的可信度都依赖这些限制被公布,而不是被隐藏。对单个二进制做静态分析存在硬下限,而该下限有三个不同原因。
Decompilation failures
Hex-Rays 对 516 个函数没有返回 cfunc:decompiler 运行了、拒绝了,该函数只剩反汇编。这些并非随机分布。它们聚集在:
- 模板爆炸代码——深度嵌套的 C++ templates(mnemonics 层中的 variant-of-strong-int dispatch、一次调用中注册数百个 MLIR ops 的
addOperations<...>),其恢复控制流超过 decompiler 能提升的范围。 - Imported PLT/GOT stubs——如
strlen、getenv、__tls_get_addr、MallocExtension_Internal_*之类的 entries 是 import thunks,不是本地代码,没有可反编译函数体。 - 手写汇编——来自静态链接 libraries 的 cryptographic 和 math routines(
bn_sqr8x_mont、bn_power5_nohw、md5_sha1_final),它们是汇编,没有可恢复的 C。
对于这 516 个函数,本书依赖反汇编、周围 xrefs 和名称;触及其中任一函数的页面都会说明。
Analysis problems
IDA 的 auto-analysis 在反汇编 pass 中标记了 7,915 个问题,记录于 problems sidecar。它们分为几类:disasm_problem(disassembler 无法确信解码的字节序列)、bad_stack(不平衡或无法恢复的 stack frame)、head_problem(指令边界歧义)和 final(持续到分析结束的问题)。与 decompilation failures 一样,它们集中在模板密集的 compiler code(primitive_util::*TypeSwitch、AlgebraicSimplifierVisitor)以及静态链接 third-party assemblers(X86AsmParser、AArch64AsmParser、PPCAsmParser::matchAndEmitInstruction)中。带 bad_stack 标记的函数拥有不可信的 local-variable recovery,任何触及它的页面都会说明。
静态分析在结构上看不到的内容
除逐函数失败外,有三类内容原则上对这个文件的静态分析不可见:
- Firmware blobs——发往 TPU 硬件的 payloads 是嵌入数据,不是 x86 代码。反汇编器看到的是字节,不是指令;这些是墙,会被记录为 opaque regions,而不是猜测。
- Runtime-only values——任何在加载或运行时计算的内容(
dlopen后解析的 relocations、环境驱动配置、经 ICI/network 到达的值)都没有静态表示。本书记录消费这类值的代码路径,从不记录该值。 - 伴随的
sdk.so——它在 sibling pass 中分析(94,732 个函数,其中 94,292 个被反编译),是一个更小且大体独立的对象;本书的主要主题是libtpu.so,sdk.so证据只在二者有可证明交互时引用。
注意 - 这些限制相对于整体是有界且很小的:884,832 个函数中 516 个 decompilation failures 远低于 0.1%,7,915 个 analysis problems 分布在一个含数百万条指令的二进制中。公布下限不是因为它很大,而是因为隐藏下限的逆向工程参考资料会让其上每一页都不可信。
复现
机械阶段可以精确复现;分析阶段复现到相同证据,由判断推动结论。想重新推导任何主张的读者可遵循此路径:
# 1. Acquire the identical artifact from PyPI.
pip download libtpu==0.0.40 --no-deps --python-version 3.14 \
--only-binary=:all: --platform manylinux_2_31_x86_64
# 2. A wheel is a ZIP. Unpack and locate the plugin.
unzip libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64.whl -d libtpu_wheel
ls libtpu_wheel/libtpu/libtpu.so
# 3. Confirm you hold the same bytes (size + build-id must match the pin).
stat -c%s libtpu_wheel/libtpu/libtpu.so # -> 781691048
readelf -n libtpu_wheel/libtpu/libtpu.so | grep -i 'build id'
# -> 89edbbe81c5b328a958fe628a9f2207d
```text
此后,分析流程为:把 `libtpu.so` 加载进 IDA Pro 9.x,运行完整 auto-analysis,并反编译。本书引用的逐函数证据是给定虚拟地址处的反编译 C 函数体;在 Hex-Rays 中打开该地址(或读取对应的 `decompiled/<addr>.c` artifact),即可逐行验证主张。每个引用中的地址锚点是该重建的入口点。
> **易错点 -** 复现保真度依赖 IDA *版本*。Auto-analysis heuristics、function-boundary recovery 和 Hex-Rays output 都会在 IDA 大版本之间变化,因此不同版本可能恢复出略有不同的函数计数,或反编译出本书列为 failure 的函数体(反之亦然)。请固定 IDA 9.x,以匹配此处引用的函数和 switch-table 计数。二进制字节不变;对它们的分析会变。
---
## 法律依据
这项工作是出于互操作性和研究目的,对合法获得、公开分发的软件进行逆向工程。二进制根据可自由再分发的条款从 PyPI 下载;没有绕过任何访问控制,也没有在任何时候使用或咨询受版权保护的源代码、内部文档或其他受限材料。每项发现都仅来自对任何人都可下载的已编译二进制的分析。
该活动建立在大西洋两岸成熟的法律之上:
| 法源 | 管辖区 | 确立内容 |
|---|---|---|
| **DMCA §1201(f)** | US | 为互操作性进行逆向工程是反规避规则的法定豁免。 |
| **Sega v. Accolade** (9th Cir. 1992) | US | 当反汇编是访问不可保护功能元素的唯一手段时,它属于 fair use。 |
| **Sony v. Connectix** (9th Cir. 2000) | US | 为构建可互操作产品而在逆向工程中的中间复制属于 fair use。 |
| **EU Directive 2009/24/EC, Art. 6** | EU | 为实现互操作性,在规定边界内,无需权利人授权即可反编译。 |
本书记录的是*二进制做什么*——其功能行为、数据布局和决策逻辑——这些是不受保护的思想和操作方法,而不是任何源文本的受保护表达。贯穿始终的来源纪律(二进制静态分析且仅静态分析)正是使工作保持在这些边界内的原因:没有复制受保护源码,因为从未持有过源码。
---
## 交叉引用
- [证据与引用约定](front/evidence-conventions.md) - 本页流程产生的标注词汇和引用语法;阅读任何其他页面前先读它。
- [Forensics Overview](forensics/overview.md) - 二进制本身的结构起点:sections、大小和直接对照字节确认的标题级计数。
- [ELF Anatomy](forensics/elf-anatomy.md) - `segments` sidecar 记录的 segment/section 布局,在 `readelf` 层面读取。
- [Dispatch-Table Taxonomy](forensics/dispatch-table-taxonomy.md) - 如何读取 33,016 张 switch tables 以及 RTTI/vtable 图,即行为页面的结构化证据骨架。