DWARF as a chain of trust
Debug information does not change program behavior. That is convenient for debugging and awkward when a safety verdict rests on it.
Debug information was invented so that a debugger could show you a source line while you step through machine code. It does that job well, and nothing about that job is safety-critical.
The moment you use it to project coverage measured on object code back to the source level, it becomes something else: an element of the safety assurance chain. Our own internal notes put it plainly - its quality must be critically questioned, as it is traditionally only used for debugging.
This article is about what changes when metadata is promoted to evidence.
DWARF is produced by the toolchain and is therefore an evidence input, not an independent ground truth. Its use requires executable identity checks, supported producer/version knowledge, consistency checks against the object code and explicit treatment of ambiguous or missing ranges. The tested compiler, linker and DWARF versions, relevant options, language and target combinations, and known producer-specific limitations belong in the published support claim - “supports DWARF” is too broad to be a useful product claim.
Four structural properties
1. It is functionally silent, so ordinary application tests do not validate it
Debug information normally does not affect the target program’s functional behavior. A binary with a thoroughly broken line table can pass every requirements-based test campaign you build, including the one that produced your coverage report - so a successful campaign does not, by itself, show that the line table or location information is correct.
Confidence has to come from dedicated metadata verification instead: defined operational requirements, producer and version test corpora, consistency checks, differential tests, regression tests and - where certification credit is taken - an appropriate qualification or independent-verification argument. The same caution applies to compiler output generally: testing contributes evidence, but does not guarantee that every code-generation defect will be exposed.
2. The format is defined, but optimized source attribution is not uniquely prescribed
Ask what line an instruction belongs to after it was hoisted out of a loop and fused with another. Under optimization this is not a hard question; it is an ill-posed one. There is no single right answer, because the instruction genuinely descends from more than one place - or from a place that no longer exists.
The format says so itself. Section 1.3.12 of the DWARF standard is titled “Permissive Rather Than Prescriptive” and states that it “does not specify in detail what a particular producer must generate for any source to object conversion” and that differences between producers are “differences in quality-of-implementation”. That is the correct engineering decision for a debugger format. DWARF specifies the representation and semantics of its data while deliberately leaving producers latitude in how optimized source constructs are represented. A coverage use therefore needs an additional operational specification: which source obligations must be mapped, which ambiguities are permitted, how inlining, cloning and folding are represented, and what constitutes an unresolved result. That operational specification can be validated and, where needed, qualified.
Dedicated checks can detect missing ranges, malformed tables, non-monotonic mappings, producer regressions and disagreement with an independent mapping. What they cannot do is make an inherently many-to-many optimized mapping unique. A report should therefore distinguish detected defects, detected ambiguity and residual undetected risk.
What the latitude looks like in practice - the jumping cursor, the merged location, the line number 0 - is what the debug information actually says.
3. The trusted computing base is large, and empirically fragile
The DWARF line table is not a lookup table. It is a bytecode program for a state machine that a consumer has to interpret. That state machine, and the compiler code that emits its program, are in the trust chain of your coverage verdict.
Two public observations about how well that goes:
- The LLVM project maintains a dedicated facility for the problem,
debugify: a pair of passes, one attaching synthetic debug information to every instruction of a module, the other checking after a transformation that it is still there and reporting what was lost. The facility exists because optimization passes can lose or misrepresent debug metadata and regressions need automated detection; it demonstrates that preservation is testable and taken seriously. How often loss occurs is a separate question, answered only by measured studies - such as the one below. - The first systematic study of the question produced 47 confirmed bug
reports against GCC and LLVM, every one of them affecting the then-current
development version, spread across all optimization levels - including GCC’s
-Og, the level whose purpose is a better debugging experience. The numbers, in context.
And the emission changes with each compiler release. Your evidence base moves with every toolchain update, silently.
4. Degradation is silent
Take the three properties above together and the consequence is the interesting one: the dedicated checks from property 2 narrow the problem, but they do not close it. Detected defects and detected ambiguity can be reported; what remains is residual undetected risk - and inside that remainder there is no error state to detect, no assertion that fires, no anomaly in the report. A coverage report built on subtly wrong debug information looks exactly like a correct one.
Compare two chains
The comparison that matters is not “which one is true”. Both chains rest on compiler output. The difference is what happens when a link is wrong.
| Verdict via debug information | Verdict via symbol table and anchors | |
|---|---|---|
| Is the artifact functionally load-bearing? | no - metadata only | yes - the linker consumes the same symbols to build the binary |
| Is the attribution criterion uniquely defined? | not under optimization - an operational specification must supply it | explicit and address-resolvable for each validated anchor-manifest entry; the source-obligation association still requires validation |
| Size of the trusted computing base | a bytecode interpreter plus the emitting passes | read a table, disassemble at an address |
| Failure mode | silent | missing, duplicate or unexpectedly resolved symbols are directly detectable; wrong placement or manifest association can stay silent unless independently checked |
The honest caveat belongs here too: a symbol table is not axiomatically true either. The difference is not truth against falsehood. It is checkable with a small trusted base and detectable degradation against a mapping that ordinary application tests do not validate, whose optimized attribution is not uniquely prescribed, and whose residual degradation is silent.
The same symmetry applies to anchors: post-link checks can detect missing, duplicate or unexpectedly resolved symbols, but the method still depends on compiler and linker behavior, manifest correctness, anchor placement, and validation of the intended source obligation.
Why this is not an optimization-level argument
The mapping problem described in
the previous article gets worse as you
optimize. The assurance question, however, remains at every optimization
level - ambiguity is generally greater under optimization, but even at -O0
the qualification question stands: how would you demonstrate, to an assessor,
that the debug information used to produce your source-level verdict is
correct?
The available answers are not satisfying:
- “The compiler is widely used.” True, and it applies to code generation, which is verified through the product. It does not transfer to metadata that the product does not exercise.
- “We spot-checked it.” Sampling finds systematic errors, not the individual wrong attribution that moved one line from uncovered to covered.
- “The debugger shows the right lines.” At
-O0. That is the regime where the mapping problem is smallest.
What to take from this
If your coverage evidence is produced at source level on an unoptimized build, this chain is short and the risk is low.
If it is produced by mapping object-level measurements back to source through debug information on an optimized build, then the debug information is part of your argument, and it is worth asking your tool vendor - and yourself - how that part is verified.
The question is not rhetorical, and it is not aimed at any particular product. It applies to every tool in the class, ours included, which is why we would rather write it down than wait to be asked.
Sources
The factual statements in this article that rely on external sources are supported by four publicly available documents, listed below for reference and verification:
- the DWARF Debugging Information Format Version 5 - section 1.3.12 is quoted in the article;
- LLVM’s own guide to updating debug information in optimization
passes,
which documents the
debugifyfacility; - Yuanbo Li, Shuo Ding, Qirun Zhang and Davide Italiano, Debug Information Validation for Optimized Code, PLDI 2020 (author’s PDF);
- RTCA DO-178C / EUROCAE ED-12C and DO-330 / ED-215 for the qualification argument (licensed publications; cited by section).
Where a statement rests on our own reasoning or our internal engineering notes, it says so at that statement.
One step to take: write down the compiler, version and options behind your current coverage report, and ask your tool vendor which of those producer combinations are validated for source attribution - and how missing or ambiguous ranges appear in the report.