The Toolbox
Type This First
Run this on any program from any previous chapter (or just use /bin/ls):
$ readelf -h /bin/ls
$ nm /bin/ls 2>/dev/null || echo "stripped"
$ file /bin/ls
$ size /bin/ls
$ strace -c ls /tmp 2>&1 | tail -20
Five tools, five different views of the same binary.
/proc: The Kernel Tells You Everything
Every running process has a directory under /proc/[pid]/. Not real files — the kernel generates them on demand.
/proc/[pid]/maps Memory layout (virtual address ranges)
/proc/[pid]/smaps Detailed per-mapping info (RSS, shared, private)
/proc/[pid]/status Process summary (state, memory, threads)
/proc/[pid]/exe Symlink to the actual executable
/proc/[pid]/fd/ Open file descriptors
$ sleep 1000 &
$ cat /proc/$!/maps
555555554000-555555556000 r--p 00000000 08:01 131074 /usr/bin/sleep
555555556000-555555558000 r-xp 00002000 08:01 131074 /usr/bin/sleep
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
Fun Fact
pmapis basically a pretty-printer for/proc/[pid]/maps.
Process Inspection: strace, ltrace, pmap
strace traces system calls — every interaction between your program and the kernel:
$ strace -e trace=write echo "hello"
write(1, "hello\n", 6) = 6
ltrace traces library calls (malloc, free, printf):
$ ltrace -e malloc+free ls /tmp
malloc(132) = 0x55a1234
free(0x55a1234)
pmap shows the memory map with sizes and permissions: pmap -x <pid>.
Binary Analysis Tools
readelf -h a.out ELF header (type, arch, entry point)
readelf -S a.out Section headers (.text, .data, .bss...)
readelf -l a.out Program headers (segments)
readelf -s a.out Symbol table
readelf -d a.out Dynamic section (.so dependencies)
objdump -d a.out Disassembly
objdump -d -M intel a.out Intel syntax (more readable)
nm a.out Symbol list (T=text, D=data, B=bss, U=undefined)
size a.out Section sizes (.text, .data, .bss)
strings a.out Embedded string literals
file a.out File type and architecture
Debugging: GDB Essentials
$ gcc -g -o prog prog.c && gdb ./prog
break main Breakpoint at function
break prog.c:42 Breakpoint at line
run / run arg1 Start execution
next (n) Step over
step (s) Step into
continue (c) Continue to next breakpoint
print x / print/x ptr Print variable (decimal / hex)
bt Backtrace (call stack)
info registers All register values
info proc mappings Memory map
x/16xb 0x7fff... Examine 16 bytes in hex
x/4xg $rsp 4 quad-words at stack pointer
watch counter Break when variable changes
What do you think happens?
If you set a watchpoint with
watch counterand two threads modify it, will GDB catch both? (Hint: hardware watchpoints are per-CPU.)
Memory Debugging
Valgrind (10-50x slowdown, very thorough):
$ valgrind --leak-check=full ./prog
==12345== Invalid read of size 4
==12345== at 0x1091A2: main (prog.c:10)
Catches: leaks, use-after-free, buffer over-reads, uninitialized reads.
AddressSanitizer (2-3x slowdown, compile-time instrumentation):
$ gcc -g -fsanitize=address -o prog prog.c && ./prog
==12345==ERROR: AddressSanitizer: heap-buffer-overflow
Catches: buffer overflows, use-after-free, double-free.
UBSan (minimal overhead):
$ gcc -g -fsanitize=undefined -o prog prog.c && ./prog
prog.c:5: runtime error: signed integer overflow
Catches: signed overflow, null deref, misaligned access.
Rust-Specific Tools
cargo-geiger counts unsafe blocks in your code and dependencies:
$ cargo geiger
Functions Expressions Impls Traits Methods
2/5 14/60 0/0 0/0 1/3
Miri interprets your code and detects UB, even in unsafe:
$ cargo +nightly miri run
error: Undefined Behavior: dereferencing null pointer
cargo-bloat shows what makes your binary big:
$ cargo bloat --release
5.3% 12.1% 3.2KiB std::io::Write::write_fmt
godbolt.org — type C or Rust, see assembly instantly. Color-coded source-to-asm mapping. The single best tool for understanding compiler output.
Quick Reference: Problem to Tool
+-----------------------------------+---------------------------+
| Problem | Tool |
+-----------------------------------+---------------------------+
| "What's in this binary?" | file, readelf -h |
| "What sections does it have?" | readelf -S, size |
| "What symbols are exported?" | nm, readelf -s |
| "What does the assembly look like"| objdump -d, godbolt.org |
| "What strings are embedded?" | strings |
| "What syscalls does it make?" | strace |
| "What libraries does it call?" | ltrace, ldd |
| "Where is its memory?" | /proc/pid/maps, pmap |
| "Why does it crash?" | gdb, bt, info registers |
| "Does it leak memory?" | valgrind --leak-check |
| "Does it overflow buffers?" | -fsanitize=address |
| "Does it have undefined behavior?"| -fsanitize=undefined, miri|
| "How much unsafe in my Rust?" | cargo-geiger |
| "Why is my Rust binary big?" | cargo-bloat |
+-----------------------------------+---------------------------+
Task
- Pick any program you compiled in a previous chapter.
- Run it through at least THREE tools from this chapter.
- For each tool, write down one thing you learned that you didn't know before.
- Bonus: Compile a buggy C program from Chapter 25 with
-fsanitize=address. Does it catch the bug?- Bonus: Run
straceonls, then onls | cat. Does thewritecount change? Why?