{
  "title": "true.c is 80 lines — so why is the binary 43 KB?",
  "steps": [
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".text"
      },
      "highlights": [
        ".text"
      ],
      "caption": "`true` is the program that does nothing — it returns success and exits. Its source, coreutils `src/true.c`, is about 80 lines. Yet the shipped `/usr/bin/true` is ~43 KB, and the file on screen is larger still: `build/true.debug` is 109,648 bytes, with a 20,275-byte `.text`.\n\nOne caveat before we start: this is not the shipped binary byte-for-byte. It is the same coreutils `true` source rebuilt in a frozen toolchain with `-g` (debug info kept), so it carries a different build-id. Strip it and it collapses to about 44 KB — within roughly 1% of the shipped `/usr/bin/true`'s 43,248 bytes (Exhibit 002, where that figure is on the page) — same source, same tools. The point of keeping the debug info is that every one of those 20,275 `.text` bytes now maps back to a real source file. Full DWARF; the symbol table names 158 functions. Let's use that to find out what actually fills the file."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".text",
        "tab": "disasm"
      },
      "highlights": [
        ".text"
      ],
      "caption": "Here is the program itself. In the Disasm tab the machine code is grouped under the DWARF source lines it came from, and `main` — 282 bytes at `0x2cfb`, all mapped to `src/true.c` — is almost embarrassingly small.\n\nIts real logic is three instructions. At `0x2d0a` `cmpl $0x2,-0x4(%rbp)` (source `true.c:59`, `if (argc == 2)`) asks whether there is exactly one argument. In the common case there is not, so `0x2d0e jne 2e0e` jumps straight past everything to `0x2e0e mov $0x0,%eax` (`true.c:79`, `return EXIT_STATUS`), then `0x2e13 leave` / `0x2e14 ret` (`true.c:80`). That is the whole program: set the return value to `0` and return."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".text",
        "tab": "disasm"
      },
      "highlights": [
        ".text"
      ],
      "caption": "The only other path is the `argc == 2` branch — and notice it contains no \"work\" either, just the ritual every well-behaved GNU tool performs. `0x2d1e call set_program_name`, then `0x2d32 call setlocale@plt`, `0x2d4b call bindtextdomain@plt`, `0x2d5a call textdomain@plt` (`true.c:62`–`65`) — locale and translation setup. `0x2d69 call atexit` (`true.c:69`) registers a cleanup handler, `close_stdout`. Hold onto that one.\n\nThen two string compares decide the tool's contract: `0x2d86 call streq` … `0x2d94 call usage` handles `--help` (`true.c:71`–`72`), and `0x2db1 call streq` … `0x2e09 call version_etc` handles `--version` (`true.c:74`–`75`). Even `true`'s \"features\" are just `--help` and `--version`."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".text",
        "tab": "disasm"
      },
      "highlights": [
        ".text"
      ],
      "caption": "So where did 20 KB of code come from? The DWARF map answers by source file. Scroll the Disasm tab and the `── file:line ──` headers keep changing — this one `.text` section is stitched together from 27 different source files.\n\nCount the address ranges each file owns (from the DWARF line map, 1,693 ranges total): `lib/quotearg.c` **699**, `lib/version-etc.c` **166**, `src/system.h` **156**, `lib/mbrtoc32.c` **140**, `lib/xmalloc.c` **137**, `lib/error.c` **74**. And `src/true.c` — the actual program — just **32**, under 2% of the file. The other 98% is gnulib: the shared \"model GNU command\" scaffolding for argument quoting, `--version` text, Unicode/locale handling, and checked allocation. The stripped shipped binary has all this mass too; it just can't tell you any of these names."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".text",
        "tab": "disasm"
      },
      "highlights": [
        ".text"
      ],
      "caption": "Take one piece up close. When `main` handles `--version` it calls `version_etc` at `0x2e09` (`true.c:75`); that function lives at `0x5ee6`, is 224 bytes, and every row of it maps to `lib/version-etc.c` — its prologue at `0x5ee6` sits under `version-etc.c:233`.\n\nThat is 166 ranges of machine code whose entire job is to print the author line, copyright, and GPL notice you see from `true --version`. `true.c` never spells any of that out; it just calls one gnulib function and inherits the standard GNU `--version` block. (The biggest file of all, `lib/quotearg.c` at 699 ranges, is the argument-quoting layer pulled in with gnulib's error machinery — code `true`'s own logic never obviously touches.)"
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign exit",
        "tab": "disasm"
      },
      "caption": "Follow the program off its own edge. When `true` finishes — whether `usage` calls `exit(status)` directly (`0x2cf6 call 22a0 <exit@plt>`, `true.c:51`) or `main` just returns `0` to `__libc_start_main` — control lands in libc's `exit`. The drawer's \"Outside this file\" banner marks the crossing: this is a reference copy of `exit` from `libc.so.6`, glibc 2.43+r22 (build-id `020d6f7c…`), symbol version `GLIBC_2.2.5`, recovered from `stdlib/exit.c`.\n\nIt is tiny: 30 bytes, 7 rows, at `0x411c0` in the library's own address space. `exit` is the shared success path every one of the 47 imported libc functions ultimately sits behind."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": "foreign exit",
        "tab": "source"
      },
      "caption": "Here is `exit` in C, recovered from *stdlib/exit.c*. The whole body is a single statement — line 148: `__run_exit_handlers (status, &__exit_funcs, true, true);` — and it compiles to `exit`'s one and only call, `0x411d9 call 40f10`.\n\nAn honesty note about that call: the archive's own control-flow analysis reports the target `0x40f10` as landing inside no exported function — it claims no name for it. So `__run_exit_handlers` is the name the *source* gives; the symbol table does not confirm it. What matters for `true` is what those handlers do: remember the `atexit(close_stdout)` registered back at `true.c:69`? This is where it runs — flushing standard output on the way out, so a `true` piped into something still delivers cleanly. `exit` reaches line 148, the handlers fire, and the process is gone."
    },
    {
      "state": {
        "view": "ribbons",
        "scale": "map",
        "select": ".text"
      },
      "highlights": [
        ".text"
      ],
      "caption": "So: an 80-line program, a 43 KB shipped binary. The gap is not the program — `src/true.c` is 32 of 1,693 mapped ranges. The gap is the *contract* of being a proper GNU tool: internationalized messages, a `--help` and `--version` block, quoted arguments in errors, checked allocation — the gnulib scaffolding in `lib/quotearg.c` (699), `lib/version-etc.c` (166), `lib/mbrtoc32.c` (140), `lib/xmalloc.c` (137), and the rest.\n\nThe shipped `/usr/bin/true` carries every byte of this too, but stripped it can only gesture at it. Because this copy was rebuilt from real source with its DWARF intact, we could name each piece by the file it came from — the honest, legible answer to \"why is `true` 43 KB?\""
    }
  ]
}
