自定义节
本页中的所有地址均适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d)。其他构建会有所不同。下面的每个节名、地址、大小和标志集合都已通过readelf -SW和readelf -x确认。
摘要
教科书式的 x86-64 共享对象有十几个众所周知的节 — .text、.rodata、.data、.bss、.dynsym、.eh_frame — 逆向工程师可以预期其中每一个。libtpu.so 有 52 个节头,其中约三分之一不在那本教科书里。有些是该二进制如何编译出来的必然产物(LLVM/clang 大代码模型构建会把 rodata、data 和 bss 拆成 .lrodata/.ldata/.lbss,因为小数据的 2 GiB 窗口容不下 745 MiB 的镜像)。另一些则是 Google 内部库发明的链接器数组节 — google_malloc、__rseq_cs、protodesc_cold、linkarr_upb_AllExts、__lcxx_override、filewrapper_toc — 每一个都是一段连续的对象或跳板,启动例程或运行时会通过其 __start_/__stop_ 封装符号遍历。若逆向工程师把这些当作填充或垃圾,就会漏掉 tcmalloc 的每 CPU 快路径、protobuf 描述符池和 PJRT API 单例。
本页是带注释的子集:它不会重新列表全部 52 个节(这是 ELF 剖析 页的职责,那里维护权威的完整表、PT_LOAD 段映射和动态数组)。这里我们只挑出值得注意且真正奇特的节,并对每个节回答一个问题:里面有什么,为什么存在。换成更熟悉的表述:可以把它看作一个单体 Abseil/protobuf/MLIR/XLA 巨块经 LLVM/lld 大代码模型链接后的结果,与一个普通小型 .so 的区别 — 额外的节就是在 32 位 R_X86_64_PC32 位移不再够用的规模上进行单体静态链接所付出的代价。
对重新实现 / 分析而言,契约是:
- 大代码模型拆分 — 为什么
.lrodata(108 MiB)、.ldata和.lbss作为.rodata/.data/.bss的同级节存在,l(large)标志和较高的0x1884a00+ 加载地址如何表明-mcmodel=large,以及 PJRT API vtable 单例位于.lbss。 - 描述符 / proto 数据 —
protodesc_cold(protobuf 描述符池)、linkarr_upb_AllExts(upb 扩展注册器)、pb_defaults。 - 运行时 / 分配器节 —
google_malloc、google_malloc_data、google_malloc_bss、__rseq_cs、__rseq_cs_ptr_array、malloc_hook— tcmalloc 每 CPU 可重启序列机制。 - 展开和 notes —
.eh_frame/.eh_frame_hdr/.gcc_except_table,以及固定版本的.note.gnu.build-id。 - 真正的异类 —
__lcxx_override、filewrapper_toc、.text.split(零字节),以及该二进制携带完整.symtab、并非经典意义上已剥离这一事实。
| 节头数量 | 52(头索引 50 = .shstrtab) |
| 文件大小 | 781,691,048 字节(约 745 MiB) |
| 代码模型 | LLVM/clang -mcmodel=large(带 l 标志的节) |
| 最大显著节 | .lrodata — 0x6c0e7d0(108.1 MiB),标志 AMSl |
| PJRT API 单例 | _ZZN4pjrt10tpu_plugin13GetTpuPjrtApiEvE8pjrt_api @ 0x227ba840(.lbss,1120 B) |
| build-id | 89edbbe81c5b328a958fe628a9f2207d(.note.gnu.build-id,32 B) |
| 已剥离? | 否 — 存在 .symtab(1,233,710 项,28.2 MiB) |
显著节一览(省略教科书式的 .text/.rodata/.data/.dynsym/.plt/.got — 见 ELF 剖析):
| 节 | VAddr | 大小 | 标志 | 内容 |
|---|---|---|---|---|
[10] .lrodata | 0x1884a00 | 0x6c0e7d0(108.1 MiB) | AMSl | 大模型只读数据:const 表、字符串池、vtables |
[12] protodesc_cold | 0xbe8af30 | 0x334180(3.2 MiB) | A | protobuf descriptor_table_protodef_* + TableStruct_*::offsets |
[13] .gcc_except_table | 0xc1bf0b0 | 0x10d584(1.05 MiB) | A | C++ 异常 landing pad 的 LSDA 表 |
[14] .eh_frame_hdr | 0xc2cc634 | 0x6bd684(6.74 MiB) | A | 指向 .eh_frame 的二分搜索索引 |
[15] .eh_frame | 0xc989cb8 | 0x1cab86c(28.7 MiB) | A | DWARF CFI 展开描述符 |
[19] google_malloc | 0xe6373c0 | 0x46f2(17.7 KiB) | AXo | tcmalloc 每 CPU rseq 跳板 + 检查失败辅助函数 |
[20] .text.split | 0xe63bab2 | 0x0(0 B) | AXo | 空的 split-text 标记节 |
[24] google_init_cold | 0x213e9d80 | 0x60f1(24.2 KiB) | AX | 冷路径静态初始化代码 |
[25] malloc_hook | 0x213efe80 | 0x89e(2.2 KiB) | AX | Abseil LowLevelAlloc 分配钩子 |
[26] __lcxx_override | 0x213f0720 | 0x105(261 B) | AX | 被覆盖的 libc++ operator new/delete 跳板 |
[30] .init_array | 0x215f26f0 | 0x5aa0(22.7 KiB) | WAo | 2900 个构造函数指针(普查 → 静态初始化) |
[31] .fini_array | 0x215f8190 | 0x10(16 B) | WA | 2 个析构函数指针 |
[33] .preinit_array | 0x22048b30 | 0x10(16 B) | WA | 2 个预初始化指针(在库中很少见) |
[38] filewrapper_toc | 0x224bf798 | 0x1e8(488 B) | WA | 嵌入文件目录表(磁盘上填零) |
[39] __rseq_cs | 0x224bf980 | 0x2260(8.8 KiB) | WA | tcmalloc 可重启序列临界区描述符 |
[40] __rseq_cs_ptr_array | 0x224c1be0 | 0x898(2.2 KiB) | WA | 指向 __rseq_cs 的 275 个指针 |
[41] linkarr_upb_AllExts | 0x224c2480 | 0x4a0(1.2 KiB) | WAo | 37 × 32 B upb proto 扩展注册器 |
[42] pb_defaults | 0x224c2920 | 0x18(24 B) | WA | protobuf C++ 特性默认实例指针 |
[43] google_malloc_data | 0x224c2938 | 0x48(72 B) | WA | tcmalloc 可写全局变量 |
[46] .ldata | 0x22798c30 | 0x21c00(135 KiB) | WAl | 大模型可写数据 |
[47] .lbss | 0x227ba840 | 0x9f940(638 KiB) | WAl | 大模型零初始化数据 — PJRT API 单例 |
[48] google_malloc_bss | 0x2285a180 | 0x5100(20.2 KiB) | WAl | tcmalloc 大模型零初始化全局变量 |
[1] .note.gnu.build-id | 0x2a8 | 0x20(32 B) | A | GNU build-id note(版本锚点) |
注意 —
Flags列使用readelf的字母:Aalloc、Wwrite、Xexecute、Mmerge、Sstrings、llarge(SHF_X86_64_LARGE)、oOS-specific(SHF_GNU_RETAIN/ link-order)。.lrodata/.ldata/.lbss/google_malloc_bss上的l标志是明确的大代码模型指纹。
大代码模型节
为什么它们存在
x86-64 小/中代码模型假定代码和数据位于一个 ±2 GiB 窗口内,可通过 32 位有符号 RIP 相对位移(R_X86_64_PC32)到达。libtpu.so 是 745 MiB 镜像,仅 .text 就有 0x12bdb484 ≈ 300 MiB,vaddr 为 0xe63c000;一旦 rodata 和 code 合计超出 2 GiB 位移预算,编译器就必须为“大”对象发出 64 位寻址。clang/LLVM 的 -mcmodel=large 答案是把大对象隔离到自己的节中,标记为 SHF_X86_64_LARGE(l),并把它们放到小数据窗口之外。这里正是这种布局:.rodata(小型、可合并只读数据)位于 0x84a0000,而大型只读数据是一个单独的 .lrodata,放在更早的 0x1884a00,大型可写数据 .ldata/.lbss 则最后放置,高于 0x22000000。
特性 — 同时存在
.rodata和.lrodata(以及.data/.ldata、.bss/.lbss)不是重复错误 — 它是 LLVM-mcmodel=large构建的定义性标志。小模型.so没有.l*同级节。只 grep.rodata的逆向工程师会错过二进制中最重要的 108 MiB 常量数据。
.lrodata — 108 MiB 常量存储
[10] .lrodata PROGBITS 0000000001884a00 1884a00 6c0e7d0 00 AMSl 0 0 16
```text
`0x6c0e7d0` = **113,305,552 字节(108.1 MiB)** — 比 `.rodata`(57.9 MiB)大将近 2×,也是文件中迄今最大的非代码、非 symtab 节。标志 `AMSl`:已分配、可合并(`M`)、包含字符串(`S`)、large(`l`),16 字节对齐。这里存放只读程序数据的主体:MLIR/LLVM 静态 op 表、解修饰类型名字符串池、RTTI type-info 记录,以及 vtables 的只读部分。`M`+`S` 标志意味着链接器在此合并了重复字符串常量;16 字节对齐是大型节的默认值。由于它是可合并只读数据,它会映射进第一个可执行+可读 `PT_LOAD`,并在进程之间共享。
### .ldata 和 .lbss — 大型可写数据
```text
[46] .ldata PROGBITS 0000000022798c30 22198c30 021c00 00 WAl 0 0 16
[47] .lbss NOBITS 00000000227ba840 221ba830 09f940 00 WAl 0 0 64.ldata 是 0x21c00(135 KiB)的已初始化大型可写数据;.lbss 是 0x9f940(638 KiB)的零初始化大型可写数据(一个 NOBITS 节 — 不占文件字节,只保留内存)。两者都带 l 标志。
.lbss 中最重要的驻留对象是 PJRT plugin API table 单例。符号表把一个 1120 字节的 LOCAL OBJECT 放在该节的精确起始位置:
1448: 00000000227ba840 1120 OBJECT LOCAL DEFAULT 47 _ZZN4pjrt10tpu_plugin13GetTpuPjrtApiEvE8pjrt_api
```text
解修饰后是 `pjrt::tpu_plugin::GetTpuPjrtApi()::pjrt_api` — 静态 `PJRT_Api` 结构体(函数指针表),导出的 `GetPjrtApi` 入口点(`0xe6a83a0`,5 字节,位于 `.text`)会把它返回给框架。它的 Meyers 单例 guard variable 单独位于 `.bss`:
```text
1447: 00000000224c3f90 8 OBJECT LOCAL DEFAULT 45 _ZGVZN4pjrt10tpu_plugin13GetTpuPjrtApiEvE8pjrt_api注意 — 整个插件中最常被调用进入的对象 — PJRT 分发表 — 正是因为这是大模型构建而位于
.lbss。在小模型.so中,它会是普通.bss对象。带l标志的位置纯粹是寻址模型的后果,不是安全或布局选择。该表的运行时构造由.init_array构造函数完成;见 static-init。
描述符 / Proto 数据节
protodesc_cold — 描述符池
[12] protodesc_cold PROGBITS 000000000be8af30 be8af30 334180 00 A 0 0 16
```text
`0x334180` = **3,359,104 字节(3.2 MiB)**,只读(`A`)。这是 protobuf C++ 运行时的*冷*描述符数据 — 序列化的 `.proto` schema,`descriptor_table_*` 初始化器会在启动时把它们送入全局描述符池。符号表直接确认了内容:
```text
1620: 000000000be8af80 497 OBJECT LOCAL .. _ZL37descriptor_table_protodef_zzRDQFgX_23
1642: 000000000be8af30 76 OBJECT LOCAL .. _ZN23TableStruct_zzRDQFgX_237offsetsE
2506: 000000000be8b210 461 OBJECT LOCAL .. _ZL37descriptor_table_protodef_eguDetDQC_5每个 descriptor_table_protodef_* blob 都是一个序列化的 FileDescriptorProto;每个 TableStruct_*::offsets 数组把 message 字段映射到其内存内偏移。_cold 后缀是 clang 附加到仅在一次性描述符注册期间触及的数据上的节放置提示 — 分组可让热 .rodata 保持缓存密集。proto 文件名被混淆了(zzRDQFgX_23、eguDetDQC_5),这是构建系统产物,不是加密。
陷阱 —
protodesc_cold尽管位置靠近可执行区域,但它不是代码 — 它只有A标志,没有X。“cold” 命名约定(也见于google_init_cold、.text.unlikely)是 clang 对很少执行的字节进行的 profile/启发式分区,这里应用于数据。不要假定*_cold节是可执行的。
linkarr_upb_AllExts — upb 扩展注册器
[41] linkarr_upb_AllExts PROGBITS 00000000224c2480 220c2480 0004a0 00 WAo 0 0 16
```text
`0x4a0` = 1184 字节 = **37 个 32 字节条目**,由封装符号 `__start_linkarr_upb_AllExts`(`0x224c2480`)和 `__stop_linkarr_upb_AllExts`(`0x224c2920`)括住。这是一个*链接器数组*:每个定义 upb(micro-protobuf)proto 扩展的翻译单元都会把一个 32 字节注册器记录放入这个命名节,启动例程会迭代 `[__start, __stop)`,把每个扩展注册到全局注册表。内容有名称:
```text
1161430: 00000000224c2480 32 OBJECT LOCAL .. envoy_annotations_disallowed_by_default_ext
1161431: 00000000224c24a0 32 OBJECT LOCAL .. envoy_annotations_deprecated_at_minor_version_exto 标志(SHF_GNU_RETAIN)会保留这些记录,即使没有符号直接引用它们 — 它们只通过 __start_/__stop_ 扫描到达,因此必须告诉链接器不要把它们垃圾回收。
pb_defaults
[42] pb_defaults PROGBITS 00000000224c2920 220c2920 000018 00 WA 0 0 8
```text
24 字节。一个很小的可写表,protobuf C++ 运行时用它保存 `CppFeatures` 默认实例指针和 edition-defaults 偏移。Hexdump 显示它基本为零,带一个 `0x18` 长度字段 — 它会在静态初始化时被填充。
---
## 运行时 / 分配器节
该二进制静态链接了 Google 的 tcmalloc,而 tcmalloc 的每 CPU 快路径建立在 Linux **restartable sequences**(`rseq`)内核 ABI 上。该机制需要三个协作的节 — 代码跳板、临界区描述符和指针数组 — 外加可写全局变量。
### google_malloc — tcmalloc rseq 跳板
```text
[19] google_malloc PROGBITS 000000000e6373c0 e6373c0 0046f2 00 AXo 0 0 640x46f2 = 18,162 字节的可执行代码(AX),保留(o),64 字节对齐,由 __start_google_malloc/__stop_google_malloc 括住。这是每 CPU 分配器热路径 — 内核可能在抢占时中止并重启的可重启序列函数:
1228043: 000000000e637480 33 FUNC LOCAL .. RseqFunction_PerCpuCmpxchg64
1228044: 000000000e637440 42 FUNC LOCAL .. RseqFunction_PerCpuTryLock
1228047: 000000000e6374c0 43 FUNC LOCAL .. RseqFunction_PerCpuCmpxchgCheck64
```text
这些函数位于自己独立的 retained 节中,因为 `__rseq_cs` 描述符(见下)指向它们内部精确的 start/commit/abort 指令地址;该节让它们保持连续,并防止链接器重排或消除这些精心布置的提交窗口。
### __rseq_cs 和__rseq_cs_ptr_array
```text
[39] __rseq_cs PROGBITS 00000000224bf980 220bf980 002260 00 WA 0 0 32
[40] __rseq_cs_ptr_array PROGBITS 00000000224c1be0 220c1be0 000898 00 WA 0 0 8__rseq_cs 是 0x2260(8800 字节)的 32 字节 struct rseq_cs 描述符 — 每个都是一个 {version, flags, start_ip, post_commit_offset, abort_ip} 记录,告诉内核“如果此线程在其指令指针位于 [start_ip, start_ip+post_commit_offset) 内时被抢占,则跳转到 abort_ip”。__rseq_cs_ptr_array 是 0x898 = 275 个八字节指针,指向 __rseq_cs,即 tcmalloc 向内核注册的间接表。start_ip/abort_ip 字段指向 google_malloc。hexdump 中内联显示了描述符的 flags/version 字段和偏移(0x1a、0x16 等)。
特性 —
__rseq_cs必须是可写的(WA):内核rseq(2)注册会写回这些描述符,加载器也会重定位嵌入的指令指针字段。把它们当作 const 的重新实现者会在注册时触发 fault。
malloc_hook, google_malloc_data, google_malloc_bss
[25] malloc_hook PROGBITS 00000000213efe80 213efe80 00089e 00 AX 0 0 32
[43] google_malloc_data PROGBITS 00000000224c2938 220c2938 000048 00 WA 0 0 8
[48] google_malloc_bss NOBITS 000000002285a180 221ba830 005100 00 WAl 0 0 16
```text
`malloc_hook`(`0x89e`,可执行)保存 Abseil 的低级分配器入口点 — `absl::base_internal::LowLevelAlloc::Alloc`/`Free`/`AllocWithArena` — 即 tcmalloc 初始化前使用的 bootstrap 分配器,由 `__start_malloc_hook`/`__stop_malloc_hook` 括住。`google_malloc_data`(72 字节,可写)和 `google_malloc_bss`(`0x5100` = 20,736 B / 20.2 KiB,零初始化,带 large 标志)保存 tcmalloc 的可变全局变量 — 每 size-class 空闲链表头、采样状态、central cache。`google_malloc_bss` 带 `l` 标志,因此连分配器的 BSS 也被推入大模型区域。
### google_init_cold
```text
[24] google_init_cold PROGBITS 00000000213e9d80 213e9d80 0060f1 00 AX 0 0 320x60f1 = 24,817 字节的可执行冷路径初始化代码 — 静态构造函数中很少采用的分支,由 clang 从 .text.startup 分区出来,使常见初始化路径保持缓存密集。它是 .text.unlikely 的伴随节。
展开与 Note 节
.eh_frame / .eh_frame_hdr / .gcc_except_table
[13] .gcc_except_table PROGBITS 000000000c1bf0b0 c1bf0b0 10d584 00 A 0 0 4
[14] .eh_frame_hdr PROGBITS 000000000c2cc634 c2cc634 6bd684 00 A 0 0 4
[15] .eh_frame PROGBITS 000000000c989cb8 c989cb8 1cab86c 00 A 0 0 8
```text
`.eh_frame` 是 **28.7 MiB** 的 DWARF Call Frame Information — 每个函数一个 FDE,描述在异常传播和栈展开期间如何恢复栈。`.eh_frame_hdr`(6.74 MiB)是排序后的二分搜索索引,C++ personality 例程用它以 `O(log n)` 而不是扫描的方式定位故障 PC 对应的 FDE。`.gcc_except_table`(1.05 MiB)保存 LSDA(Language-Specific Data Area)表 — 从每函数的 call-site PC 范围到 landing-pad 地址和 catch-type 过滤器的映射。
其庞大大小(合计 36.5 MiB)反映了约 100 万个函数,且都启用了异常。注意该节命名为 `.gcc_except_table`,尽管工具链是 clang/LLVM — 这个名称是平台 ABI 约定,不是 GCC 的证据。
> **注意 —** 这三个节在*种类*上是教科书式的,但在*大小*上极不寻常,并且是该二进制 RTTI/异常机制的骨干。剥离异常的重新实现者可减少约 5% 文件体积。封装 `.eh_frame_hdr` 的 `PT_GNU_EH_FRAME` 段的 unwinder 类型是 `X86_64_UNWIND`。
### .note.gnu.build-id
```text
[1] .note.gnu.build-id NOTE 00000000000002a8 0002a8 000020 00 A 0 0 4这个 32 字节 GNU note 将整个 wiki 锚定到一个构建。其布局是标准 Elf_Nhdr:namesz=4("GNU\0")、descsz=0x10、type=3(NT_GNU_BUILD_ID),后跟 16 字节 hash:
0x000002a8 04000000 10000000 03000000 474e5500 ............GNU.
0x000002b8 89edbbe8 1c5b328a 958fe628 a9f2207d .....[2....(.. }
```text
→ build-id `89edbbe81c5b328a958fe628a9f2207d`。这是文件中第一个已分配节(vaddr `0x2a8`,紧跟 program headers 之后),也是每个页面版本固定 blockquote 中引用的规范版本键。
---
## 真正的异类
### __lcxx_override — 被覆盖的全局 new/delete
```text
[26] __lcxx_override PROGBITS 00000000213f0720 213f0720 000105 00 AX 0 0 32261 字节的可执行代码,保存替换后的全局分配运算符,libc++(__lcxx 前缀)会把它们路由到 tcmalloc,而不是默认 malloc:
1231661: 00000000213f0720 69 FUNC LOCAL .. _Znwm // operator new(size_t)
1231663: 00000000213f07a0 104 FUNC LOCAL .. _ZnwmSt11align_val_t // operator new(size_t, align_val_t)
1231665: 00000000213f0780 5 FUNC LOCAL .. _Znam // operator new[](size_t)
1231669: 00000000213f0820 5 FUNC LOCAL .. _ZnamSt11align_val_t // operator new[](size_t, align_val_t)
```text
把 operator-new/delete 覆盖隔离到命名节中,可以让链接保证它们优先于 libc++ 默认实现(插桩/符号介入顺序),并让运行时验证覆盖确实存在。看到 `__lcxx_override` 的逆向工程师应立即把它理解为“这个二进制替换了全局分配器” — 整个 745 MiB 镜像中的每个 C++ `new` 都会经由这四个跳板流入 tcmalloc。
### filewrapper_toc
```text
[38] filewrapper_toc PROGBITS 00000000224bf798 220bf798 0001e8 00 WA 0 0 8488 字节,可写,磁盘上填零(hexdump 全为 00)。这是 Google file_wrapper/嵌入文件机制的目录表 — 一个编译进二进制的资源注册表,由重定位在静态初始化时填充,而不是在磁盘上携带已初始化字节。在没有非零磁盘内容的情况下,无法仅凭字节恢复精确 schema;其用途是根据符号名以及 WA + 零填充模式推断的(对精确记录格式为 MEDIUM 置信度)。
.text.split — 空节
[20] .text.split PROGBITS 000000000e63bab2 e63bab2 000000 00 AXo 0 0 1
```text
一个 **零字节** 可执行节,位于 `google_malloc` 和 `.text` 之间。它是 clang 的 machine-function-splitting pass 发出的标记/锚点,用于界定 split-text 区域;在此构建中,拆分出的冷代码落在了别处(`.text.unlikely`、`google_init_cold`),留下这个空但存在的节。它无害,但逆向工程师扫描节表时不应被 0 字节 `PROGBITS X` 条目吓到 — 它不包含代码。
### 未剥离 — 完整 .symtab
```text
[49] .symtab SYMTAB 0000000000000000 221ba830 1c3cc50 18 51 1232970 8
[51] .strtab STRTAB 0000000000000000 23df76c3 ab824de 00 0 0 1
# note: the [49] Inf field 1232970 is readelf's "first global" index, not the
# entry count; the table holds 1,233,710 entries (size 0x1c3cc50 ÷ 24).尽管是生产插件,libtpu.so 随附其完整 .symtab — 1,233,710 项(28.2 MiB;大小 0x1c3cc50 ÷ 24),外加 171.5 MiB 的 .strtab,其中是它们的 mangled names — 此外还有加载时 .dynsym(741 个符号)。因此它不是经典意义上已剥离:每个局部函数和对象都有可读的修饰名,这就是为什么本 wiki 能按名称而不只是地址解析 descriptor_table_protodef_*、RseqFunction_PerCpuCmpxchg64 和 PJRT 单例。
特性 —
.symtab+.strtab这一对是非分配的(vaddr0x0,无A标志)— 它不消耗运行时内存,但增加约 200 MiB 文件大小。Google 发布它是为了 crash-symbolication。对逆向工程师来说,这是巨大的礼物:该二进制实际上是自文档化的。运行strip --strip-debug后,200 MiB 和所有局部名称都会消失,只剩 741 个.dynsym导出。注意: 一个包含零大小
.tm_clone_table的 “sections: 88” 汇总,是把两个 ELF 对象(libtpu.so+sdk.so)一起计数。仅libtpu.so按readelf -h/readelf -S计算正好有 52 个节头,并且libtpu.so没有.tm_clone_table(该行属于同级sdk.so)。本页表格只描述libtpu.so。注意:
.lbss是0x9f940= 653,632 字节(638 KiB),.ldata是0x21c00= 138,240 字节(135 KiB)。PJRT 单例位于该节的精确基址0x227ba840,由.symtab项确认,而不只是“靠近”它。
交叉引用
- ELF 剖析 — 维护权威的完整 52 节表、
PT_LOAD段映射和动态数组;本页是显著节的带注释子集。 - 静态初始化 — 维护
.init_array普查(2900 个构造函数槽);本页只固定该数组的位置,并说明它构建的 PJRT 单例。 - 尾随 zstd Blob — 负责回答是否存在任何嵌入/尾随切分载荷的问题;本页只覆盖已命名 ELF 节。
- 二进制取证概览 — 整个取证部分的地图,以及每个节的深入分析所在位置。
- 双二进制拆分 — 为什么
libtpu.so和sdk.so是独立对象,以及.tm_clone_table/ 合并节数数字来自何处。 - 嵌入库图谱 — 本页列举的命名节所属的第三方库(tcmalloc、protobuf/upb、libc++、Abseil)。