{
  "title": "What happens after main calls puts",
  "steps": [
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "illuminate": "libc.so.6"
      },
      "highlights": [
        ".text"
      ],
      "caption": "`build/hello` is a 16,984-byte PIE with no printing code of its own. Its one runtime dependency arcs out to the `libc.so.6` chip — the `needs_library` edge, read from `.dynamic`.\n\nThree of this file's calls land in `libc.so.6` — `puts`, `__libc_start_main`, `__cxa_finalize` — but only `puts` travels through the PLT, and it is the one that prints. Let's follow that call from `main` all the way into the library and watch what `puts` does with the string."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "examine": 1,
        "select": ".text",
        "tab": "disasm"
      },
      "highlights": [
        ".text"
      ],
      "caption": "Here is the call itself. `main` sits at `0x1139` inside `.text` (275 bytes, 68 instructions). Two instructions set up the argument: the 7-byte `lea 0xec0(%rip),%rax` at `0x113d` computes the string's address — `0x1144 + 0xec0 = 0x2004` — and `mov %rax,%rdi` loads it.\n\nThen at `0x1147`: `call 1030 <puts@plt>` (5 bytes). Notice the target is not `puts` — it is `0x1030`, a stub *inside this file*."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "examine": 1,
        "select": ".plt",
        "tab": "disasm"
      },
      "highlights": [
        ".plt"
      ],
      "caption": "That target lives in `.plt` — the Procedure Linkage Table, 32 bytes, 6 rows. The label `puts@plt` marks `0x1030`; a shared resolver trampoline sits above it at `puts@plt-0x10` (`0x1020`).\n\nThe stub's row on screen is a single `jmp` through a pointer — not library code. The call reaches this stub, and the stub jumps to wherever that pointer currently aims."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".rela.plt",
        "illuminate": ".rela.plt"
      },
      "highlights": [
        ".rela.plt",
        ".got.plt"
      ],
      "caption": "What fills that pointer is one relocation. `.rela.plt` is a single 24-byte `Elf64_Rela` entry, and its edge patches `.got.plt` (the `reloc_patches` link lit here) — the 32-byte slot table the `.plt` stub jumps through.\n\nBecause `hello` is a PIE, no address is known until the loader maps the file; the dynamic linker writes `puts`'s resolved address into that `.got.plt` slot so the stub can find it. This one relocation is the whole binding for the program's one import."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign puts",
        "tab": "fields"
      },
      "caption": "Follow the pointer and we leave the file entirely. `puts` is 542 bytes at `0x83fc0` (540,608) — an address in `libc.so.6`'s own space, not `hello`'s.\n\nThe drawer's \"Outside this file\" banner means exactly that: this is a reference copy of `libc.so.6` from glibc 2.43+r22+g8362e8ce10b2-2 (build-id 020d6f7c…), symbol version `GLIBC_2.2.5`, recovered from `libio/ioputs.c`. Everything past this point is a different file with its own layout."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign puts",
        "tab": "disasm"
      },
      "caption": "Inside `puts`. The prologue at `0x83fc0` opens with `endbr64` then `push %rbp`. The disassembly is grouped under the glibc source lines the DWARF maps it to.\n\nUnder *ioputs.c:35* the source computes `strlen(str)`; it compiles to `0x83fd6 call 24250 <*ABS*+0xaff50@plt>` — a PLT thunk whose target the archive names no symbol behind. Under *ioputs.c:36* `puts` loads the `stdout` FILE* (`0x83fdb mov 0x18ed66(%rip),%r13 # … stdout`) and takes its lock: after a `__libc_single_threaded` shortcut check at `0x83ff9`, the uncontended acquire is `0x8401f lock cmpxchg %ecx,(%rdi)`."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign puts",
        "tab": "disasm"
      },
      "caption": "Now the write. Before dispatching through the stream's vtable, `puts` validates that vtable — the inlined `IO_validate_vtable` check `0x8408b cmp $0x92f,%rdx` (from *libioP.h:1038*) bounds-checks the pointer. Then, mapped to *ioputs.c:40*, `0x8409e call *0x38(%rax)` dispatches through the stream's vtable to send the string; being an indirect call, its target is not statically resolvable.\n\nAnd here is the newline. Mapped to *ioputs.c:41*, `0x840c0 movb $0xa,(%rax)` writes byte `0x0a` — `'\\n'` — straight into the output buffer. If the buffer is full instead, the slow path at `0x841a0 mov $0xa,%esi; 0x841a5 call 915d0 <__overflow>` hands that same newline to `__overflow` (whose own write goes through the stream vtable — beyond static resolution). `puts` adds the `\\n` itself."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign puts",
        "tab": "source"
      },
      "caption": "The whole function in C — recovered *libio/ioputs.c*, sparse with ⋯ for unmapped lines. `_IO_puts(const char *str)` reads top to bottom: line 35 `strlen(str)`, line 36 `_IO_acquire_lock(stdout)`, line 40 `_IO_sputn(stdout, str, len)`, line 41 `_IO_putc_unlocked('\\n', stdout)`, on success line 42 sets `result = MIN(INT_MAX, len + 1)`, then line 44 `_IO_release_lock(stdout)` and line 45 `return result`.\n\nLine 41 is the C behind the newline byte you just saw. `puts` is `static_weak_alias(_IO_puts, puts)` — the name `main` called is an alias for this."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "examine": 2,
        "select": ".rodata",
        "tab": "strings"
      },
      "highlights": [
        ".rodata"
      ],
      "caption": "Back in `hello`, the loop closes. `.rodata` holds `\"hello, world\"` at `0x2004` — 12 bytes, and no trailing newline. The programmer wrote `printf(\"…\\n\")`; the compiler, seeing a plain string with a newline, rewrote it as `puts(\"…\")` and dropped the `\\n` from the data.\n\nThat missing byte is not lost — it is line 41 of `ioputs.c`, the `movb $0xa` we watched `puts` write at run time. The file stores twelve characters; the thirteenth is supplied by the library on every successful write — line 41 only runs once `_IO_sputn` has delivered all twelve."
    }
  ]
}
