{
  "title": "When hello exits: who calls __cxa_finalize, and what it does",
  "steps": [
    {
      "state": {
        "view": "ribbons",
        "scale": "map"
      },
      "caption": "A program's last act is not `main` returning — it is the runtime unwinding what was set up. `build/hello` prints one line, then tears itself down.\n\nThe guide names three of this file's calls that land in `libc.so.6`: `puts`, `__libc_start_main`, and `__cxa_finalize`. This tour follows the last one — `__cxa_finalize`, glibc's per-object destructor sweep — from this file's code region across the boundary into libc's own body."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "examine": 1,
        "select": ".text",
        "tab": "disasm"
      },
      "highlights": [
        ".text"
      ],
      "caption": "Here is the code you ran. `.text` is 275 bytes, 68 instructions in `segment 2` (R-X). `main` sits at `0x1139`; its `call 1030 <puts@plt>` at `0x1147` is the whole visible job.\n\nControl reached `main` from `_start`, whose `call *0x2f5b(%rip) # 3fc0 <__libc_start_main@GLIBC_2.34>` at `0x105f` handed off to libc. When `main` returns, it returns *into* that libc frame — and libc, not this file, drives the exit. **Honest limit:** these tools show the entry hand-off and the `puts` call, but not the return-through-`exit` instructions that eventually reach `__cxa_finalize`."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "examine": 1,
        "select": ".fini",
        "tab": "disasm"
      },
      "highlights": [
        ".fini"
      ],
      "caption": "The executable region carries a dedicated finalization section, `.fini` — 13 bytes, 4 rows, also in `segment 2` (R-X). It is the counterpart to the startup `.init`: the place reserved for shutdown glue.\n\n**Honest limit:** the guide names `.fini` and gives its size and row count, but does not enumerate its instructions here, so these tools do not show `.fini` itself reaching `__cxa_finalize`. The exit-time destructor walk runs through `.fini_array` (8 bytes) — the table of finalizers the runtime steps at teardown."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".rela.dyn",
        "illuminate": ".rela.dyn"
      },
      "highlights": [
        ".fini_array",
        ".got"
      ],
      "caption": "None of that machinery has real addresses on disk — this is a PIE, so the loader patches it. `.rela.dyn` (8 entries, 192 bytes) is lit here, arcing to the four sections it patches: `.init_array`, `.fini_array`, `.got`, and `.data`.\n\nTwo of those matter at exit: `.fini_array` (the finalizer table) and `.got` (40 bytes) — where an imported symbol's resolved address is written for a `GLOB_DAT`-style binding. An imported function reached at teardown, like `__cxa_finalize`, is called through such a `.got` slot. **Honest limit:** these tools give the four patch targets but do not itemize *which* `.rela.dyn`/`.got` entry is `__cxa_finalize`'s."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign __cxa_finalize",
        "tab": "fields"
      },
      "caption": "Across the boundary now. **Outside this file:** `__cxa_finalize` lives in `libc.so.6` — glibc `2.43+r22+g8362e8ce10b2-2`, build-id `020d6f7c…` — at address `0x40960` (264,544) *in libc's own address space*, symbol version `GLIBC_2.2.5`, 692 bytes.\n\nIts recovered source is a single file, `stdlib/cxa_finalize.c`, and it folds in no inlined helpers — a self-contained routine. The Fields tab labels every number as libc's, not this file's."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign __cxa_finalize",
        "tab": "disasm"
      },
      "caption": "The real instructions — 179 rows, byte-canonical GNU objdump, grouped under their glibc source lines. It opens at `0x40960 endbr64` / `push %rbp` (line 45), then at `0x40986` takes a lock with `lock cmpxchg %edx,(%r14)` (line 48).\n\nThe payload is one instruction: `0x40b0a call *%rax` (line 97). `archive_callees` classifies exactly this edge as `indirect` — *\"not statically resolvable (*%rax)\"* — because it invokes a **registered** function pointer, unknowable until run time. Just before it, `0x40aec ror $0x11,%rax` and `0x40af0 xor %fs:0x30,%rax` (line 93) demangle that pointer. The two named callees, `__lll_lock_wait_private` and `__lll_lock_wake_private` (both `GLIBC_PRIVATE`), belong to the line-48/96 locking code, not the destructor path. The mainline return is `0x40a74 ret` (line 122); the listing continues past it with further rows — including that `call *%rax` — down to its final row at `0x40c12`."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign __cxa_finalize",
        "tab": "source"
      },
      "caption": "The C says what the machine does. `void __cxa_finalize (void *d)` (line 44) takes a DSO handle and, under `__libc_lock_lock (__exit_funcs_lock)` (line 48), walks `for (funcs = __exit_funcs; funcs; funcs = funcs->next)` (line 51). For each entry whose handle matches and whose flavor is `ef_cxa` (lines 55–56), it sets `f->flavor = ef_free` (line 91) *first* — so the destructor never runs twice — then `PTR_DEMANGLE (cxafn)` (line 93), unlocks (line 96), and runs `cxafn (cxaarg, 0)` (line 97): that is the `call *%rax`.\n\nAnd when does this sweep matter? The header comment is candid (lines 36–42): this path \"is specific to dlclose\"; during process termination it is `__run_exit_handlers` that calls the registered destructors itself, and the GCC-provided `__cxa_finalize` calls made then \"do not result in further destructor invocations.\" Finally, only if `d != NULL`, `UNREGISTER_ATFORK (d)` (lines 119–120) removes fork handlers."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign __cxa_finalize",
        "tab": "fields"
      },
      "caption": "So: **who** calls `__cxa_finalize` when `hello` exits? Not `main` directly — and, by glibc's own comment, at process termination it is `__run_exit_handlers` that runs the registered destructors itself; the `__cxa_finalize` calls made then run no further ones. What these tools do establish: this file imports `__cxa_finalize` through a loader-patched `.got` slot, so code in this file calls it during teardown with its DSO handle. The exact trigger chain is not shown by these tools.\n\n**What** it does: take the exit lock, walk the registered `__cxa_atexit` destructor lists, and for each match demangle the stored pointer and call it (`0x40b0a call *%rax`), marking the entry `ef_free` so it runs exactly once. That indirect call is the door out of libc and into whichever registered function the demangled pointer holds — a target beyond static resolution. Every foreign number here is libc's own (build-id `020d6f7c…`), not this file's."
    }
  ]
}
