FForge
← Labsbigcode-03Large codebases●●○○

Flags archaeology — which compiler, which flags, produced this binary?

bigcode/03_flags_archaeology.mdneeds: `readelf`, `llvm-dwarfdump`, `clang -frecord-command-line`, a compile database
evidence to leave with
The local clang was built by GCC 15 (from .comment); the exact driver line recovered from an object's .GCC.command.line

BIGCODE LAB #03: Flags archaeology — which compiler, which flags, produced this binary?

TRACK: bigcode | DIFFICULTY: 2 | NEEDS: readelf, llvm-dwarfdump, clang -frecord-command-line, a compile database

What this teaches

"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.

STEP 1 — .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.

STEP 2 — 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.

STEP 3 — -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.

STEP 4 — Build-id and debuglink: the handle to everything else

$ 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.

STEP 5 — The driver's own testimony

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.

Putting it together: the "what am I looking at" checklist

EvidenceWherePresent whenAnswers
.commentreadelf -p .commentnot stripped of itcompiler(s) and versions that produced the objects
DW_AT_producerllvm-dwarfdump --debug-infobuilt with -gcompiler per CU; flags too with -grecord-command-line
.GCC.command.linereadelf -p .GCC.command.linebuilt with -frecord-command-linethe exact driver line per object
build-id + debuglinkreadelf -n, readelf -p .gnu_debuglinkalmost always (build-id), if packaged well (debuglink)which build; where its debug info is
compile databasecompile_commands.jsonyou have the build treeeverything, but only for that tree, not for the binary in your hands
ISA actually usedobjdump -d bin | grep -cE "vfmadd|vpgather|zmm"alwaysdid -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).

Try this yourself

  1. Add -frecord-command-line -grecord-command-line to perf/build.sh, rebuild, and read both sections back.
  2. 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.)
  3. Find a binary on the system with a .GCC.command.line section: for f in /usr/bin/*; do readelf -S "$f" 2>/dev/null | grep -q GCC.command && echo "$f"; done | head.
◇
Finish the lab: write the evidence note

A lab is done when you can say what it showed on your machine — not when you have read it.