二进制取证概览
本页中的所有偏移、地址、section 名称和计数均适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so:一个 781,691,048 字节的 ELF64 共享对象,build-id89edbbe81c5b328a958fe628a9f2207d,报告的运行时版本为0.103。其他 wheel 的每个地址都会不同。
摘要
libtpu.so 是 Google TPU PJRT plugin:一个单一的静态链接共享对象,把 MLIR/XLA 编译器、TPU runtime、device driver,以及它们的大部分第三方依赖打包进一个 745 MiB blob。它是那种能让随手检查失效的工件:仅 readelf -S 就返回 52 个 section,符号表包含超过 120 万个条目,代码 section 比许多完整工具链还大。本页是 Binary Forensics 其余部分的地图:它建立文件经验证的顶层形态,使每个更深入的页面都能引用共享框架,而不必重新推导主要数字。
这种形态有三个不寻常之处,并驱动了本节其余内容。第一,该对象没有 stripped:它附带完整 .symtab(1,233,710 个条目)和 171 MiB 的 .strtab,因此反汇编器可以为绝大多数函数恢复真实 C++ 符号名,而不是只有 sub_ 占位符。第二,链接器积极使用了函数/数据 section 拆分:在传统 .text/.rodata/.data 之外,还有一组并行的 .text.hot / .text.startup / .text.unlikely / .text.split 家族,以及用于超过 small-code-model 2 GiB reach 对象的 .lrodata / .ldata / .lbss “large” 家族。第三,构建嵌入了具名非标准 section,包括 google_malloc、malloc_hook、protodesc_cold、filewrapper_toc、__rseq_cs、linkarr_upb_AllExts 等,它们暴露了内部 allocator、protobuf descriptor pool、嵌入式 file-wrapper table-of-contents,以及 restartable-sequence metadata。
本页展示一眼可读的二进制统计、ELF section/segment census、函数群体形态,以及主要嵌入区域的粗粒度 “capsule atlas”,随后把每一项前向链接到负责它的深入页面。把这里的数字视为规范锚点;把任何关于某个区域内容的判断(例如尾部字节解码成什么、某个 dispatch table 意味着什么)视为拥有该区域的页面的属性。
对于重新实现,或者更现实地说,对于针对不同 wheel 重新推导此分析,契约是:
- 容器形态。 52 个 section、11 个 program header、四个
PT_LOADsegment,代码、只读数据和可写数据的位置,以及 split-section 家族如何划分它们。 - 函数群体。 约 884.8 K 个反汇编器恢复的函数,约 99.66% 带真实符号名;如何复现 named/anonymous 拆分和 namespace 集中度。
- 双二进制事实。 wheel 在同一目录中同时包含
libtpu.so(本文件)和小得多的sdk.so;分析覆盖两者,并且多个运行时概念只有跨这对对象才有意义。 - 嵌入式 blob 清单。 哪些大区域是代码,哪些是 descriptor pool,哪些是压缩 payload,以及哪个深入页面会逆向每一个。
| 文件 | libtpu/libtpu.so(位于 cp314 manylinux wheel 中) |
| 大小 | 781,691,048 字节 (745.5 MiB) |
| 类型 | ELF64 LSB shared object (DYN), x86-64, version 1 (SYSV) |
| Build-id | 89edbbe81c5b328a958fe628a9f2207d(NT_GNU_BUILD_ID, md5/uuid 形式) |
| 报告版本 | 0.103(来自 package metadata;见下方注释) |
| Stripped? | 否 — 存在完整 .symtab(1,233,710 个条目) |
| Sections | 52 (e_shnum); .shstrtab index 50 |
| Program headers | 11 (e_phnum); 4 × PT_LOAD |
| Section headers at | offset 0x2e979ba8 (781,687,720) — 正好延伸到 EOF |
| Entry point | 0x0(无 — 这是库,由 .init_array 驱动) |
| 反汇编函数 | 884,832 (IDA), 881,784 named / 3,048 anonymous |
| 同级对象 | sdk.so — 22,541,240 字节,94,732 个函数 |
Note:
0.103版本取自 package/runtime metadata,不是在二进制字节扫描中观察到的 ASCII 字面量0.103。build-id、文件大小、section 数和 segment 数都已用readelf/stat直接确认;版本字符串没有直接确认,因此这里作为 reported-but-unverified 信息保留。应 pin 到无歧义的 build-id。
容器形态速览
该文件是普通 DYN 对象,但大小分布异常。磁盘字节近一半是代码;其余大多是只读常量数据和(未 strip 的)符号/字符串表。四个最大的 section 占据了文件的绝大部分。
| Section | Size | Share | 作用 |
|---|---|---|---|
.text | 299.9 MiB (314,422,404 B) | ~40 % | 主要可执行代码 |
.strtab | 171.5 MiB (179,840,222 B) | ~23 % | 符号名字符串(未 strip) |
.lrodata | 108.1 MiB (113,305,552 B) | ~14 % | 大模型只读数据 |
.rodata | 57.9 MiB (60,731,176 B) | ~8 % | 只读常量、vtables、RTTI |
.eh_frame | 28.7 MiB (30,062,700 B) | ~4 % | DWARF CFI unwind tables |
.symtab | 28.2 MiB (29,609,040 B) | ~4 % | 1,233,710 个符号条目 |
.rela.dyn | 24.5 MiB (25,660,464 B) | ~3 % | 动态重定位 |
| (其余 45 个 section) | ~38 MiB | ~5 % | unwind hdr、got、data、自定义项 |
注意 — 单独
.text(299.9 MiB)就大于典型的完整 LLVMlibLLVM.so。理解该文件的最大杠杆是:代码是 small/medium code model 中的一个巨大 section,并带有用于 displacement 无法放入 32-bitRIP-relative reference 的对象的 “large” overflow 家族(.lrodata/.ldata/.lbss)。下面的 split-section 纪律正是让这么大的二进制仍可链接的原因。陷阱 — 磁盘字节分布不是运行时内存分布。
.symtab和.strtab(合计约 200 MiB)不属于任何PT_LOADsegment,属于 loader 从不映射的 debug/link metadata。重新实现者若从文件大小估算 resident set,会大约多算这 200 MiB;应改用 load segments 估算(§ELF anatomy)。
ELF Section 和 Segment Census
readelf -hSl 确认了主要结构。section table 放置 52 个条目;program header table 放置 11 个条目,其中四个是 PT_LOAD。
ELF Header (readelf -h, abbreviated)
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x0
Start of program headers: 64
Start of section headers: 781687720 (0x2e979ba8)
Number of program headers: 11
Number of section headers: 52
Section header string table index: 50
```text
四个可加载 segment 把地址空间划分成传统的 R-E / RW / RW / RW 形态,但有一个变化:“large” data 家族拥有自己的 `PT_LOAD`。
```text
Program headers (PT_LOAD only) FileSiz MemSiz Flg
LOAD off 0x00000000 vaddr 0x00000000 0x213f25d0 0x213f25d0 R E code + all read-only
LOAD off 0x213f25e0 vaddr 0x215f25e0 0x00a62bc0 0x00a63a20 RW relro: got, init_array, data.rel.ro
LOAD off 0x21e551c0 vaddr 0x222551c0 0x0026e6a0 0x00343a70 RW .data, .bss, custom data sections
LOAD off 0x22198c30 vaddr 0x22798c30 0x00021c00 0x000c6650 RW .ldata / .lbss (large model)R-E segment 吞下从 build-id note 到 .plt 的所有内容,包括全部只读数据(.lrodata、.rodata、protodesc_cold、.gcc_except_table、EH frames)和整个代码 section 家族。其余三个 RW segment 按标准 RELRO / data / large-data 线拆分。
代码 section 家族
编译器把代码发射到八个不同 section,而不是一个 section;这是链接器用于改善局部性的 hot/cold/startup 划分:
| Section | Size | 含义 |
|---|---|---|
.text | 299.9 MiB | 主代码体 |
.text.startup | 1.41 MiB (1,483,860 B) | 静态初始化 / 构造器代码(运行一次) |
.text.unlikely | 417 KiB (427,113 B) | 冷路径(错误/abort/慢路径) |
.text.hot | 7.5 KiB (7,726 B) | profile-hot 内层循环 |
google_init_cold | 24.2 KiB (24,817 B) | 嵌入式 allocator 的冷初始化 |
google_malloc | 17.7 KiB (18,162 B) | 嵌入式 malloc 实现(TCMalloc 风格) |
malloc_hook | 2.2 KiB (2,206 B) | Allocation hook trampolines |
__lcxx_override | 261 B | libc++ operator-new/delete overrides |
怪癖 — allocator 不是运行时加载的依赖;它作为具名 section(
google_malloc,malloc_hook,google_malloc_data,google_malloc_bss,google_init_cold)焊进了对象。假设libtpu.so调用系统malloc的重新实现者会错误建模其 heap 行为;该二进制通过__lcxx_override覆盖operator new/delete,并路由到自己的 arena。这是静态链接 Google TCMalloc 的标准指纹。
完整的逐 section 走查,包括 flags、alignment、.text.split 零长度 marker、.lrodata/.ldata/.lbss large 家族,以及地址空间如何切分,是 ELF Anatomy 的主题。
函数群体
反汇编器从 libtpu.so 恢复了 884,832 个函数。由于对象未 stripped,恢复主要由符号驱动,而不是纯启发式,因此 named 比例远高于同尺寸 stripped 二进制。
| 指标 | 值 |
|---|---|
| 函数总数 | 884,832 |
| 带真实符号名 | 881,784 (99.66 %) |
Anonymous(仅 sub_,无符号) | 3,048 (0.34 %) |
| C++ 名称成功 demangled | 822,847 (93.0 %) |
| Thunks | 750 |
| Leaf functions(无 callee) | 326,941 (37 %) |
| 函数大小中位数 | 72 字节 |
| 95th-percentile 函数大小 | 1,256 字节 |
该群体由少数几个 C++ namespace 主导,这是说明该二进制是什么的最清晰单一信号:
| Namespace prefix | Functions | 它是什么 |
|---|---|---|
mlir::RegisteredOperationName | 19,171 | MLIR op registration(每 op 机制) |
asic_sw::driver | 18,834 | TPU device driver / 低层 ASIC software |
mlir::TF | 9,125 | TensorFlow MLIR dialect |
std::__u | 5,449 | libc++(__u inline namespace) |
mlir::detail | 5,389 | MLIR internals |
tensorflow::(anon) | 2,514 | TensorFlow translation-unit-local |
platforms_deepsea::jellyfish | 2,505 | TPU platform / codegen(“jellyfish”) |
注意 — 反汇编器的 per-function
addr_name字段始终是地址形式(sub_E635524);真实符号位于单独的name字段中。上面的 named/anonymous 拆分通过比较两者计算:只有当函数的name折回sub_形式时,才是 “anonymous”。不要把sub_前缀名解读为函数未命名的证据;该二进制中 99.66% 的函数背后都有真实 mangled 符号。Note: 两个接近但不相等的总数不能混淆。per-function artifact coverage 是 884,843 个条目;权威 function-record count 是 884,832。差值正好是 11,即少数 thunk/alias/data-stub 条目获得了 artifact file,但没有被记为完整 function record。本页引用函数数量时使用 884,832;artifact coverage 是 884,843。见 Methodology 和 Deep Methodology。
named/anonymous 机制、namespace 集中度,以及 326 K 个 leaf function 如何与 call-graph 交互,在 Per-Gen Function Dispatcher(每世代入口 fan-out)中展开,并把函数群体框架延续到 RTTI / Vtable Census。
Capsule Atlas — 主要嵌入区域
除标准 ELF section 外,该文件还携带若干大型 capsule:自包含的嵌入式区域,其内容类型不同于普通编译代码。下方 atlas 是粗粒度的:它命名每个区域、定位它,并指向逆向其内部的页面。这是一张路由表,不是分析。
| Capsule | 位置 | 性质 | 归属页面 |
|---|---|---|---|
| Code body | .text (0xe63c000–0x21217484) | 300 MiB x86-64 | ELF Anatomy |
| Large read-only data | .lrodata (108 MiB) | 超过 32-bit reach 的常量池 | ELF Anatomy |
| Protobuf descriptor pool | protodesc_cold (3.2 MiB) | Serialized FileDescriptorProtos | Custom Sections |
| File-wrapper TOC | filewrapper_toc (488 B) | 嵌入式 file blob 的索引 | Custom Sections |
| upb extension registry | linkarr_upb_AllExts (1.2 KiB) | upb extension 的 link-array | Custom Sections |
| Restartable-sequence metadata | __rseq_cs, __rseq_cs_ptr_array | Per-CPU rseq critical sections | Custom Sections |
| Embedded allocator | google_malloc + data/bss | 静态链接 TCMalloc | ELF Anatomy |
| Dispatch / vtable tables | .rodata, .data.rel.ro | C++ vtables、RTTI、jump tables | Dispatch-Table Taxonomy, RTTI/Vtable Census |
| LLVM/MLIR manifest | .rodata string pools | Pass names、dialect/op tables | LLVM/MLIR Manifest |
| Trailing compressed blob | end-of-file region | 疑似 zstd payload | Trailing zstd Blob |
陷阱 — section header table 正好在 EOF 结束:
e_shoff(781,687,720) + 52 × 64 bytes = 781,691,048 = file size。因此 section header 之后没有朴素的尾随数据。对此层中 zstd frame magic(28 b5 2f fd)的普通 ASCII/byte 扫描返回零命中。任何 “trailing zstd blob” 因而要么位于某个 section 内部(例如.lrodata/.rodata中的嵌入 payload),要么在此构建中不存在;该判断以 LOW 置信度保留,并由 Trailing zstd Blob 通过适当搜索负责。本页不断言该 blob 存在,只路由这个问题。
双二进制拆分
wheel 目录包含两个 ELF 对象,而不是一个。两者都已反汇编;分析把它们视为一对,因为 runtime/driver 拆分跨越两者。
| Object | Size | Functions | Needed libs | 作用 |
|---|---|---|---|---|
libtpu.so | 745.5 MiB | 884,832 | libm, libpthread, libdl, librt, libc, ld | PJRT plugin:compiler + runtime + driver |
sdk.so | 21.5 MiB | 94,732 | libstdc++, libgcc_s, libpthread, libm, libc, ld | SDK / support library |
两点区分它们。libtpu.so 不链接外部 libstdc++:它的 C++ runtime 是静态嵌入的(与焊入的 allocator 和函数群体中看到的 std::__u / libc++ namespace 一致)。相比之下,sdk.so 动态需要 libstdc++.so.6,并呈现 protobuf/absl-heavy namespace(google::protobuf, absl::lts_*, libtpu::sdk, tpu::monitoring)。完整来源,包括为什么有两个对象、每个对象拥有什么、符号如何在它们之间流动,是 Two-Binary Split 的主题。
Forensics 部分路线图
本概览是 Binary Forensics 部分的索引。上面命名的每个区域或结构事实都在同级页面中深入逆向;建议大致按此顺序阅读。
overview (this page) ── container shape, population, capsule routing
├─ elf-anatomy ─────── sections, segments, code-model, split families
├─ two-binary-split ── libtpu.so vs sdk.so, symbol flow
├─ static-init ─────── .init_array / .preinit_array / .text.startup order
├─ custom-sections ─── protodesc_cold, filewrapper_toc, __rseq_cs, upb
├─ embedded-library-atlas ── third-party libs welded in (absl, protobuf, MLIR…)
├─ dispatch-table-taxonomy ── jump/dispatch table families and their shapes
├─ rtti-vtable-census ─ C++ class hierarchy from RTTI + vtables
├─ polymorphic-entry-points ── the virtual entry surface of the plugin
├─ per-gen-function-dispatcher ── per-TPU-generation code selection
├─ llvm-mlir-manifest ── recovered pass / dialect / op inventory
└─ trailing-zstd-blob ── the suspected compressed payload (LOW confidence)
```text
- [ELF Anatomy](elf-anatomy.md) — 完整 section/segment 走查;代码模型;hot/cold/large split 家族。
- [Two-Binary Split](two-binary-split.md) — 把 `libtpu.so` 和 `sdk.so` 作为一对;每个对象拥有什么以及它们如何链接。
- [Static Initialization](static-init.md) — `.preinit_array`(16 B / 2 个指针)、`.init_array`(23,200 B / 2,900 个指针)和 `.text.startup`;构造器顺序。
- [Custom Sections](custom-sections.md) — 具名非标准 section:protobuf descriptor pool、file-wrapper TOC、upb extension link-array、`rseq` metadata。
- [Embedded-Library Atlas](embedded-library-atlas.md) — 从 namespace census 识别出的静态链接第三方库(libc++、absl、protobuf、MLIR、TensorFlow)。
- [Dispatch-Table Taxonomy](dispatch-table-taxonomy.md) — `.rodata` / `.data.rel.ro` 中的 jump-table 和 dispatch-table 家族(报告 33,016 个 switch construct)。
- [RTTI / Vtable Census](rtti-vtable-census.md) — 从 RTTI records 和 vtable layout 重建 C++ class hierarchy。
- [Polymorphic Entry Points](polymorphic-entry-points.md) — runtime dispatch 经过的 virtual call surface。
- [Per-Gen Function Dispatcher](per-gen-function-dispatcher.md) — 二进制如何按 TPU generation 选择 code path。
- [LLVM/MLIR Manifest](llvm-mlir-manifest.md) — 从 MLIR namespaces 恢复出的 pass、dialect 和 op 清单。
- [Trailing zstd Blob](trailing-zstd-blob.md) — 疑似 compressed payload;本页只路由该问题(见上方 GOTCHA)。
---
## 交叉引用
- [ELF Anatomy](elf-anatomy.md) — 本页概述的 section/segment 细节。
- [Two-Binary Split](two-binary-split.md) — 此处引入的 `libtpu.so` / `sdk.so` 配对。
- [Custom Sections](custom-sections.md) — 上方 atlas 中的具名 capsule。
- [Trailing zstd Blob](trailing-zstd-blob.md) — LOW-confidence trailing-payload 判断的归属页面。
- [Per-Gen Function Dispatcher](per-gen-function-dispatcher.md) — 展开函数群体框架。
- [Lifecycle Overview](../lifecycle/overview.md) — 静态初始化 plugin 加载后如何被驱动。