Skip to content

二进制取证概览

本页中的所有偏移、地址、section 名称和计数均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so:一个 781,691,048 字节的 ELF64 共享对象,build-id 89edbbe81c5b328a958fe628a9f2207d,报告的运行时版本为 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_mallocmalloc_hookprotodesc_coldfilewrapper_toc__rseq_cslinkarr_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_LOAD segment,代码、只读数据和可写数据的位置,以及 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-id89edbbe81c5b328a958fe628a9f2207d(NT_GNU_BUILD_ID, md5/uuid 形式)
报告版本0.103(来自 package metadata;见下方注释)
Stripped? — 存在完整 .symtab(1,233,710 个条目)
Sections52 (e_shnum); .shstrtab index 50
Program headers11 (e_phnum); 4 × PT_LOAD
Section headers atoffset 0x2e979ba8 (781,687,720) — 正好延伸到 EOF
Entry point0x0(无 — 这是库,由 .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 占据了文件的绝大部分。

SectionSizeShare作用
.text299.9 MiB (314,422,404 B)~40 %主要可执行代码
.strtab171.5 MiB (179,840,222 B)~23 %符号名字符串(未 strip)
.lrodata108.1 MiB (113,305,552 B)~14 %大模型只读数据
.rodata57.9 MiB (60,731,176 B)~8 %只读常量、vtables、RTTI
.eh_frame28.7 MiB (30,062,700 B)~4 %DWARF CFI unwind tables
.symtab28.2 MiB (29,609,040 B)~4 %1,233,710 个符号条目
.rela.dyn24.5 MiB (25,660,464 B)~3 %动态重定位
(其余 45 个 section)~38 MiB~5 %unwind hdr、got、data、自定义项

注意 — 单独 .text(299.9 MiB)就大于典型的完整 LLVM libLLVM.so。理解该文件的最大杠杆是:代码是 small/medium code model 中的一个巨大 section,并带有用于 displacement 无法放入 32-bit RIP-relative reference 的对象的 “large” overflow 家族(.lrodata/.ldata/.lbss)。下面的 split-section 纪律正是让这么大的二进制仍可链接的原因。

陷阱 — 磁盘字节分布不是运行时内存分布。.symtab.strtab(合计约 200 MiB)属于任何 PT_LOAD segment,属于 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

text
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.rodataprotodesc_cold.gcc_except_table、EH frames)和整个代码 section 家族。其余三个 RW segment 按标准 RELRO / data / large-data 线拆分。

代码 section 家族

编译器把代码发射到八个不同 section,而不是一个 section;这是链接器用于改善局部性的 hot/cold/startup 划分:

SectionSize含义
.text299.9 MiB主代码体
.text.startup1.41 MiB (1,483,860 B)静态初始化 / 构造器代码(运行一次)
.text.unlikely417 KiB (427,113 B)冷路径(错误/abort/慢路径)
.text.hot7.5 KiB (7,726 B)profile-hot 内层循环
google_init_cold24.2 KiB (24,817 B)嵌入式 allocator 的冷初始化
google_malloc17.7 KiB (18,162 B)嵌入式 malloc 实现(TCMalloc 风格)
malloc_hook2.2 KiB (2,206 B)Allocation hook trampolines
__lcxx_override261 Blibc++ 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++ 名称成功 demangled822,847 (93.0 %)
Thunks750
Leaf functions(无 callee)326,941 (37 %)
函数大小中位数72 字节
95th-percentile 函数大小1,256 字节

该群体由少数几个 C++ namespace 主导,这是说明该二进制是什么的最清晰单一信号:

Namespace prefixFunctions它是什么
mlir::RegisteredOperationName19,171MLIR op registration(每 op 机制)
asic_sw::driver18,834TPU device driver / 低层 ASIC software
mlir::TF9,125TensorFlow MLIR dialect
std::__u5,449libc++(__u inline namespace)
mlir::detail5,389MLIR internals
tensorflow::(anon)2,514TensorFlow translation-unit-local
platforms_deepsea::jellyfish2,505TPU 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。见 MethodologyDeep 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 (0xe63c0000x21217484)300 MiB x86-64ELF Anatomy
Large read-only data.lrodata (108 MiB)超过 32-bit reach 的常量池ELF Anatomy
Protobuf descriptor poolprotodesc_cold (3.2 MiB)Serialized FileDescriptorProtosCustom Sections
File-wrapper TOCfilewrapper_toc (488 B)嵌入式 file blob 的索引Custom Sections
upb extension registrylinkarr_upb_AllExts (1.2 KiB)upb extension 的 link-arrayCustom Sections
Restartable-sequence metadata__rseq_cs, __rseq_cs_ptr_arrayPer-CPU rseq critical sectionsCustom Sections
Embedded allocatorgoogle_malloc + data/bss静态链接 TCMallocELF Anatomy
Dispatch / vtable tables.rodata, .data.rel.roC++ vtables、RTTI、jump tablesDispatch-Table Taxonomy, RTTI/Vtable Census
LLVM/MLIR manifest.rodata string poolsPass names、dialect/op tablesLLVM/MLIR Manifest
Trailing compressed blobend-of-file region疑似 zstd payloadTrailing 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 拆分跨越两者。

ObjectSizeFunctionsNeeded libs作用
libtpu.so745.5 MiB884,832libm, libpthread, libdl, librt, libc, ldPJRT plugin:compiler + runtime + driver
sdk.so21.5 MiB94,732libstdc++, libgcc_s, libpthread, libm, libc, ldSDK / 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 部分的索引。上面命名的每个区域或结构事实都在同级页面中深入逆向;建议大致按此顺序阅读。

text
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 加载后如何被驱动。