嵌入式库图谱
本页中的所有字节偏移、大小和版本字符串均适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(发布标记libtpu_lts_20260413_b_RC00,build-id89edbbe81c5b328a958fe628a9f2207d,磁盘上 781,691,048 bytes)。其他 wheel 会不同。
摘要
libtpu.so 是一个单一的静态链接 PJRT 插件,在一个 ELF 内携带了完整的编译器、运行时和 ML 栈。它的动态依赖列表有六项 — libm、libpthread、libdl、librt、libc 和 ld-linux-x86-64 — 没有 libstdc++,也没有 libgcc_s。其他所有东西,从 C++ 标准库到 LLVM/MLIR,再到 crc32 表,都被编译进来了。本页是物料清单:权威列出哪些第三方库被嵌入、证明每个库存在的二进制证据、二进制固定的版本,以及每个库占据的大致代码足迹。
对一个已发布制品来说,不寻常的是,这个二进制没有被 strip。它的 .symtab 保留了约 1.2 百万个已定义的 Itanium-mangled symbols。这完全改变了取证方法:经典 FLIRT 签名匹配 — 面对 stripped binary 时会使用的技术 — 在这里并不需要,因为每个函数已经用明文声明了自己的所属 namespace(absl::、mlir::、re2::、tcmalloc::)。因此,本图谱通过对 demangled symbol pool 进行 namespace 分桶构建,并用烘进 .rodata 的 third_party/... source-path literals 以及带版本的 banners 进行佐证。全文出现三类证据:namespace marker(demangled symbol prefix,出现在某个 file offset)、version string(固定某个 release 的 banner 或 path literal)以及 symbol-family count(某个库从 IDA names sidecar 贡献多少不同名称)。引用原始计数时,它是文本包含该 family fragment 的 symbol-table names 数量。
下面的结构先给出一眼可见的物料清单,然后把细节分成四个家族:Google C++ 核心(Abseil、protobuf、tcmalloc、riegeli、RE2、farmhash)、数学/ML 层(Eigen、oneDNN)、压缩/加密层(zlib、zstd、Brotli、snappy、BoringSSL)和杂项(ICU、gRPC、RE2 的相邻工具)。LLVM 和 MLIR 是迄今最大的单体占用者;它们有自己的页面,这里只作为 line items 摘要 — commit pin 和 dialect inventory 见 LLVM/MLIR 清单。
对重实现者而言,契约是:
- 依赖姿态 — 哪些是动态链接的(六个库),哪些是静态嵌入的(其他全部,包括 C++ runtime),以及如何从
DT_NEEDED确认。 - 每个库的检测方法 — 哪个 namespace marker、path literal 或 banner 证明存在,以及位于哪个 file offset,使 inventory 可以复现和重新验证。
- 二进制实际固定的 version pins(oneDNN v3.3、ICU 78、protobuf editions、LLVM trunk commit),相对于它只给出下限的版本(Abseil LTS、Eigen 3.4.x、gRPC),以及每个结论为什么落在那里。
| 二进制 | libtpu.so, 781,691,048 bytes, ELF64 DYN x86-64 |
| Build-id | 89edbbe81c5b328a958fe628a9f2207d (.note.gnu.build-id) |
| 发布标记 | libtpu_lts_20260413_b_RC00 @ file offset 0x84A0E80 (139,071,104) |
| 动态依赖 | 6: libm, libpthread, libdl, librt, libc, ld-linux-x86-64 |
| 是否 stripped? | 否 — .symtab retains ~1.2 M mangled symbols;classification is by namespace, not FLIRT |
| 工具链 | clang version 9999.0.0 (a70419505471…) @ 0xAF59226; Bazel r4rca-2026.04.04-1 (mainline @894239244) @ 0x84A12A0 |
| 最大占用者 | LLVM + MLIR(见 sibling page),然后是 Abseil、oneDNN、libc++、Eigen |
物料清单
这张表是权威 inventory。Version evidence 是二进制支持的最强 pin:存在时是 path literal 或 banner;仅能观察行为时则是 feature-floor。Footprint 是库的 namespace 拥有的 code-plus-rodata symbol bytes 的数量级份额 — 粗略的大小信号,不是 linker map。Confidence 反映存在性和版本被多直接地观察到:CERTAIN = namespace 和 version string 都存在;HIGH = namespace family 存在,version 从 features 推断;MEDIUM = 存在但 version unresolved。
足迹尺度供定向参考:huge ≳ 25 MB,large 5–25 MB,medium 1–5 MB,small 0.1–1 MB,trace < 0.1 MB。
| 库 | 版本证据 | 足迹 |
|---|---|---|
| LLVM(见 相邻页面) | LLVM version g3_____-trunk 8918319853fbdf… @ 0xB1FA070 — trunk commit,无 upstream tag | huge (~84 MB) |
| MLIR(见 相邻页面) | 与同一个 LLVM trunk 捆绑;mlir:: namespace | huge (~72 MB) |
| Abseil | absl::log_internal::SetTimeZone @ 0x8714E5B; absl::AnyInvocable @ 0x188C785 → ≥ LTS 20240722,ceiling open | huge (~29 MB) |
| Intel oneDNN (DNNL) | path third_party/intel_dnnl/v3_3/src/common @ 0x85BEAE8 → v3.3 | large (~25 MB) |
| libc++ | std::__u:: ABI tag — google3 libc++,匹配 LLVM commit | large (~18 MB) |
| Eigen | Eigen namespace,28,702 names;Eigen::bfloat16 tensor ABI → 3.4.x branch,exact tag open | large (~12 MB) |
| protobuf | EDITION_2024 @ 0x5EE8C1E, EDITION_2026 @ 0x5EE8C31; descriptor_table_protodef @ 0x23DF7736 → v32+ editions-era | medium (~8 MB) |
| gRPC | grpc_core:: @ 0x188C4DE; chaotic_good transport → google3-tip(无 tagged release),≥ v1.66 dev | medium (~3 MB) |
| BoringSSL | BoringSSL banner @ 0x857D84C; bssl:: / EVP_/SSL_/X509_ C symbols | medium (~1.3 MB) |
| Brotli | BrotliDecoderDecompress @ 0xA2869A1; static-dictionary tables → ≥ 1.1,通过 riegeli 到达 | small (~0.85 MB) |
| zstd | ZSTD_compressBound @ 0x270D623C, ZSTD_createDCtx @ 0x8716BDF; bundled as third_party/zippy/ | small (~0.6 MB) |
| ICU | data file icudt78 @ 0x83FE04C; namespace icu_78 → ICU 78 | small (~0.5 MB) |
| riegeli | path third_party/riegeli/ @ 0x85D7066; riegeli:: namespace | small (~0.3 MB) |
| snappy | snappy:: namespace @ 0xA1251E5 | trace (~18 KB) |
| RE2 | re2:: @ 0x86679B9, re2::RegexpFlag, third_party/re2 | small (~0.12 MB) |
| zlib | zlib_rs Rust-port symbols @ 0x852F25E; deflate/inflate/crc32 C symbols | small (~0.17 MB) |
| Xbyak (oneDNN JIT) | Xbyak namespace @ 0xB38846D | trace (~90 KB) |
| tcmalloc | tcmalloc:: namespace;custom google_malloc ELF section | trace (~55 KB code, allocator-only) |
| farmhash | farmhash family(farmhashna/uo/te/xo/mk/cc)@ 0xfdce579 | trace (~7 KB) |
NOTE — footprint 数字是 named symbol bytes 的份额,而不是 segment sizes。它们能正确排列库之间的相对大小,但不应被当作精确的
.text贡献;一个失去 namespace prefix 的 Abseil TU-local helper 会被计入 anonymous residue,而不是 Abseil。应把 scale buckets 视为可靠信号,而不是括号内数字。QUIRK —
DT_NEEDED中缺少libstdc++.so.6和libgcc_s.so.1是本页最具后果的事实。标准 C++ runtime 以 google3 的 libc++ 构建形式静态链接,可通过每个std::符号上的__uinline-namespace ABI tag 识别(std::__u::basic_string、std::__u::vector)。假设加载了系统libstdc++的重实现者会在 string 和 exception ABI 上不匹配。相比之下,配套的sdk.so确实声明libstdc++.so.6— 两个二进制做出了相反的链接选择;见 双二进制拆分。
Google C++ 核心
这些库构成其他每个 google3 组件的基础:collections、strings、status propagation、logging、allocator 和 serialization stack。它们无处不在 — 单是 Abseil 就比任何非 LLVM 库贡献了更多 named symbols。
Abseil
Abseil 是基底。它的 absl:: namespace 出现在 109,189 个 symbol-table names 中(nm -C libtpu.so | grep -c 'absl::'),是该二进制中最大的非 LLVM/MLIR family,覆盖 absl::Status/StatusOr、absl::Cord、absl::flat_hash_map、flags library 和 structured-logging stack。
版本有下限但没有上限。offset 0x8714E5B(141,643,355)处存在 absl::log_internal::SetTimeZone,0x188C785(25,741,189)处存在 absl::AnyInvocable,这把构建放在 LTS 20240722 或之后 — 两者都是在该 cut 或之后落地的功能。.rodata 中没有留下 ABSL_LTS_RELEASE_VERSION 或 ABSL_OPTION_* literal,因此无法仅从字符串固定上界。要闭合它,需要对 tagged Abseil trees 做逐符号 diff(例如检查 SwissTable container internals 是否匹配 LTS 20250127 重组)。
absl::log_internal::SetTimeZone @ 0x8714E5B → ≥ LTS 20240722
absl::AnyInvocable @ 0x188C785 → ≥ LTS 20240722
absl:: (symbol family) 109,189 names → pervasive
```text
### protobuf
Protobuf 处于现代 **editions** 时代。enum literals `EDITION_2024`(offset `0x5EE8C1E`,99,519,518)和 `EDITION_2026`(`0x5EE8C31`,99,519,537)在 `.rodata` enum-name pool 中相邻出现,生成的 descriptor 机制则以 `descriptor_table_protodef_*` symbols(首个在 `0x23DF7736`,601,847,606)以及 `TableStruct_*` reflection metadata 的形式出现。`EDITION_2024`-production / `EDITION_2026`-declared 组合与 protobuf **v32 或之后**一致(editions support 在 v32 发布)。确切 minor version 没有以 banner 暴露;固定它需要把 serialized descriptor-pool wire format 与候选源码树匹配。
> **NOTE —** 760 条 serialized `FileDescriptorProto` records 位于专用的 `protodesc_cold` ELF section 中,被构建从 `.rodata` 其余部分隔离出来。这种隔离是一个编译时提示:descriptor pool 是 cold-path data;它记录在 [自定义 Sections](custom-sections.md) 下,而不是在这里重复。
### tcmalloc
allocator 是 google3 的 TCMalloc,而不是系统 `malloc`。两项证据具有决定性:`tcmalloc::` / `tcmalloc_internal::` namespace family(107 names;裸 `tcmalloc` fragment 为 110),以及构建专门为它发出的一组 **custom ELF sections** — `google_malloc`(allocator code body)、`google_malloc_bss`(其 `.bss` state)、`malloc_hook`(`MallocHook` lifecycle thunks)和 `__rseq_cs`(per-CPU caches 的 restartable-sequence critical-section metadata)。`__rseq_cs` section 是 post-2024 TCMalloc 的 rseq-accelerated per-CPU cache design 的明显标记。named code footprint 很小(约 55 KB),因为 allocator 大多是紧凑的 hot path;标记它的是 section-level isolation,而不是 byte count。
### riegeli、RE2、farmhash
三个较小的 google3 常用组件补齐了 core:
- **riegeli** — record-IO format。`0x85D7066`(140,341,350)处的 path literal `third_party/riegeli/` 和 `riegeli::` namespace 确认它存在。Riegeli 是拉入 Brotli(见下)的消费者,用其作为 block codec。
- **RE2** — regex engine。`0x86679B9`(140,933,561)处的 namespace `re2::`,加上 internal `re2::RegexpFlag` symbol 和 `third_party/re2` path。约 18,722 个 mangled names 携带 `re2` fragment(`nm libtpu.so | grep -c re2`),不过该计数包括生成的 automaton tables,不只是 engine code;demangled `re2::` namespace 自身只占 605 names。
- **farmhash** — non-cryptographic hash。完整的子变体 family 均存在(`farmhashna`、`farmhashuo`、`farmhashte`、`farmhashxo`、`farmhashmk`、`farmhashcc`,从 `0xfdce579` 开始),这具有决定性:偶然字符串不会复现全部六个 dispatch variants。Footprint 只是少数几个函数(约 7 KB)。
---
## 数学 / ML 层
这是 XLA 和 TPU runtime 依赖的数值核心:来自 Eigen 的 dense linear algebra 和 tensor expression templates,以及用于 host-side fallback paths 的 x86 JIT-kernel library oneDNN。
### Eigen
Eigen 贡献 28,702 个 symbol-table names(`nm libtpu.so | grep -c Eigen`)— 是 Abseil 和 LLVM/MLIR 组合之后第三大的 C++ family — 几乎全部是 unsupported `Tensor` module 的 template instantiations(`Eigen::Tensor`、`ThreadPoolDevice`、async executor)。`Eigen::bfloat16` 作为 first-class scalar 存在(而不只是 legacy `Eigen::half`),把该构建放在 **3.4.x branch 或之后**。`.rodata` 中没有留下 `EIGEN_WORLD_VERSION` / `EIGEN_MAJOR_VERSION` macro literal,因此无法用字符串证据区分 3.4.0 和 3.4-series snapshot;pin 指向 branch,而不是 tag。
> **GOTCHA —** Eigen 的 footprint 几乎全是 template bloat。29 K names 并不意味着 29 K 个不同的 engine logic 函数 — 它们是同一小组 expression templates 按 type、device 逐一实例化的结果。给“真实” Eigen surface 估算大小的重实现者应统计不同 algorithm bodies,而不是 symbol names。
### Intel oneDNN (DNNL)
oneDNN 是该二进制**精确**固定的数学库。offset `0x85BEAE8`(140,241,640)处的 source-path literal `third_party/intel_dnnl/v3_3/src/common` 把版本固定为 **v3.3**,无需推断 — version 被直接编码在 vendored path 中。oneDNN 带来了自己的 JIT assembler **Xbyak**(namespace 在 `0xB38846D`),用来在运行时发出 CPU kernels;因此 Xbyak 的存在是 oneDNN 的结果,而不是独立依赖。组合 footprint 很大(约 25 MB named symbols),反映了 oneDNN 发布的 primitive implementations(convolution、matmul、normalization)的广度。
---
## 压缩 / 加密层
嵌入了四种 compression codecs 和一个 TLS/crypto stack。codec 选择映射到不同消费者:zstd 用于 wheel-internal payload 和 runtime block compression,Brotli 作为 riegeli 的 record codec,snappy 和 zlib 用于 legacy compatibility paths,BoringSSL 用于 transport security。
### zstd
zstd 存在并承担两个角色。runtime-codec 角色显示为完整的 `ZSTD_*` C API — offset `0x270D623C`(655,188,540)处的 `ZSTD_compressBound`、`0x8716BDF` 处的 `ZSTD_createDCtx`,以及 `ZSTD_CCtx`/`ZSTD_c*` context machinery — 通过 google3 的 `third_party/zippy/` wrapper 捆绑。第二个角色是解压 wheel-internal trailing blob,这由自己的页面说明,这里不重复。
> **NOTE —** `.rodata` 中没有留下 `ZSTD_versionNumber` banner 或 `1.5.x` literal,因此无法从字符串固定确切 zstd release — 只能确认 API surface。两种 zstd 用途(runtime codec 与 trailing payload)分别记录;trailing-blob 叙事见 [尾部 zstd Blob](trailing-zstd-blob.md)。
### Brotli 和 snappy
- **Brotli** 通过 riegeli 作为 record-block codec 到达。`0xA2869A1`(170,420,641)处的 `BrotliDecoderDecompress` 和嵌入的 static-dictionary tables(Brotli 发布的大型 `.rodata` arrays)确认它存在;static-dictionary 形式指向 Brotli ≥ 1.1。symbol table 中有 16 个 `BrotliDecoder*` names — 一个薄的、偏 decode 的 surface,与 read-path use 一致。
- **snappy** 作为 `0xA1251E5`(168,972,773)处的 `snappy::` namespace 存在 — trace footprint(约 18 KB),是 legacy fast-compression path。
### zlib
zlib 以一种不寻常的形式存在:主要实现是 **Rust port** `libz-rs`,其 mangled symbols 携带 `zlib_rs` fragment(首个在 `0x852F25E`,139,653,726),同时还有经典 C `deflate`/`inflate`/`crc32` entry points 和 `crc_braid_table` lookup。这是二进制中唯一由 Rust crate 提供 C-ABI compression primitive 的地方;它与 C zlib symbols 共存(而不是取代它们)。
### BoringSSL
crypto 和 TLS stack 是 **BoringSSL**,不是系统 OpenSSL。literal `BoringSSL` banner 出现在 offset `0x857D84C`(139,974,732),并且 C symbol families `EVP_*`、`SSL_*`、`X509_*`、`BN_*`、`RSA_*`、`AES_*`、`SHA256_*`、`ED25519_*`、`X25519_*` 全部存在(9 个 names 直接携带 `BoringSSL` fragment;algorithm families 远大得多)。它 vendored 在 `third_party/openssl/` 下,这是 BoringSSL fork 的常规 google3 位置。BoringSSL 是嵌入式 gRPC stack 背后的 transport-security provider。
---
## 杂项
### ICU
International Components for Unicode 以固定版本存在:offset `0x83FE04C`(138,403,916)处的 data-file marker `icudt78` 和 versioned C++ namespace `icu_78` 一起把它固定为 **ICU 78**(`icudt78` 是 ICU 78 data-bundle 命名约定,`icu_78::` 是 ICU 78 namespace-versioning macro expansion)。ICU 把自己的 trie-based property tables(`propsTrie`、`ucase_props`、`ubidi_props`、`norm2_nfc_data`)作为 `.rodata` blobs 拉入。
### gRPC
gRPC 作为 google3-tip 嵌入,而不是 tagged release。`grpc_core::` namespace(首个在 `0x188C4DE`,25,740,510)以及 **`chaotic_good`** transport 的存在 — 它只存在于 gRPC trunk,直到 v1.71 都没有 tagged release 包含 — 将其放在 **≥ v1.66 dev** 的开发分支上。`grpc_version_string` symbol 存在,但其 build-time `gRPC C++ x.y.z` literal 被 Google 内部 release tagging 替换,因此无法读出 upstream semver。gRPC 的 TLS 由上面的 BoringSSL stack 提供;serialization 由 protobuf stack 提供。
> **Note:** **double-conversion**(wheel 的 `THIRD_PARTY_NOTICES.txt` 中命名的 float↔string library)**未**列入物料清单:对 `double_conversion` / `double-conversion` markers 的直接探测在 `.rodata` 或 symbol table 中没有返回任何内容。它可能只作为没有残留 namespace marker 的内联 `absl::str_format` internals 存在,也可能完全不在此构建中。没有 binary marker 就不计为存在 — 相信二进制,没有 marker 就没有 entry。
>
> **NOTE —** wheel 的 `THIRD_PARTY_NOTICES.txt`(731,537 bytes)命名了比本图谱多得多的组件。notices 文件是一个*法律超集*:它覆盖构建配置中可能被链接的所有内容,包括从这个具体 artifact 中编译出去的组件。上方物料清单中的每个库都独立于 notices 文件、从 binary evidence 重新推导而来;只出现在 `THIRD_PARTY_NOTICES.txt` 中,而二进制里没有 namespace marker 或 path literal 的名称,不计为存在。
---
## 复现
因为二进制未 strip,inventory 无需专门工具即可复现。端到端方法如下:
```bash
# 1. Confirm the dependency posture — six entries, no libstdc++/libgcc.
readelf -d libtpu.so | grep NEEDED
# 2. Pin the build identity (version-bearing banners, with offsets).
grep -aboE 'LLVM version g3[_]+-trunk [0-9a-f]{40}' libtpu.so
grep -aboE 'libtpu_lts_[0-9]+_b_RC[0-9]+' libtpu.so
# 3. Per-library presence: namespace marker / path literal / banner, at an offset.
grep -aboE 'third_party/intel_dnnl/v3_3/src/common' libtpu.so # oneDNN v3.3
grep -aboE 'icudt78' libtpu.so # ICU 78
grep -aboE 'EDITION_2024|EDITION_2026' libtpu.so # protobuf editions
grep -aboE 'BoringSSL|ZSTD_compressBound|re2::' libtpu.so # crypto / codec / regex
# 4. Symbol-family counts: how many names a library contributes.
# Bucket the demangled symbol pool by namespace prefix.
nm -SC --defined-only libtpu.so | grep -c 'absl::'物料清单中的每一行都对应一个这样的 probe;上面引用的 file offsets 是字面结果,因此独立验证者会落在同一个 byte 上。
QUIRK — 因为二进制未 strip,诱惑在于绝对相信名称并跳过 path-literal 佐证。对 version claims 不要这样做。
re2::这样的 namespace 能证明库被链接,但不能说明其版本;只有 path literal(third_party/intel_dnnl/v3_3/...)或 data-file marker(icudt78)能固定 release。存在性和版本是两个不同的 claims,具有两种不同的证据等级 — Confidence column 跟踪两者中较弱的那个。
交叉引用
- LLVM/MLIR 清单 — 负责两个最大占用者(LLVM trunk
8918319853fbdf…、MLIR dialects);本图谱只把它们列为 line items - 二进制取证概览 — 取证工作的 top-level map,以及本图谱在其中的位置
- 双二进制拆分 —
sdk.so确实声明libstdc++.so.6;与libtpu.so嵌入的 libc++ 做出相反链接选择 - 尾部 zstd Blob — wheel-internal zstd payload;本页记录 zstd 的存在,那个页面讲述它的故事
- 自定义 Sections —
google_malloc、protodesc_cold、__rseq_cs以及其他 build-emitted sections,它们隔离了 tcmalloc 和 protobuf descriptor pool - ELF 剖析 — section-size accounting,为 per-library footprint figures 提供框架