Skip to content

do_init / do_fini

本页所有地址都适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d,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_arrayINIT_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.mdGoogleInitializer module DAG 和 PJRT_Plugin_Initialize bootstrap 位于 module-init-plugin-discovery.mdtftpu-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。它们填表并构建 GoogleInitializer registry;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_guard bytes 让每个 function-local static one-shot 且 thread-safe。
  • 对称 teardown —— __do_fini 调用 __cxa_finalize(__dso_handle),后者以 LIFO drain __cxa_atexit list,每个已注册 destructor 调用一次。PJRT surface 刻意在此 list 上 —— 它是 leaked Meyers singleton。
Constructor arrayINIT_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 guardslibtpu 自己的 __cxa_guard_acquire/release/abort @ 0x213e9ac0 / 0x213e9be0 / 0x213e9c20
Teardown drain__cxa_finalize(_dso_handle)(libtpu 自己的 libc++abi)
Teardown arrayFINI_ARRAY @ 0x215f8190 —— 16 B = 2 × 8:__do_finirand_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_ARRAYSZ0x215f26f0 处的 section,2900 entries),然后在 DT_INITPREINIT_ARRAY 之后按数组顺序调用每个指针。对于 libtpu,这是整个 register-only landscape 被构建的位置:2900 次 constructor calls 在 abseil、protobuf、LLVM、MLIR、TPU runtime 和 GoogleInitializer registry 中构造 globals。它是加载时最大的单一事件,并且刻意保持浅层 —— 只做 registration。

入口点

text
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_guard bytes,理由相同。重新实现者若把 __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 bucketWhat its globals registerDistinct TUs
TPU/XLA/TSL runtimemodule 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.cccommandlineflags.cc*_flags.cc —— FLAGS_* 到 absl flag registry~28(*[Ff]lags*
MLIR / HLO dialects + passesmhlostablehlomlir_bridge_pass、dialect/pass registrations~14
Metrics / telemetrygauge/monitor/metric registries~19
protobuf / upb descriptorsproto 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.cc ctors 在 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 锚定在 deduped nm symbol 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。

c
// __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_fini slot 通过 __cxa_finalize drain 它们全部。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)

c
// __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