Measure DWARF Variable Coverage with llvm-locstats
You will finish with a repeatable report of how much of each variable's or parameter's live scope has usable DWARF location information, plus a comparison between two builds. The report helps explain why a debugger shows a value for only part of a function.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 15 minutes, plus time to produce two suitable binaries. You need an ELF or Mach-O object with DWARF information and an LLVM installation containing llvm-locstats. The local reference is from the llvm-20 package, version 20.1.8.
Important local check: this machine has the llvm-locstats-20 manpage, but the installed package does not provide an executable with that name. Run the check below before doing anything else. A manpage is documentation, not the program.
$ command -v llvm-locstats-20 || command -v llvm-locstats
$ dpkg -L llvm-20 | grep '/llvm-locstats'
$ printf '%s\n' 'If both checks are empty, obtain an LLVM build that includes llvm-locstats before continuing.'
On this host the first two commands produce no path. Do not replace the missing tool with llvm-dwarfdump: it can inspect DWARF, but it does not produce the location-coverage histogram described here.
1. Prepare a binary with debug information
Compile a small test program with debug information and without aggressive optimisation. This creates a useful baseline where variables are easier to track:
$ cat > /tmp/locstats-demo.c <<'EOF'
int add(int left, int right)
{
int total = left + right;
return total;
}
int main(void)
{
return add(20, 22);
}
EOF
$ clang-20 -g -O0 /tmp/locstats-demo.c -o /tmp/locstats-demo
$ llvm-dwarfdump-20 --debug-info /tmp/locstats-demo | grep -E 'DW_TAG_(variable|formal_parameter)'
The last command should show entries for the function parameters and the local variable. The exact offsets and attributes vary between compiler builds. If it prints nothing, stop and check that the compiler actually emitted DWARF before blaming the coverage tool.
Checkpoint: keep the input file path short and explicit. The filename is a positional argument, not an option value, so place it after any options.
2. Print the coverage histogram
With an executable available, run the basic report:
$ llvm-locstats /tmp/locstats-demo
=================================================
Debug Location Statistics
=================================================
cov% samples percentage(~)
-------------------------------------------------
0% ...
[50%,60%) ...
100% ...
-the number of debug variables processed: ...
-PC ranges covered: ...%
-total availability: ...%
The numbers depend on the compiler and optimisation choices, so treat the shape of the report as the stable part. The 0% row counts DIEs with no location information. The 100% row counts DIEs whose location information covers all code-section bytes in scope. A bucket such as [50%,60%) means that the location covers at least 50% and less than 60% of that scope.
The final figures answer two related questions. The number of processed debug variables tells you the population. PC ranges covered describes the code-range coverage used by the calculation, while total availability is the aggregate availability reported by the tool. Neither percentage proves that a debugger can show every source-level value at every instruction.
3. Narrow the population when investigating a symptom
Start with the full report, then narrow it so unrelated DIEs do not distract from the question. To measure local variables only:
$ llvm-locstats --only-variables /tmp/locstats-demo
To measure formal parameters only:
$ llvm-locstats --only-formal-parameters /tmp/locstats-demo
These filters select what is counted; they do not repair missing debug information or alter the binary. The options are mutually useful investigations, not switches that change compiler output. Record which filter was used beside each result, otherwise two reports can look comparable while measuring different populations.
4. Decide how to treat entry-value locations
Optimised code can describe a parameter using a DWARF debug entry value. If you want a report that ignores locations containing that operation, add the documented flag:
$ llvm-locstats --ignore-debug-entry-values /tmp/locstats-demo
Use this only when it matches the question you are asking. A lower result after ignoring entry values does not mean the compiler lost more variables; it means the calculation excluded a class of location descriptions. Keep the default and filtered reports separate.
5. Compare two builds
Comparison needs two input files and requires matplotlib. Build a second variant, then pass both files to --compare:
$ clang-20 -g -O2 /tmp/locstats-demo.c -o /tmp/locstats-demo-O2
$ llvm-locstats --compare /tmp/locstats-demo /tmp/locstats-demo-O2
The command compares debug-location coverage and draws a plot showing the difference. A plot dependency failure is an environment problem, not evidence that the binaries have no DWARF. Install matplotlib only through your normal package or virtual-environment policy, then rerun the same command. The plot option writes an image as part of its operation; check the command's output directory before overwriting any existing result.
For one input file without a comparison, --draw-plot draws the bucket histogram:
$ llvm-locstats --draw-plot /tmp/locstats-demo
These plotting modes are optional. The text report is the better first artefact for logs and automated review.
6. Handle failures without guessing
The documented exit status is simple: zero means the input was parsed successfully, and one means it was not. Capture it immediately when scripting:
if llvm-locstats "$binary" >"$report"; then
printf 'location report written to %s\n' "$report"
else
status=$?
printf 'llvm-locstats could not parse %s (status %s)\n' "$binary" "$status" >&2
exit "$status"
fi
There is no documented repair flag for a malformed or unsupported input. Check that $binary names the intended file, that it is readable, and that it contains the debug information your build was meant to preserve. Use llvm-dwarfdump-20 --debug-info "$binary" as a read-only inspection. Do not use sudo for an ordinary local object, and do not delete or rewrite a build artefact while diagnosing it.
Done means
- The executable availability check passed, rather than relying on the manpage alone.
- The input binary was built or selected with DWARF information and its DIEs were inspected.
- The unfiltered histogram was saved with the compiler, optimisation level and LLVM version.
- Any variable, parameter or entry-value filter is recorded with its report.
- A comparison uses two explicit binaries and plotting is treated as optional.
- A non-zero exit status is reported as a parse failure, not silently interpreted as poor coverage.