ELF 剖析
本页中的所有偏移、虚拟地址和计数均适用于
libtpu-0.0.40wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d,构建标签libtpu_lts_20260413_b_RC00)。该 wheel、其dist-info/METADATA以及libtpu/__init__.py都报告 wheel 版本为0.0.40;build-id 是唯一无歧义的锚点,也是每个页面固定引用的字段。其他构建在每个地址上都会不同。
摘要
libtpu.so 是一个 745 MiB(781,691,048 字节)的、位置无关的 ELF64 共享对象 — ET_DYN、EM_X86_64、小端序、System V ABI。它把整个 Google TPU PJRT 插件静态链接进了一个文件:XLA 编译器、MLIR/LLVM 后端、TPU 运行时、gRPC、protobuf、Abseil、Eigen 以及设备驱动都在其中。它没有 DT_SONAME,没有 RPATH/RUNPATH,没有单独的调试对象,并且只依赖六个裸系统库。尽管体积巨大,它仍然是一个完全普通的 clang/LLVM 构建的 .so,只是规模放大了三个数量级。
决定性结构事实是,这个对象使用 x86-64 large code model 构建。该模型把只读数据和 BSS 数据拆成两层:普通 small-model .rodata/.bss 可通过 32 位 %rip 相对位移到达,而 large-model .lrodata/.lbss/.ldata(l 标志,SHF_X86_64_LARGE)由于与 .text 的距离超过 ±2 GiB,需要通过 64 位绝对寻址到达。仅 .lrodata 就有 108 MiB。这就是运行时依赖的单例表 — 包括 PJRT API vtable — 位于 vaddr 0x227ba840 附近的 .lbss,而不是 .bss 的原因。假设只有一对 .rodata/.bss 的重实现者会把三分之一的常量数据映射错误。
本页是该二进制的权威 section/segment/dynamic 映射。本 wiki 其他地方引用的每个地址都位于下表列出的某个区域内;本页确定哪个 section 拥有哪些 vaddr 范围,哪个 PT_LOAD 携带哪些权限,以及动态链接器在加载时获得什么指令。深入内容 — .init_array 构造函数是谁,以及异常的 google_*/__rseq_cs/linkarr_* sections 持有什么 — 属于各表处标注的相邻页面。
对重实现而言,契约是:
- 四段式加载映像 — 一个
R Etext/rodata segment,后接三个RWdata segments,以及第一个 segment 之后打破 file-offset ≡ vaddr 的页移位规则。 - large-code-model section 拆分 —
.rodata/.bss/.data(small)相对于.lrodata/.lbss/.ldata(large,带l标志),以及为什么运行时的单例落在 large 层。 - 动态契约 — 六个
DT_NEEDED、仅 GNU-hash 的查找、带有一百多万个R_X86_64_RELATIVE重定位的.rela.dyn/.rela.plt拆分,以及 init/fini/preinit array 锚点。
| 文件大小 | 781,691,048 bytes (~745 MiB) |
| 类别 / 数据 / 类型 | ELF64 · LSB (2's complement, little-endian) · ET_DYN |
| 机器 / OS-ABI | EM_X86_64 · UNIX System V (ABI v0) |
| 入口点 | 0x0(无 — 纯共享库) |
| Program headers | 11 entries, 56 bytes each, at file offset 64 |
| Section headers | 52 entries, 64 bytes each, at file offset 0x2e979ba8 |
.shstrtab 索引 | section [50] |
| Build-id (NT_GNU_BUILD_ID) | 89edbbe81c5b328a958fe628a9f2207d (16 bytes) |
DT_SONAME | 无 |
| Code model | x86-64 large(存在 .lrodata/.lbss/.ldata) |
.text 范围 | 0xe63c000 … 0x21217484, 0x12bdb484 bytes (~299 MiB) |
ELF 头部
这个 header 是 readelf -h 的原样内容。本页以及 wiki 其他地方的所有下游地址,都是该 header 所描述地址空间中的虚拟地址。
Class: ELF64
Data: 2's complement, little endian
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Entry point address: 0x0
Start of program headers: 64 (bytes into file)
Start of section headers: 781687720 (bytes into file) # 0x2e979ba8
Size of program headers: 56 · Number of program headers: 11
Size of section headers: 64 · Number of section headers: 52
Section header string table index: 50
```text
三个 header 事实塑造了下面的一切。
- **`Entry point = 0x0`。** 这是库,不是可执行文件;不存在 `_start`。执行通过 `DT_INIT`/`DT_INIT_ARRAY` 机制(由 loader 驱动的构造函数链)以及 `GetPjrtApi` 等导出入口符号进入。进入 init 的机制由 [`lifecycle/elf-entry-and-init-proc.md`](../lifecycle/elf-entry-and-init-proc.md) 负责。
- **section header table 位于文件最末尾**(偏移 `0x2e979ba8`,约 745 MiB 处),在巨大的 `.symtab`/`.strtab` 之后。section headers、symbol table 和 string tables 都*不是*任何 `PT_LOAD` 的一部分;它们存在于磁盘上,但运行时从不映射。
- **包含 `NULL` section `[0]` 在内共 52 个 sections。** 枚举“真实”sections 的工具会报告 51;两个计数描述的是同一张表。
> **NOTE —** 在 `.symtab` 意义上该二进制是 `not_stripped`:磁盘上存在完整的 `.symtab`,包含 `1,233,710` 个 entries(大小 `0x1c3cc50` ÷ 24),外加一个 `0xab824de` 字节(约 172 MiB)的 `.strtab`。这些为静态分析提供每个内部函数的名称,但它们是非 `SHF_ALLOC`,对运行时映像没有任何贡献。运行时只看见 741-entry 的 `.dynsym`。
| | |
|---|---|
| ELF 类别 / 字节序 / 类型 | ELF64 · LSB · `ET_DYN` |
| 机器 | `EM_X86_64` (`Advanced Micro Devices X86-64`) |
| 入口点 | `0x0`(无 `_start`) |
| PHDR 数量 / 大小 | 11 × 56 bytes at offset `0x40` |
| SHDR 数量 / 大小 | 52 × 64 bytes at offset `0x2e979ba8` |
| `.shstrtab` 索引 | `[50]` |
---
## Segment 表(Program Headers)
运行时映像是**四个 `PT_LOAD` segments**:一个 read-execute,三个 read-write。其余七个 program headers 是指向相同字节的元数据视图(`PHDR`、`TLS`、`DYNAMIC`、`GNU_RELRO`、`GNU_EH_FRAME`、`GNU_STACK`、`NOTE`)。所有对齐都是 2 MiB(`0x200000`)— 该构建以 huge pages 为目标。
```text
Type Offset VirtAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x00000040 0x000268 0x000268 R 0x8
LOAD [1] 0x000000 0x00000000 0x213f25d0 0x213f25d0 R E 0x200000
LOAD [2] 0x213f25e0 0x215f25e0 0xa62bc0 0xa63a20 RW 0x200000
LOAD [3] 0x21e551c0 0x222551c0 0x26e6a0 0x343a70 RW 0x200000
LOAD [4] 0x22198c30 0x22798c30 0x021c00 0x0c6650 RW 0x200000
TLS 0x213f25e0 0x215f25e0 0x000110 0x000e78 R 0x20
DYNAMIC 0x21e48b40 0x22048b40 0x000210 0x000210 RW 0x8
GNU_RELRO 0x213f25e0 0x215f25e0 0xa62bc0 0xa63a20 R 0x1
GNU_EH_FRAME 0x0c2cc634 0x0c2cc634 0x6bd684 0x6bd684 R 0x4
GNU_STACK 0x000000 0x00000000 0x000000 0x000000 RW 0x0
NOTE 0x0002a8 0x000002a8 0x000020 0x000020 R 0x4四个 load segments
| Seg | 权限 | 文件偏移 | Vaddr | FileSiz | MemSiz | 包含 |
|---|---|---|---|---|---|---|
| LOAD 1 | R E | 0x0 | 0x0 | 0x213f25d0 (~534 MiB) | 相同 | 元数据、.lrodata、.rodata、EH tables、全部 .text*、.plt |
| LOAD 2 | RW | 0x213f25e0 | 0x215f25e0 | 0xa62bc0 (~10.4 MiB) | 0xa63a20 | TLS template、init/fini/preinit arrays、.data.rel.ro、.dynamic、.got |
| LOAD 3 | RW | 0x21e551c0 | 0x222551c0 | 0x26e6a0 (~2.5 MiB) | 0x343a70 | .data、google_*/__rseq_cs/linkarr_* sections、.got.plt、.bss |
| LOAD 4 | RW | 0x22198c30 | 0x22798c30 | 0x021c00 | 0x0c6650 (~806 KiB) | .ldata、.lbss、google_malloc_bss(large-model writable) |
GOTCHA — file-offset ≡ vaddr 恒等关系只在 LOAD 1 中成立,因为两者都从
0开始。从 LOAD 2 起,linker 将 vaddr 相对 file offset 向前按递增的 2 MiB 步长移位:LOAD 2 的 vaddr − offset 为0x200000,LOAD 3 为0x400000,LOAD 4 为0x600000。计算file_offset = vaddr的工具(在.text内正确)会为 writable segments 中的任何内容读取错误字节 — 包括.dynamic、.got和.lbss单例。始终通过所属PT_LOAD转换,不要使用单一全局 delta。
三个 writable segments 被拆开而非合并,是因为 large-code-model writable data(.ldata/.lbss)必须留在自己的区域:LOAD 4 精确承载带 l 标志的 writable sections,与 LOAD 3 的 small-model .data/.bss 分离。
非 LOAD program headers
GNU_RELRO覆盖整个 LOAD-2 字节范围(0x215f25e0,0xa62bc0bytes)。动态链接器应用重定位后,这整个 10.4 MiB 跨度 — init arrays、.data.rel.ro、.dynamic和.got— 会被重新映射为只读。.got.plt特意位于 LOAD 3,处于 RELRO 之外,这样 lazy PLT binding 仍可写入已解析地址。TLS指向 LOAD-2 开头:filesz 0x110是.tdata初始化映像,memsz 0xe78加上.tbss。对这种体量的二进制来说,thread-local block 很小(3704 bytes)。DYNAMIC是 528 字节(0x210)的.dynamicarray,也可作为 section[34]寻址。GNU_EH_FRAME是 vaddr0xc2cc634处的.eh_frame_hdr二分搜索索引,位于 30 MiB.eh_frame之前。GNU_STACK大小为零且RW(无X):非可执行栈,现代默认值。NOTE是0x2a8处的 32 字节 build-id note。
QUIRK — read-execute LOAD 1 覆盖
0x0…0x213f25d0(约 534 MiB),物理上包含的常量数据远多于代码:.lrodata(108 MiB)、.rodata(58 MiB)和 EH tables(约 38 MiB unwind)都位于 executable segment 内,因为它们是只读的,而不是因为它们可执行。.text是最大租户(约 299 MiB),但在小型二进制意义上该 segment 不是“代码段” — 它是“所有不可变内容”。
Section 表
52 个 section headers。下表按角色分组,并给出 readelf -S 报告的地址、文件偏移、大小和 flags。Flag key:A alloc,X execute,W write,M mergeable,S strings,T TLS,I info-link,o OS-processing,l SHF_X86_64_LARGE。
动态链接元数据(LOAD 1 头部)
[Nr] 名称 | 类型 | 地址 | 偏移 | 大小 | 标志 |
|---|---|---|---|---|---|
[1] .note.gnu.build-id | NOTE | 0x2a8 | 0x2a8 | 0x20 | A |
[2] .dynsym | DYNSYM | 0x2c8 | 0x2c8 | 0x4578 | A |
[3] .gnu.version | VERSYM | 0x4840 | 0x4840 | 0x5ca | A |
[4] .gnu.version_d | VERDEF | 0x4e0c | 0x4e0c | 0x38 | A |
[5] .gnu.version_r | VERNEED | 0x4e44 | 0x4e44 | 0x270 | A |
[6] .gnu.hash | GNU_HASH | 0x50b8 | 0x50b8 | 0x678 | A |
[7] .dynstr | STRTAB | 0x5730 | 0x5730 | 0x3a3c | A |
[8] .rela.dyn | RELA | 0x9170 | 0x9170 | 0x1878c30 | A |
[9] .rela.plt | RELA | 0x1881da0 | 0x1881da0 | 0x2c58 | AI |
只读数据(LOAD 1 主体)
[Nr] 名称 | 类型 | 地址 | 大小 | 标志 | 包含 |
|---|---|---|---|---|---|
[10] .lrodata | PROGBITS | 0x1884a00 | 0x6c0e7d0 (~108 MiB) | AMSl | large-model RO constants 和 merged strings |
[11] .rodata | PROGBITS | 0x84a0000 | 0x39eaf28 (~58 MiB) | AMSo | small-model RO constants,64 KiB 对齐 |
[12] protodesc_cold | PROGBITS | 0xbe8af30 | 0x334180 (~3.2 MiB) | A | cold protobuf descriptor tables |
[13] .gcc_except_table | PROGBITS | 0xc1bf0b0 | 0x10d584 (~1.0 MiB) | A | LSDA exception tables |
[14] .eh_frame_hdr | PROGBITS | 0xc2cc634 | 0x6bd684 (~6.7 MiB) | A | unwind search index |
[15] .eh_frame | PROGBITS | 0xc989cb8 | 0x1cab86c (~28.7 MiB) | A | DWARF CFI unwind records |
可执行代码(LOAD 1 尾部)
[Nr] 名称 | 类型 | 地址 | 大小 | 标志 | 角色 |
|---|---|---|---|---|---|
[16] .init | PROGBITS | 0xe635524 | 0x17 | AX | DT_INIT legacy ctor stub |
[17] .fini | PROGBITS | 0xe63553c | 0x9 | AX | DT_FINI legacy dtor stub |
[18] .text.hot | PROGBITS | 0xe635560 | 0x1e2e | AXo | profile-hot functions |
[19] google_malloc | PROGBITS | 0xe6373c0 | 0x46f2 | AXo | TCMalloc fast-path code |
[20] .text.split | PROGBITS | 0xe63bab2 | 0x0 | AXo | empty placeholder section |
[21] .text | PROGBITS | 0xe63c000 | 0x12bdb484 (~299 MiB) | AX | main code body |
[22] .text.startup | PROGBITS | 0x21217490 | 0x16a454 (~1.4 MiB) | AX | constructor/startup code |
[23] .text.unlikely | PROGBITS | 0x21381900 | 0x68469 (~427 KiB) | AX | cold/unlikely paths |
[24] google_init_cold | PROGBITS | 0x213e9d80 | 0x60f1 | AX | cold init code |
[25] malloc_hook | PROGBITS | 0x213efe80 | 0x89e | AX | allocator hook trampolines |
[26] __lcxx_override | PROGBITS | 0x213f0720 | 0x105 | AX | libc++ operator new/delete overrides |
[27] .plt | PROGBITS | 0x213f0830 | 0x1da0 | AX | procedure linkage table(473 slots) |
可写:TLS、init arrays、RELRO data(LOAD 2)
[Nr] 名称 | 类型 | 地址 | 偏移 | 大小 | 标志 |
|---|---|---|---|---|---|
[28] .tdata | PROGBITS | 0x215f25e0 | 0x213f25e0 | 0x110 | WAT |
[29] .tbss | NOBITS | 0x215f26f0 | 0x213f26f0 | 0xd68 | WAT |
[30] .init_array | INIT_ARRAY | 0x215f26f0 | 0x213f26f0 | 0x5aa0 (23200 B) | WAo |
[31] .fini_array | FINI_ARRAY | 0x215f8190 | 0x213f8190 | 0x10 | WA |
[32] .data.rel.ro | PROGBITS | 0x215f81a0 | 0x213f81a0 | 0xa50990 (~10.3 MiB) | WA |
[33] .preinit_array | PREINIT_ARRAY | 0x22048b30 | 0x21e48b30 | 0x10 | WA |
[34] .dynamic | DYNAMIC | 0x22048b40 | 0x21e48b40 | 0x210 | WA |
[35] .got | PROGBITS | 0x22048d50 | 0x21e48d50 | 0xc450 | WA |
[36] .relro_padding | NOBITS | 0x220551a0 | 0x21e551a0 | 0xe60 | WA |
NOTE —
.init_array在 vaddr0x215f26f0处为0x5aa0= 23,200 bytes,与下面的DT_INIT_ARRAY/DT_INIT_ARRAYSZ精确匹配。按每个指针 8 bytes 计算,这是 2,900 个构造函数指针 — 一个巨大的静态初始化扇出。这些构造函数的名单(以及顺序风险)由forensics/static-init.md负责;本页只固定该 array 的位置和范围。
可写:small-model data 和 custom sections(LOAD 3)
[Nr] 名称 | 类型 | 地址 | 偏移 | 大小 | 标志 |
|---|---|---|---|---|---|
[37] .data | PROGBITS | 0x222551c0 | 0x21e551c0 | 0x26a5d8 (~2.5 MiB) | WA |
[38] filewrapper_toc | PROGBITS | 0x224bf798 | 0x220bf798 | 0x1e8 | WA |
[39] __rseq_cs | PROGBITS | 0x224bf980 | 0x220bf980 | 0x2260 | WA |
[40] __rseq_cs_ptr_array | PROGBITS | 0x224c1be0 | 0x220c1be0 | 0x898 | WA |
[41] linkarr_upb_AllExts | PROGBITS | 0x224c2480 | 0x220c2480 | 0x4a0 | WAo |
[42] pb_defaults | PROGBITS | 0x224c2920 | 0x220c2920 | 0x18 | WA |
[43] google_malloc_data | PROGBITS | 0x224c2938 | 0x220c2938 | 0x48 | WA |
[44] .got.plt | PROGBITS | 0x224c2980 | 0x220c2980 | 0xee0 | WA |
[45] .bss | NOBITS | 0x224c3880 | 0x220c3860 | 0xd53b0 (~853 KiB) | WAo |
filewrapper_toc、__rseq_cs/__rseq_cs_ptr_array(TCMalloc 的 restartable-sequences critical-section descriptors)、linkarr_upb_AllExts(upb proto-extension link array)、pb_defaults 和 google_malloc_data sections 是非标准的、由 linker script 放置的 sections。它们的内部布局和用途是 forensics/custom-sections.md 的主题;这里列出它们只是为了在全局映射中固定地址。
可写:large-model data(LOAD 4)
[Nr] 名称 | 类型 | 地址 | 偏移 | 大小 | 标志 |
|---|---|---|---|---|---|
[46] .ldata | PROGBITS | 0x22798c30 | 0x22198c30 | 0x21c00 | WAl |
[47] .lbss | NOBITS | 0x227ba840 | 0x221ba830 | 0x9f940 (~654 KiB) | WAl |
[48] google_malloc_bss | NOBITS | 0x2285a180 | 0x221ba830 | 0x5100 | WAl |
QUIRK — 运行时的 PJRT API 单例位于
.lbss,靠近 vaddr0x227ba840(section 基址),而不在.bss。由于该二进制是 large-code-model,任何编译器无法证明可用 32 位位移到达的全局对象,都会被放入带l标志的 large 层。对分发 PJRT 函数指针表的 dispatcher 来说,这意味着该表通过 64 位绝对加载到达;逆向工程师跟踪GetPjrtApi时必须映射 LOAD 4(delta0x600000)才能找到后备字节。见lifecycle/get-pjrt-api-thunk.md。
未分配尾部(不在任何 segment 中)
[Nr] 名称 | 类型 | 偏移 | 大小 |
|---|---|---|---|
[49] .symtab | SYMTAB | 0x221ba830 | 0x1c3cc50 (~28.2 MiB, 1,233,710 entries) |
[50] .shstrtab | STRTAB | 0x23df7480 | 0x243 |
[51] .strtab | STRTAB | 0x23df76c3 | 0xab824de (~172 MiB) |
这三个没有 SHF_ALLOC flag(address 0x0):它们存在于磁盘上供静态工具使用,从不被映射。它们合计约占文件 200 MiB — 磁盘大小超过四分之一是 loader 从不触碰的符号名称。
动态 Section
.dynamic array(section [34],PT_DYNAMIC)在 vaddr 0x22048b40 处为 33 entries / 0x210 bytes。它是 loader 执行以连接该库的完整指令集。readelf -d:
Tag Type Name/Value
NEEDED libm.so.6
NEEDED libpthread.so.0
NEEDED libdl.so.2
NEEDED librt.so.1
NEEDED libc.so.6
NEEDED ld-linux-x86-64.so.2
RELA 0x9170 RELASZ 25660464 (bytes) RELAENT 24
RELACOUNT 1069006
JMPREL 0x1881da0 PLTRELSZ 11352 (bytes) PLTREL RELA
PLTGOT 0x224c2980
SYMTAB 0x2c8 SYMENT 24
STRTAB 0x5730 STRSZ 14908 (bytes)
GNU_HASH 0x50b8
PREINIT_ARRAY 0x22048b30 PREINIT_ARRAYSZ 16
INIT_ARRAY 0x215f26f0 INIT_ARRAYSZ 23200
FINI_ARRAY 0x215f8190 FINI_ARRAYSZ 16
INIT 0xe635524
FINI 0xe63553c
VERSYM 0x4840
VERDEF 0x4e0c VERDEFNUM 2
VERNEED 0x4e44 VERNEEDNUM 6
NULL 0x0
```text
### loader 获得的指令
| 方面 | 值 | 含义 |
|---|---|---|
| `DT_NEEDED` × 6 | `libm`, `libpthread`, `libdl`, `librt`, `libc`, `ld-linux-x86-64` | 自包含:没有 C++ runtime、没有 CUDA、没有 driver `.so`。不需要 `libstdc++`/`libgcc_s` — C++ runtime 被静态链接进来。 |
| `DT_SONAME` | 不存在 | 该对象只通过路径命名自身;没有东西按 SONAME `dlopen` 它。 |
| `DT_RPATH`/`DT_RUNPATH` | 不存在 | 没有嵌入搜索路径。 |
| 符号哈希 | `DT_GNU_HASH` only (`0x50b8`); no legacy `DT_HASH` | Loader 必须支持 GNU hash;仅支持 DT_HASH 的 loader 无法在这里解析符号。 |
| `DT_INIT` / `DT_FINI` | `0xe635524` / `0xe63553c` | `.init`/`.fini` 中的 legacy single-function ctor/dtor stubs。 |
| `DT_INIT_ARRAY` / 大小 | `0x215f26f0` / 23,200 B (2,900 ptrs) | 真正的构造函数扇出 — 见 [static-init](static-init.md)。 |
| `DT_FINI_ARRAY` / 大小 | `0x215f8190` / 16 B (2 ptrs) | 只有两个析构函数。 |
| `DT_PREINIT_ARRAY` / 大小 | `0x22048b30` / 16 B (2 ptrs) | 在 `.init_array` *之前*运行 — 在库中少见。 |
| `DT_VERSYM`/`VERDEF`/`VERNEED` | `0x4840` / `0x4e0c` / `0x4e44` | 符号版本控制已启用:2 个 version definitions,6 个 version-need records。 |
> **QUIRK —** 对一个 745 MiB 插件来说,`DT_NEEDED` 只列出六个裸系统库。其他所有东西 — LLVM/MLIR 编译器、XLA、gRPC、protobuf、Abseil、Eigen、libstdc++ ABI 和 TPU driver — 都被静态归档进这一个文件。依赖面被有意压到最低,使插件可以落入任何 manylinux_2_31 host,而无需拖入传递 `.so` 图。期望以共享依赖形式找到这些组件的重实现者,会在 `.text` 中找到它们。
>
> **NOTE —** dynamic-string table `DT_STRTAB`/`STRSZ` 只有 14,908 bytes — 它只命名 741 个动态符号和 6 个库。它与 172 MiB 的 `.strtab` 无关;后者命名 1.2 M 个静态符号,加载时从不查询。
---
## 符号和重定位表面
相对于二进制体量,运行时可见的符号面很小;重定位面极大,因为一个充满绝对指针的 534 MiB PIE text segment 必须在加载时重新定址。
### 动态符号
| 指标 | 值 |
|---|---|
| `.dynsym` entries | 741(大小 `0x4578` / 24) |
| 其中 imports (`UND`) | 515 with `Ndx == UND`(index-0 `NULL` + imports)and 226 with a defined section index(exports) |
| 导出符号族 | `Tpu*` C API(`TpuExecutor`、`TpuCompiler`、`TpuProgram`、…),加上 `GetPjrtApi`、`TfTpu_Initialize`、Abseil internal entry points |
| 符号版本 | `GLIBC_2.x`(libc/libm/librt)、`VERS_1.0`(对象自身的 definition) |
导出面刻意很窄:host 加载该库并调用少数 C 入口点 — 主要是 `GetPjrtApi` 和 `TfTpu_*` bootstrap — 整个 compiler/runtime 都隐藏在它们后面作为 internal(`LOCAL`)符号。这些少数导出背后的多态分发由 [`forensics/polymorphic-entry-points.md`](polymorphic-entry-points.md) 和 [`lifecycle/get-pjrt-api-thunk.md`](../lifecycle/get-pjrt-api-thunk.md) 覆盖。
### 重定位
| 表 | 地址 | 条目 | 类型组合 |
|---|---|---|---|
| `.rela.dyn` | `0x9170` | 1,069,186(大小 `0x1878c30` / 24) | `R_X86_64_RELATIVE` 占主导(`DT_RELACOUNT` = 1,069,006 ≈ 99.98%) |
| `.rela.plt` | `0x1881da0` | 473(大小 `0x2c58` / 24,`PLTRELSZ` 11,352) | `R_X86_64_JUMP_SLOT`(PLT GOT entries) |
> **GOTCHA —** 在约 1.07 M 个动态重定位中,除约 180 个外全是 `R_X86_64_RELATIVE` — 纯粹的 load-bias 加法,没有符号引用。这是 534 MiB position-independent text segment 的代价:烘进 `.data.rel.ro`、vtables 和 jump tables 的每个绝对指针都必须重新定址。重定位表(`RELASZ` = 25,660,464 bytes ≈ 24.5 MiB)本身就比许多完整共享库更大。loader 在第一个构造函数运行前处理超过一百万个重定位;对这个插件而言,主导 `dlopen` 延迟的是这个,而不是 `.init_array`。
473 个 PLT slots 是对六个 `DT_NEEDED` 库的 lazy-bound 调用(`malloc`、`pthread_*`、`dlopen`、math 等)。`PLTGOT` = `0x224c2980`(section `[44] .got.plt`)位于 LOAD 3,处于 `GNU_RELRO` 之外,正是为了让 lazy resolver 能在运行时把已解析目标写入其中 — GOT 的其余部分(`[35] .got`)则被 RELRO 冻结。
> **Note:** 51 和 52 的 section-count 数字都一致 — `readelf -h` 报告 `Number of section headers: 52`,其中 51 统计有意义的 sections,52 包含强制的 `NULL` section `[0]`。本页使用 wire 上的计数 52。同样,`DT_SONAME` 确实不存在(`readelf -n`),但 build-id 存在且等于 `89edbbe81c5b328a958fe628a9f2207d`;每个 wiki 页面顶部的 version-pin 都依赖这个 build-id。
---
## 交叉引用
- [取证概览](overview.md) — 该二进制的并行高层导览;本页是其字节级基底
- [双二进制拆分](two-binary-split.md) — 为什么 wheel 同时发布 `libtpu.so` 和较小的 `sdk.so`,以及它们的 ELF 形态如何不同
- [静态初始化](static-init.md) — 负责 `.init_array` 构造函数名单和顺序;本页只固定该 array 的位置/大小
- [自定义 Sections](custom-sections.md) — 深入说明上面 section table 中列出的 `google_*`、`__rseq_cs`、`linkarr_upb_AllExts`、`filewrapper_toc`、`pb_defaults`
- [ELF 入口和 Init 过程](../lifecycle/elf-entry-and-init-proc.md) — 一个 entry 为 `0x0` 的库如何通过 `DT_PREINIT_ARRAY`/`DT_INIT`/`DT_INIT_ARRAY` 到达首次执行
- [GetPjrtApi 跳板](../lifecycle/get-pjrt-api-thunk.md) — 其后备单例位于本文记录的 `.lbss` large-model 区域中的导出入口
- [生命周期概览](../lifecycle/overview.md) — 消费本页 dynamic section 的加载时序列