The Smallest C Binary

signa111 pts0 comments

The smallest C binary | weinengI thought of a cute problem: what is the smallest (size) ./a.out binary I can create?<br>Here are some rules the program should follow:<br>./a.out must run successfully.<br>$? must deterministically be 0.<br>The binary must be produced by GCC only; no post-processing with objcopy, hex editors, or manual patching.<br>We begin with the simplest program possible:<br>// compiled with gcc empty.c<br>int main() {<br>return 0;

This gives us a file size of 15816 bytes (from stat). Not too shabby, but we will need four of the RAM used in the Apollo guidance computer to fit our binary that does nothing.<br>Looking at file:<br>❯ file a.out<br>a.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /nix/store/jms7zxzm7w1whczwny5m3gkgdjghmi2r-glibc-2.42-51/lib/ld-linux-x86-64.so.2, for GNU/Linux 3.10.0, not stripped

not stripped looks suspicious. Whatever it is, surely it is better if we can strip stuff out of our binary. It turns out that gcc provides a -s flag that compiles the code without retaining any debugging information. We are now at 14352 bytes with our code stripped.<br>Between running ./a.out and hitting int main(), there are many sorceries happening behind the scenes - so much so that there was a one-hour talk by Matt Godbolt at cppcon about it. Let’s tweak the main function so that we have a freestanding binary that skips everything that happened before int main().<br>// compiled with gcc empty.c -s -nostartfiles<br>#include

extern "C" __attribute((noreturn)) void _start() { exit(0); }

This only gives us a measly improvement to 13632 bytes. Given how much Matt complains is happening before int main, surely there is still code in there that we aren’t running but is still in our binary!<br>Checking objdump -x a.out, we can see a bunch of libraries being dynamically loaded:<br>❯ objdump -x a.out

a.out: file format elf64-x86-64<br>a.out<br>architecture: i386:x86-64, flags 0x00000150:<br>HAS_SYMS, DYNAMIC, D_PAGED<br>start address 0x0000000000001020

Program Header:<br>PHDR off 0x0000000000000040 vaddr 0x0000000000000040 paddr 0x0000000000000040 align 2**3<br>filesz 0x00000000000002a0 memsz 0x00000000000002a0 flags r--<br>INTERP off 0x00000000000002e0 vaddr 0x00000000000002e0 paddr 0x00000000000002e0 align 2**0<br>filesz 0x0000000000000053 memsz 0x0000000000000053 flags r--<br>LOAD off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**12<br>filesz 0x00000000000004a8 memsz 0x00000000000004a8 flags r--<br>LOAD off 0x0000000000001000 vaddr 0x0000000000001000 paddr 0x0000000000001000 align 2**12<br>filesz 0x000000000000002e memsz 0x000000000000002e flags r-x<br>LOAD off 0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**12<br>filesz 0x000000000000007c memsz 0x000000000000007c flags r--<br>LOAD off 0x0000000000002e78 vaddr 0x0000000000003e78 paddr 0x0000000000003e78 align 2**12<br>filesz 0x0000000000000190 memsz 0x0000000000000190 flags rw-<br>DYNAMIC off 0x0000000000002e78 vaddr 0x0000000000003e78 paddr 0x0000000000003e78 align 2**3<br>filesz 0x0000000000000170 memsz 0x0000000000000170 flags rw-<br>NOTE off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3<br>filesz 0x0000000000000030 memsz 0x0000000000000030 flags r--<br>0x6474e553 off 0x0000000000000338 vaddr 0x0000000000000338 paddr 0x0000000000000338 align 2**3<br>filesz 0x0000000000000030 memsz 0x0000000000000030 flags r--<br>EH_FRAME off 0x0000000000002000 vaddr 0x0000000000002000 paddr 0x0000000000002000 align 2**2<br>filesz 0x000000000000001c memsz 0x000000000000001c flags r--<br>STACK off 0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**4<br>filesz 0x0000000000000000 memsz 0x0000000000000000 flags rw-<br>RELRO off 0x0000000000002e78 vaddr 0x0000000000003e78 paddr 0x0000000000003e78 align 2**0<br>filesz 0x0000000000000188 memsz 0x0000000000000188 flags r--

Dynamic Section:<br>NEEDED libc.so.6<br>RUNPATH /nix/store/jms7zxzm7w1whczwny5m3gkgdjghmi2r-glibc-2.42-51/lib:/nix/store/ab3753m6i7isgvzphlar0a8xb84gl96i-gcc-15.2.0-lib/lib<br>HASH 0x0000000000000368<br>GNU_HASH 0x0000000000000380<br>STRTAB 0x00000000000003d0<br>SYMTAB 0x00000000000003a0<br>STRSZ 0x0000000000000099<br>SYMENT 0x0000000000000018<br>DEBUG 0x0000000000000000<br>PLTGOT 0x0000000000003fe8<br>PLTRELSZ 0x0000000000000018<br>PLTREL 0x0000000000000007<br>JMPREL 0x0000000000000490<br>FLAGS_1 0x0000000008000000<br>VERNEED 0x0000000000000470<br>VERNEEDNUM 0x0000000000000001<br>VERSYM 0x000000000000046a

Version References:<br>required from libc.so.6:<br>0x09691a75 0x00 02 GLIBC_2.2.5

Sections:<br>Idx Name Size VMA LMA File off Algn<br>0 .interp 00000053 00000000000002e0 00000000000002e0 000002e0 2**0<br>CONTENTS, ALLOC, LOAD, READONLY, DATA<br>1 .note.gnu.property 00000030 0000000000000338 0000000000000338 00000338 2**3<br>CONTENTS, ALLOC, LOAD, READONLY, DATA<br>2 .hash 00000014 0000000000000368 0000000000000368 00000368 2**3<br>CONTENTS, ALLOC, LOAD, READONLY, DATA<br>3 .gnu.hash 0000001c 0000000000000380 0000000000000380 00000380 2**3<br>CONTENTS, ALLOC, LOAD, READONLY, DATA<br>4 .dynsym 00000030 00000000000003a0...

flags vaddr paddr align filesz memsz

Related Articles