TRACK: bigcode | DIFFICULTY: 2 | NEEDS: readelf, llvm-dwarfdump, clang -frecord-command-line, a compile database
"The build is -O2" is a belief until you read it off the binary. Binaries carry evidence of their origin in four places; each answers a different question and each can be missing. This lab reads all four on real files.
.comment: which compiler linked the objects$ readelf -p .comment $LLVM_BUILD/bin/clang | grep "\]" | head -3
[ 0] GCC: (GNU) 15.2.1 20260123 (Red Hat 15.2.1-7)
$ readelf -p .comment /lib64/libLLVM.so.21.1
readelf: Warning: Section '.comment' was not dumped because it does not exist
Surprise number one: the clang binary in the local build was compiled by GCC 15. Lab 01's compile database said /usr/lib64/ccache/c++; .comment confirms it from the artifact alone. Surprise number two: the distro library has no .comment at all: Fedora strips it. .comment collects one string per object file's compiler; a binary built from mixed toolchains shows several.
DW_AT_producer: compiler and flags, per compilation unit, when there is DWARF$ llvm-dwarfdump --debug-info perf/bin/03_hot_function | grep -m1 DW_AT_producer
DW_AT_producer ("clang version 21.1.8 (Fedora 21.1.8-4.fc43)")
$ llvm-dwarfdump --debug-info $LLVM_BUILD/bin/clang | grep -m1 DW_AT_producer
(nothing: the Release build has no DWARF)
With -g, every compilation unit records its producer. Clang records the version by default and adds the flags with -grecord-command-line (GCC: -grecord-gcc-switches, on by default in some distro configurations): then DW_AT_producer reads like clang version 21.1.8 -O2 -march=x86-64-v3 -g .... Without -g there is nothing here, which is the case for this Release LLVM and for most shipped binaries.
-frecord-command-line: the whole command line, even without -g$ clang -O2 -g -march=x86-64-v3 -frecord-command-line -c perf/01_noise_floor.c -o /tmp/rc.o
$ readelf -p .GCC.command.line /tmp/rc.o | grep "\]" | cut -c1-200
[ 1] /usr/bin/clang-21 --gcc-triple=x86_64-redhat-linux -O2 -g -march=x86-64-v3 -frecord-command-line -c -fcolor-diagnostics -o /tmp/rc.o perf/01_noise_floor.c
The .GCC.command.line section (the name is a GCC convention that clang honours) stores the exact driver command line. Note what the driver added that you did not type: --gcc-triple, -fcolor-diagnostics. If you own the build, turn this on for release binaries: it costs a few hundred bytes per object and ends every "what flags was this built with" argument. It survives linking (sections are concatenated, one entry per object) but not strip --remove-section.
$ readelf -n perf/bin/03_hot_function.stripped | grep -A1 "Build ID"
Build ID: 7ba4afdee8cb58b4f09b12d6547ad57353ac7865 (from the perf-track build)
$ readelf -p .gnu_debuglink perf/bin/03_hot_function.stripped | grep -o "03_hot_function.debug"
03_hot_function.debug
The build-id is a hash of the linked contents. It identifies the exact build across machines: perf's cache (~/.debug/.build-id/), debuginfod servers, crash reporters and symbol servers all key on it. If you keep the unstripped binary or the .debug file for every build-id you ship, you can symbolize any crash or profile from the field.
For a binary you are about to build, ask the compiler what it will do before it does it:
clang -O2 -march=native -### -c x.c 2>&1 | tail -1 # the exact cc1 invocation: -target-cpu, -target-feature, defaults you did not specify
clang -O2 -march=native -E -dM - </dev/null | grep -E "__AVX2__|__FMA__|__OPTIMIZE__" # what the preprocessor sees
clang -v -c x.c 2>&1 | grep -A20 "#include <...>" # the include search order actually used
clang -print-search-dirs # where libraries and crt files come from
-### is how you discover that -march=native on this laptop means -target-cpu alderlake with a list of -target-feature +avx2 +fma ..., that -ffp-contract=on is implied (codegen lab 02's FMA surprise), and that -fPIE may or may not be default.
| Evidence | Where | Present when | Answers |
|---|---|---|---|
.comment | readelf -p .comment | not stripped of it | compiler(s) and versions that produced the objects |
DW_AT_producer | llvm-dwarfdump --debug-info | built with -g | compiler per CU; flags too with -grecord-command-line |
.GCC.command.line | readelf -p .GCC.command.line | built with -frecord-command-line | the exact driver line per object |
| build-id + debuglink | readelf -n, readelf -p .gnu_debuglink | almost always (build-id), if packaged well (debuglink) | which build; where its debug info is |
| compile database | compile_commands.json | you have the build tree | everything, but only for that tree, not for the binary in your hands |
| ISA actually used | objdump -d bin | grep -cE "vfmadd|vpgather|zmm" | always | did -march reach the code? |
The last row is the one people forget: flags describe intent; the disassembly describes the result. A binary "built with -march=native" that contains no AVX instruction in its hot loop was built with something else, or the hot loop did not vectorize (codegen lab 02).
-frecord-command-line -grecord-command-line to perf/build.sh, rebuild, and read both sections back.objdump -d /lib64/libLLVM.so.21.1 | grep -c vpgatherdd versus the same for $LLVM_BUILD/bin/clang: which build targets a newer ISA? (Fedora builds for x86-64 baseline; check your local build's -march in the compile database.).GCC.command.line section: for f in /usr/bin/*; do readelf -S "$f" 2>/dev/null | grep -q GCC.command && echo "$f"; done | head.A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.