2026-07-19
The asker is building Cortex-M0 firmware with arm-none-eabi-gcc, using the classic size-shrinking trio: -ffunction-sections -fdata-sections -Wl,--gc-sections. This tells the compiler to emit each function into its own .text.foo section, and the linker to garbage-collect any section nothing references. It works — the binary shrinks — but under GCC 11+, single-stepping in GDB shows source lines from functions that were discarded. GCC 10 and earlier don't do this.
Why is this interesting? It's a subtle interaction between three moving parts: DWARF line-number tables, section garbage collection, and a GCC 11 change in how relocations against discarded sections are emitted. The DWARF .debug_line program encodes (address → file:line) tuples. When the linker GCs a .text.foo section, any address-typed relocation pointing into it needs to be resolved to something. Historically the linker zeroed such relocations, so those line entries collapsed to address 0 and GDB happily ignored them. Starting with binutils/GCC changes around that era, .debug_line uses DW_LNE_set_address relocations that survive GC differently — the linker resolves them to the tombstone value (either 0, -1, or -2 depending on ld's --no-dwarf-tombstone/-z dead-reloc-in-nonalloc settings), and GDB's interpretation of tombstones changed too.
Direction toward a solution:
.debug_line with llvm-dwarfdump --debug-line or readelf --debug-dump=decodedline. Look for entries with addresses of 0, 0xffffffff, or 0xfffffffe — those are tombstones for GC'd code. If GDB is treating them as real, that's your regression.-Wl,-z,dead-reloc-in-nonalloc=.debug_line=0xffffffffffffffff (or =0) to explicitly pick a tombstone value. GDB from ~10.x onward should skip -1 tombstoned rows; older versions expected 0.objdump -Wl whether the offending line entries reference a range that was GC'd. If so, upgrading GDB (or downgrading the tombstone via linker flag) is the fix, not touching the source.Gotchas: LTO complicates this further — line info can be attributed to the wrong TU entirely. Also, --gc-sections doesn't touch .debug_* sections themselves, only allocatable ones, so the debug info describing dead code stays put. And if you use -flto, .debug_line can be regenerated from the merged IR, sometimes with different tombstone conventions than a non-LTO build.
