do_init / do_fini
本页所有地址都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(buildlibtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,ELF x86-64 DYN,未 strip;demangled C++ symbols 按原文引用)。其他版本会有所不同。
摘要
本页负责记录 libtpu 在加载时运行的 C++ static-constructor iteration,以及在卸载时运行的对称 destructor teardown —— 这是生命周期中完全位于 PJRT C-ABI 之下、由 C runtime 驱动而不是由任何 framework call 驱动的部分。当 dynamic linker 遍历 .init_array(INIT_ARRAY @ 0x215f26f0,2900 slots)时,它按 link order 调用每个 translation-unit static-init function;这场风暴填充 libtpu 的 flag registries、protobuf descriptor pools、LLVM/MLIR backend tables,以及 —— 对正确性真正重要的一块 —— GoogleInitializer module descriptors 和它们的 dependency edges。卸载时,同一机制通过 __cxa_finalize 反向运行,drain constructors 用 __cxa_atexit 注册的 destructors LIFO list。
参考框架是任何 clang/libstdc++ 二进制都会实现的 Itanium C++ ABI:per-TU _GLOBAL__sub_I_<file>.cc functions 填充 .init_array;function-local statics 通过 __cxa_guard_acquire/release/abort 实现 thread-safe;带非平凡 destructor 的 globals 会用 __cxa_atexit(dtor, obj, &__dso_handle) 注册 teardown callback;__cxa_finalize(__dso_handle) 在 dlclose/exit 时 drain 该 list。libtpu 有三点不寻常。第一,它携带自己的 libc++abi —— __cxa_guard_* 位于 0x213e9ac0 / 0x213e9be0 / 0x213e9c20,__cxa_finalize 也是 libtpu-internal,而不是 glibc 的,因此 guard word layout 和 finalize list 都是此 image 私有的。第二,根本没有 glibc __do_global_ctors/__do_global_dtors driver —— 这是一个 lld-linked clang-CRT image;取而代之的是两个自定义 byte-guarded stubs,__do_init @ 0xe63c000(.init_array slot 761,不是 slot 0)和 __do_fini @ 0xe63c020(.fini_array slot 0),它们本身只是其余条目中的普通 array slots。第三 —— 也是整个设计能成立的原因 —— constructors 只注册;它们不运行任何 order-critical 内容。 每个 cross-TU ordering hazard 都被推迟到 GoogleInitializer DAG,它稍后以 topological order 运行(PHASE B,在第一次 PJRT_Plugin_Initialize 时),所以 constructors 执行所依据的 link order 从不决定正确性。
本页按 runtime 生命周期的两个半段布局:§1 load-time constructor walk(.init_array 调用什么、_GLOBAL__sub_I_* 集构造什么、__cxa_guard singleton discipline、ordering guarantees 以及它们的弱点所在),§2 unload-time teardown(__do_fini、__cxa_finalize(__dso_handle) LIFO drain,以及因为 leaked-on-exit 而不会 teardown 的内容)。ELF DT_INIT/DT_FINI/PREINIT_ARRAY tags 和 linker 的 init_proc trampoline 位于 elf-entry-and-init-proc.md;GoogleInitializer module DAG 和 PJRT_Plugin_Initialize bootstrap 位于 module-init-plugin-discovery.md 和 tftpu-initialize-bootstrap.md。
对于重新实现,static-init/fini 契约是:
- Constructor walk 模型 ——
.init_array是 function pointers 数组,linker 按数组顺序调用;slot 0 是__cpu_indicator_init(CPU/IFUNC detection),__do_init是 slot 761 的 guarded stub,主体是 per-TU_GLOBAL__sub_I_*functions。每个 function 构造 file-scope globals,并对任何带非平凡 destructor 的对象,用__cxa_atexit(dtor, obj, &__dso_handle)注册 teardown。 - 只注册纪律 —— 约 2900 个 constructors 中没有一个运行 order-critical TPU bring-up。它们填表并构建
GoogleInitializerregistry;order-sensitive work 被推迟到 DAG。重新实现者如果在 static ctor 里运行 HAL/platform setup,就会重新引入该设计本来要避免的 static-init-order fiasco。 - 两个 byte guards ——
__do_init的__do_init.__initialized和__do_fini的__do_fini.__finalized让 array-bracketing slots 幂等;per-singleton__cxa_guardbytes 让每个 function-local static one-shot 且 thread-safe。 - 对称 teardown ——
__do_fini调用__cxa_finalize(__dso_handle),后者以 LIFO drain__cxa_atexitlist,每个已注册 destructor 调用一次。PJRT surface 刻意不在此 list 上 —— 它是 leaked Meyers singleton。
| Constructor array | INIT_ARRAY @ 0x215f26f0 —— 23200 B = 2900 × 8,全部为 R_X86_64_RELATIVE |
| Constructor entry stub | __do_init @ 0xe63c000 —— guard byte __do_init.__initialized @ 0x224c3880 |
| Destructor entry stub | __do_fini @ 0xe63c020 —— guard byte __do_fini.__finalized @ 0x224c3881 |
| Per-TU ctor symbols | _GLOBAL__sub_I_<file>.cc/.cpp —— 1885 distinct symbols(1764 distinct base names;71 names 在静态链接组件间重复) |
| Grouped / single-var ctors | _GLOBAL__I_NNNNNN(759),__cxx_global_var_init[.N](89 distinct names / 221 instances) |
| Function-local-static guards | libtpu 自己的 __cxa_guard_acquire/release/abort @ 0x213e9ac0 / 0x213e9be0 / 0x213e9c20 |
| Teardown drain | __cxa_finalize(_dso_handle)(libtpu 自己的 libc++abi) |
| Teardown array | FINI_ARRAY @ 0x215f8190 —— 16 B = 2 × 8:__do_fini,rand_thread_state_clear_all @ 0x2063df60 |
| 可信度 | CONFIRMED(字节锚定 vs 反编译),除非行或 callout 另有说明 |
1. Load-Time —— static-constructor walk
目的
.init_array 是 Itanium-ABI 机制,用于让每个 translation unit 的 file-scope dynamic initialization 在 dlopen 时运行。dynamic linker 读取 DT_INIT_ARRAY/DT_INIT_ARRAYSZ(0x215f26f0 处的 section,2900 entries),然后在 DT_INIT 和 PREINIT_ARRAY 之后按数组顺序调用每个指针。对于 libtpu,这是整个 register-only landscape 被构建的位置:2900 次 constructor calls 在 abseil、protobuf、LLVM、MLIR、TPU runtime 和 GoogleInitializer registry 中构造 globals。它是加载时最大的单一事件,并且刻意保持浅层 —— 只做 registration。
入口点
dynamic linker (DT_INIT_ARRAY walk) ── one call per slot, array order
└─ INIT_ARRAY @ 0x215f26f0 (2900 slots, all R_X86_64_RELATIVE)
[0] __cpu_indicator_init 0x21211240 ── clang/GCC ifunc + CPU-feature detector (first; addend of slot-0 reloc @ 0x215f26f0)
[1] ARGV_INIT_ARRAY::init_wrapper 0x20a0d2b0 ── Rust runtime argv capture
... (slots 2..760 — early CRT/IFUNC + grouped C++ ctors) ...
[761] __do_init 0xe63c000 ── guarded array-bracket stub (no work; reloc @ 0x215f3eb8)
... (the long tail) ...
[n] _GLOBAL__sub_I_<file>.cc × 1885 ── per-TU static init (1764 distinct base names)
_GLOBAL__I_NNNNNN × 759 ── grouped / priority-tagged ctors
__cxx_global_var_init[.N] × 221 ── single-global inits (89 distinct names)
```text
> **注意 ——** relocation 步骤属于 [elf-entry-and-init-proc.md](elf-entry-and-init-proc.md):磁盘上的全部 2900 个 in-file slots 都是零,每个 slot 都是一个 `R_X86_64_RELATIVE`,linker 在 walk 开始前用 target VA 填充。本页假定数组已经 relocated,只关心调用 targets 时它们*做什么*。
### 算法
linker 的 loop 很简单;有意思的逻辑在每个 per-TU function 里面。一个代表性的 `_GLOBAL__sub_I_*` 会构造它的 file-scope globals,并对任何带非平凡 destructor 的 global 注册 teardown callback —— 这是 §2 drain 的对称半段。`_GLOBAL__sub_I_tpu_platform_registration.cc @ 0x2121f040` 函数体是规范的 “register-only” 形状:它构造一个 `GoogleInitializer` object 并返回。
```c
// the linker's array walk (conceptual; not a libtpu function)
function run_init_array():
for i in 0 .. DT_INIT_ARRAYSZ/8 - 1: // 2900 iterations
(*INIT_ARRAY[i])() // call in ARRAY order
// __do_init @ 0xe63c000 — the guarded array-bracket stub
function __do_init():
if !__do_init.__initialized: // function-static byte guard
__do_init.__initialized = 1 // set-and-return; NO ctor body
// intentionally empty: real ctors are the OTHER array slots
// representative per-TU ctor — register-only, no order-critical work
// _GLOBAL__sub_I_tpu_platform_registration.cc @ 0x2121f040
function _GLOBAL__sub_I_tpu_platform_registration():
// construct ONE file-scope global: a GoogleInitializer descriptor
GoogleInitializer( // ctor @ 0x210b2780
&google_initializer_module_tpu_platform, // the .data object
"module", "tpu_platform", // tag + module NAME
&google_init_module_tpu_platform) // fn to RUN LATER (PHASE B)
// (other registration TUs additionally register FLAGS_tf_jf_* absl flags)
// the module body does NOT run here — only the descriptor is built
// the destructor-registration pattern every non-trivial global ctor uses
// (observed pervasively across the decompiled _GLOBAL__sub_I_* bodies)
function some_ctor_with_destructible_global():
construct_in_place(&GetThing()::thing) // placement-new the global
__cxa_atexit(&Thing::~Thing, // teardown callback ...
&GetThing()::thing, // ... bound to the object ...
&_dso_handle) // ... tagged to THIS image
// __cxa_atexit pushes onto libtpu's own __cxa_finalize LIFO list (§2)QUIRK ——
__do_init除了翻转一个 byte 之外什么都不做。它不是调用其他 constructors 的 dispatcher —— glibc-style__do_global_ctors_aux才会是。这里__do_init只是.init_array中又一个 entry(slot 761),与 1885 个真正的_GLOBAL__sub_I_*slots 并列。它唯一的职责是幂等:如果数组曾被 walk 两次(已映射 image 的重新dlopen),guard 会让此 slot 的第二次 walk 变成 no-op。真正的 per-TU constructors 带有自己的__cxa_guardbytes,理由相同。重新实现者若把__do_init当作 constructor driver,就会在里面寻找 ctor calls 并且找不到 —— 这是正确的,不是 decompiler failure。
_GLOBAL__sub_I_* 集构造什么
1885 个 _GLOBAL__sub_I_* constructors 不是一个应该平铺枚举的列表 —— 那是反模式。更好的理解方式是按它们填充的 registry 种类分桶;所有桶都有一个共同属性:它们注册到 table 或构建 descriptor,不运行任何 hardware 或 order-critical setup。下表按每个 TU 的 globals 所做的工作对 constructor set 分桶,并列出匹配每个桶的 TU 数量(对 _GLOBAL__sub_I_*.cc/.cpp symbol set 的 keyword scan;桶会重叠,所以不会加总到 1885)。
| Constructor bucket | What its globals register | Distinct TUs |
|---|---|---|
| TPU/XLA/TSL runtime | module descriptors、factory tables、runtime flags(最大区域) | ~162(*tpu*) |
| LLVM target backends | *TargetMachine.cpp、*AsmPrinter.cpp、*ISelLowering.cpp、*Subtarget.cpp、*CodeGen* —— RegisterTarget/RegisterPass 到 LLVM global registries(X86、AArch64、AMDGPU、ARM、TPU) | ~51 |
GoogleInitializer module descriptors | _GLOBAL__sub_I_*_registration.cc 集 —— 绑定 module NAME → google_init_module_* fn + dependency edges | ~41(*registration*/*register*) |
| abseil flag registries | _GLOBAL__sub_I_absl_flags.cc、commandlineflags.cc、*_flags.cc —— FLAGS_* 到 absl flag registry | ~28(*[Ff]lags*) |
| MLIR / HLO dialects + passes | mhlo、stablehlo、mlir_bridge_pass、dialect/pass registrations | ~14 |
| Metrics / telemetry | gauge/monitor/metric registries | ~19 |
| protobuf / upb descriptors | proto descriptor pools + linkarr_upb_AllExts mini-table extension array(0x224c2480..0x224c2920) | ~11 named + linker array |
注意 ——
GoogleInitializer-descriptor 桶(约 41 个*registration*TUs)是唯一一个其 registrations 在 run time order-critical 的桶,而它恰恰是执行被 deferred 的桶。_GLOBAL__sub_I_*_registration.ccctors 在 load 时运行(构建 descriptors),但它们指向的google_init_module_*functions 稍后才在 DAG 中、第一次PJRT_Plugin_Initialize时运行。descriptor → run mapping 见 module-init-plugin-discovery.md。注意: symbol table(
nm -C libtpu.so)包含 1885 个 distinct_GLOBAL__sub_I_*symbols,每个都有自己的地址,以及 759 个_GLOBAL__I_*symbols。1885 是 distinct symbols;1764 是 distinct base names —— 71 个 names 重复,因为相同 source filename 被从多个组件静态链接进来(metrics.cc出现 8×;trace_codec_factory.cc/performance_counters.cc/kernel_firmware_factory.cc/hardware_attributes_factory.cc各 6×),每次重复都是一个真正不同的 TU initializer,位于不同地址。请把这些 counts 锚定在 dedupednmsymbol table,而不是对 decompile tree 做 grep(后者会放大 duplicate names)。2900 total slot count 来自DT_INIT_ARRAYSZ的字节锚定(0x5aa0 / 8)。完整 census 位于 ../forensics/static-init.md。
__cxa_guard singleton discipline
带非平凡 initialization 的 function-local statics(Meyers singletons)通过 per-static guard word 和 __cxa_guard_acquire/release/abort 三元组实现 thread-safe 和 one-shot。libtpu 在 0x213e9ac0 / 0x213e9be0 / 0x213e9c20 链接了它自己的 libc++abi 实现,而不是 glibc 的 —— 已由反编译确认。该实现是标准 libc++abi futex-backed guard:它通过 CAS 安装 “in-progress” 状态,让竞争线程阻塞在 futex syscall,并检测 recursive initialization。
// __cxa_guard_acquire @ 0x213e9ac0 (libtpu's own libc++abi) — abbreviated
function __cxa_guard_acquire(guard):
if google_cxa_guard_acquire_begin: google_cxa_guard_acquire_begin(guard) // hook
if (guard->byte[0]) return 0 // already initialized → skip
prev = CAS8(&guard->byte[1], /*expect*/0, /*set*/2) // try to claim "in-progress"
if prev != 0: // someone else is initializing
loop:
if prev == 1: return 0 // became initialized → skip
tid = syscall(186 /*gettid*/)
if guard->owner_tid == tid: // SAME thread re-entered
__abort_message("__cxa_guard_acquire detected recursive "
"initialization: ...") // recursion → abort
mark waiter bit; syscall(202 /*futex*/ wait) // block until released
prev = CAS8(&guard->byte[1], 0, 2) // re-try claim on wake
guard->owner_tid = syscall(186 /*gettid*/) // record owner
return 1 // caller runs the initializer
```text
> **GOTCHA ——** 这些*不是* glibc 的 `__cxa_guard_*`。libtpu 携带自己的 libc++abi(同一个 image 也携带自己的 `__cxa_finalize` 和 `__cxa_atexit`),所以 guard word 是 libtpu 的 two-byte `{initialized, in-progress}` layout,recursion check 直接通过 `syscall(186)` 使用 `gettid`。重新实现者如果链接 host libc 的 guard,会得到*不同*的 guard-word ABI;在同一个 static 上混用两者是 undefined。生命周期 Stage 2 中的 17 个 `GetTpuPjrtApi` guards,以及 TPU runtime 中每个 Meyers singleton,都使用*这个*实现 —— 17-guard chain 见 [get-pjrt-api-thunk.md](get-pjrt-api-thunk.md#the-lazy-builder--gettpupjrtapi)。
### Ordering guarantees
constructor walk 只提供一个 ordering guarantee,且没有更多:**`.init_array` entries 按数组顺序运行,也就是其 translation units 的 link order。** 没有 cross-TU dependency ordering —— 如果 TU A 的 global 依赖 TU B 的 global 已构造,唯一能让它工作的是 linker 恰好把 B 放在 A 前面。这就是经典 static-initialization-order fiasco,而 libtpu 的设计选择是**不依赖它完成任何 order-critical 内容**:
- **在一个 TU 内**,declaration order 会被遵守(标准 C++)。
- **跨 TUs**,只有 link order 得到保证。`__cpu_indicator_init`(clang/GCC ifunc + CPU-feature detector,slot 0)和 Rust `ARGV_INIT_ARRAY::init_wrapper`(slot 1)由 linker 放在最前,因为 CPU-feature detection 之前不能运行任何 C++;`__do_init` guard stub 位于 slot 761,per-TU `_GLOBAL__sub_I_*` constructors 填满长尾。
- **对于 order-critical TPU stack**(HAL factories、XLA targets、StreamExecutor platform),constructors 注册带显式 dependency edges 的 `GoogleInitializer` *descriptor*,并把执行推迟到 DAG。DAG 在 PHASE B 按 topological order 运行,不受 static-ctor order 影响。这就是为什么 `tpu_hal_jxc_hardware_impl` module 可以依赖 `tpu_hal`,而无需对它们 `_GLOBAL__sub_I_*_registration.cc` files 的 link order 施加任何约束。
> **QUIRK ——** 对重新实现至关重要的反转是:你会*预期* order-critical 的东西(platform/HAL/target bring-up)恰恰被显式*移出* static-init ordering,而真正会在 load 时运行的东西(flag tables、descriptor pools、LLVM/MLIR registries)按构造就是 order-*insensitive* 的 —— 每个都按 name/ID 注册到独立 table,所以注册顺序不会改变结果。该设计正是为了掏空 static-init phase,让它唯一脆弱的保证(link order)不必被依赖。
---
## 2. Unload-Time —— `__do_fini` 和 `__cxa_finalize` drain
### 目的
在 `dlclose` 或进程退出时,C runtime 必须运行 constructor walk 期间注册的 destructors。Itanium ABI 机制与 `__cxa_atexit` 对称:`__cxa_finalize(dso_handle)` 以 **LIFO** 顺序 drain registered-destructor list,调用每个针对该 DSO 注册的 callback 一次,然后清除它们。libtpu 通过 `FINI_ARRAY` 驱动这一过程,其第一个 slot 是 guarded `__do_fini` stub。teardown 刻意很薄 —— constructors 注册的 destructors 远少于它们运行的 constructors,因为最大的对象(PJRT surface、extension chain)被有意泄漏。
### 入口点
```text
dynamic linker (DT_FINI_ARRAY walk, reverse of init)
└─ DT_FINI (.fini @ 0xe63553c) ── empty stub (sub/add/ret)
└─ FINI_ARRAY @ 0x215f8190 (2 slots, R_X86_64_RELATIVE)
[0] __do_fini 0xe63c020 ── guarded __cxa_finalize(_dso_handle)
[1] rand_thread_state_clear_all 0x2063df60 ── per-thread BoringSSL/RNG cleanup注意 —— array bracket 与 init 有一点不对称:
FINI_ARRAY只有 2 个 slots,而INIT_ARRAY有 2900 个,因为 per-TU teardown 不是_GLOBAL__sub_D_*array。相反,每个 destructor 都在 constructor walk 期间动态注册到__cxa_atexit,单个__do_finislot 通过__cxa_finalizedrain 它们全部。linker 以反向 slot order 遍历FINI_ARRAY,所以rand_thread_state_clear_all(slot 1)在__do_fini(slot 0)之前运行。
算法
__do_fini 是 __do_init 的镜像:一个 guard byte 加上这一次的真实调用。guard 让 drain one-shot;函数体在 weak-symbol presence check 保护下调用 __cxa_finalize(_dso_handle)。
// __do_fini @ 0xe63c020 — the guarded teardown stub (byte-exact from decompile)
function __do_fini():
int result // uninitialized return (see GOTCHA)
if !__do_fini.__finalized: // function-static byte guard
__do_fini.__finalized = 1 // set BEFORE the call → reentrancy-safe
if &_cxa_finalize: // weak-symbol presence check
return __cxa_finalize(_dso_handle) // drain THIS image's atexit LIFO
return result
// __cxa_finalize(dso) — libtpu's own libc++abi (conceptual, standard Itanium drain)
function __cxa_finalize(dso):
// walk the __cxa_atexit list NEWEST-FIRST (LIFO)
for entry in reverse(cxa_atexit_list):
if dso == NULL or entry.dso_handle == dso: // only THIS image's dtors
d = entry.dtor; entry.dtor = NULL // mark consumed (one-shot)
d(entry.obj) // run the destructor
// entries are cleared so a second finalize is a no-op
```text
> **GOTCHA ——** `__do_fini` 中的 `int result` 在 already-finalized 路径(`__do_fini.__finalized` 已为 1)和 no-`__cxa_finalize` 路径上会读取未初始化值。这是一个具有 `void` 语义的 tail-call function 的 decompiler artifact,在这些路径上 return register 只是没有被写入 —— caller(linker 的 fini walk)忽略返回值,因此垃圾 `eax` 无害。重新实现者应把 `__do_fini` 建模为返回 `void`;不要传播这个伪 `int`。
>
> **QUIRK ——** `__do_fini` 先 set 再 check:它在调用 `__cxa_finalize` *之前*写入 `__do_fini.__finalized = 1`,所以如果一个 registered destructor(在 `__cxa_finalize` 内运行)以某种方式重新进入 `__do_fini`,guard 已经设置,re-entry 会是 no-op。`if (&_cxa_finalize)` weak-symbol check 是标准 `crtstuff` guard,用于 image 未链接 finalizer 的情况;在 libtpu 中该符号始终存在(libtpu 自带),所以该分支实际上总会走。两个细节都镜像 glibc 的 `__do_global_dtors_aux`,但这里的 `__cxa_finalize` 是 libtpu 内部版本,drain libtpu 的私有 atexit list。
### 不会 teardown 的内容
teardown 按设计很薄。生命周期中构建的最大且最昂贵的对象是 **leaked-on-exit function-local statics**,这是 plugin `.so` 的正常 Meyers-singleton lifetime —— 它们从未注册到 `__cxa_atexit`,所以 `__cxa_finalize` 永远不会触碰它们:
| 对象 | 存储 | exit 时会 teardown? | 实际释放方式 |
|---|---|---|---|
| `GetTpuPjrtApi()::pjrt_api`(140-slot table) | `.lbss @ 0x227BA840` | **否** —— leaked | 永不;进程死亡回收 |
| 16 个 `.bss` extension nodes | `.bss @ 0x224c3880+` | **否** —— leaked | 永不;进程死亡回收 |
| `xla::PjRtClient` / `TpuPlatform` / executors | heap | exit 时**否** | 显式 `PJRT_*_Destroy` C-API calls |
| Per-thread BoringSSL/RNG state | TLS | **是** | `rand_thread_state_clear_all @ 0x2063df60`(FINI slot 1) |
| 带非平凡 dtors 的 globals(caches、flag stores、`APFloat` constants、`StringMap`s) | `.data`/`.bss` | **是** | `__cxa_finalize` LIFO drain(FINI slot 0 → `__do_fini`) |
libtpu 还提供自己的 `atexit` / `__cxa_thread_atexit` shims(`0x21217360 / 0x2120f1e0`),以及用于 exit 时 flush logs 的 `threadlogger::FlushLogsAtExit @ 0x20f3dfe0`。这些都会进入 `__cxa_finalize` drain 的同一个 LIFO list。
> **GOTCHA ——** 重新实现者不能假设 `dlclose` 会释放 PJRT surface。位于 `0x227BA840` 的 `PJRT_Api` table 和 extension chain 是 leaked Meyers singletons:它们的 destructors 从未注册,所以 `__cxa_finalize` 不会调用它们;在同一进程中 `dlopen`、使用、`dlclose` 并重新 `dlopen` libtpu 的 host,会发现第二次 load 时 table *已经构建好*(`.bss` 中的 `__cxa_guard` bytes 会存活,因为 image 在 PJRT 的 reference-counted plugin lifetime 下保持 resident)。Clients、executables 和 buffers 必须在 unload *之前*通过它们显式的 `PJRT_*_Destroy` calls 释放;fini 不会替你做。
---
## 相关组件
| 组件 | 关系 |
|---|---|
| `INIT_ARRAY @ 0x215f26f0` | linker 在 `dlopen` 时遍历的 2900-slot constructor array |
| `__do_init @ 0xe63c000` | `.init_array` slot 761 的 guarded array-bracket stub,设置 `__do_init.__initialized` |
| `_GLOBAL__sub_I_*`(1885 symbols) | 执行实际 registration 的 per-TU static-init functions |
| `GoogleInitializer` ctor `@ 0x210b2780` | 由 `*_registration.cc` ctors 构造;绑定 module name → run-later fn |
| `__cxa_guard_acquire/release/abort @ 0x213e9ac0 / 0x213e9be0 / 0x213e9c20` | libtpu 自己的 libc++abi function-local-static guards |
| `__cxa_atexit` / `_dso_handle` | 每个非平凡 global ctor 发出的 destructor-registration call |
| `FINI_ARRAY @ 0x215f8190` | 2-slot teardown array(`__do_fini`,`rand_thread_state_clear_all`) |
| `__do_fini @ 0xe63c020` | Guarded `__cxa_finalize(_dso_handle)` —— drain atexit LIFO |
| `rand_thread_state_clear_all @ 0x2063df60` | FINI slot 1 —— per-thread BoringSSL/RNG cleanup |
## 交叉引用
- [overview.md](overview.md) —— 完整 load-to-unload timeline;本页负责 Stage 0 的 constructor walk 和 Stage 5 的 teardown
- [elf-entry-and-init-proc.md](elf-entry-and-init-proc.md) —— ELF `DT_INIT`/`DT_FINI`/`PREINIT_ARRAY` tags、array relocation,以及驱动 `.init_array` 的 `init_proc` CRT trampoline
- [module-init-plugin-discovery.md](module-init-plugin-discovery.md) —— `*_registration.cc` ctors 注册的内容(`GoogleInitializer` descriptors),以及 DAG 如何在 PHASE B 运行它们
- [tftpu-initialize-bootstrap.md](tftpu-initialize-bootstrap.md) —— Stage 3,在这里 constructors 只注册的 deferred `google_init_module_*` functions 最终执行
- [get-pjrt-api-thunk.md](get-pjrt-api-thunk.md) —— 使用本文记录的同一个 libc++abi guard implementation 的 17 个 `__cxa_guard` Meyers-singleton builders