Step 7 · Ubuntu · Independent

Reading binaries

A compiled program is just a file. These tools let you read that file as what it is — bytes, sections, symbols, instructions — rather than as what it does. It is the fastest way to understand what compiling actually produced.

This step stands alone

It pairs naturally with step 3, since having a compiler gives you binaries of your own to take apart. But it does not require it — every one of these tools works on programs already on your machine. It is not part of the install_all.sh run.

Run this

$ curl -fsSL https://aikaryashala.com/system_setup/scripts/install_binary.sh | bash

The script finishes by compiling a small program — or falling back to /bin/ls if you have not installed clang — and showing you its first bytes, its size breakdown and its ELF header.

What gets installed

Reading a file as bytes

Reading structure and symbols

Watching a program run

Building more than one file

Look at the bytes

Compile anything — or just use a program you already have — and look at it:

xxd — the classic

$ xxd -l 64 hello
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0300 3e00 0100 0000 4010 0000 0000 0000  ..>.....@.......
00000020: 4000 0000 0000 0000 7834 0000 0000 0000  @.......x4......
00000030: 0000 0000 4000 3800 0d00 4000 1e00 1d00  ....@.8...@.....

Three columns: the offset into the file, the bytes in hexadecimal, and those same bytes as text where they happen to be printable.

Those first four bytes — 7f 45 4c 46 — are the magic number 0x7F followed by "ELF". That is how Linux recognises an executable. Every file format has a marker like this; file works by reading them.

xxd options you will actually use

hexyl — the readable one

$ hexyl -n 64 hello

Same information, but colour-coded by category: null bytes, ASCII text, printable punctuation and non-ASCII each get their own colour. When you are hunting for structure in an unfamiliar file, this is the one to reach for.

hexdump and od — the traditional pair

# hexdump -C is the canonical side-by-side view
$ hexdump -C -n 64 hello

# od: hex bytes with hex offsets and printable characters
$ od -A x -t x1z -N 64 hello

# od can also show other bases - here, octal
$ od -A o -t o1 -N 32 hello

od is worth knowing because it is guaranteed present on any Unix system, however minimal — including containers where xxd is not.

Reading structure, not just bytes

# what kind of file is this?
$ file hello

# the readable text hiding inside the binary
$ strings hello | head

# how big are the code and data sections?
$ size hello

# the ELF header, explained field by field
$ readelf -h hello

# every section in the file
$ readelf -S hello

# function and variable names
$ nm hello

# disassemble main back into assembly
$ objdump -d --disassemble=main hello

# assembly interleaved with the C it came from (needs -g)
$ objdump -S hello | less
The exercise that makes this click

Compile the same file twice — once with -O0 and once with -O2 — then run objdump -d --disassemble=main on each and put the output side by side. Seeing what the optimiser did to your loop teaches more about how C works than any amount of reading.

Watching a program run

# find memory leaks and invalid accesses, without recompiling
$ valgrind --leak-check=full ./hello

# every system call the program makes
$ strace ./hello

# just the file-related ones
$ strace -e trace=openat,read,write ./hello

# calls into shared libraries instead
$ ltrace ./hello

strace is the tool that answers “why can't it find that file?” — you see the exact path it tried and the error it got back.

A Makefile, once one file is not enough

CC      = clang
CFLAGS  = -g -O0 -Wall -Wextra -std=c17
TARGET  = hello

$(TARGET): hello.c
	$(CC) $(CFLAGS) -o $(TARGET) hello.c

debug: CFLAGS += -fsanitize=address
debug: clean $(TARGET)

clean:
	rm -f $(TARGET)

.PHONY: debug clean
Makefiles demand real tab characters

The indented lines under each rule must begin with a tab, not spaces. This is the single most common reason a Makefile fails with “missing separator”.

Then make to build, make debug for the sanitizer build, make clean to start over.

Check it worked

$ xxd -l 16 /bin/ls
$ readelf -h /bin/ls | head -6
$ objdump --version | head -1
$ valgrind --version