Home / Alt manpages / llvm-cov-18(1)

  • llvm-cov-18(1)
  • User command
  • linux

Measure C Coverage with LLVM 18's llvm-cov

You will build a small C program with LLVM's source-based coverage instrumentation, run it once, and produce a line and branch coverage report with llvm-cov-18. The examples use the installed LLVM package version 18.1.3. Allow about fifteen minutes if Clang is already installed. Everything below writes only into a temporary working directory.

The workflow has four moving parts: Clang puts coverage mapping into the executable, the program writes a raw profile, llvm-profdata-18 merges that profile, and llvm-cov-18 reads the executable plus merged profile. The executable contains the mapping; the profile contains what happened when it ran.

1. Check the installed tools

Run these read-only checks first:

$ command -v clang-18 llvm-profdata-18 llvm-cov-18
/usr/bin/clang-18
/usr/bin/llvm-profdata-18
/usr/bin/llvm-cov-18
$ llvm-cov-18 --version
Ubuntu LLVM version 18.1.3

The package is llvm-18, version 18.1.3-1ubuntu1, on the machine used for this guide. If your command has a different version, check its own help and manual page before copying the option details below.

Checkpoint: all three commands must resolve before you continue. This guide does not require sudo; compiling and collecting coverage should normally be an unprivileged operation.

2. Create a small instrumented program

Create a working directory and a source file. This example deliberately leaves one branch untested so the report has something useful to show:

$ work=/tmp/llvm-cov-demo
$ mkdir -p "$work/build"
$ editor "$work/main.c"
#include <stdio.h>

static int classify(int value) {
    if (value > 0)
        return 1;
    return 0;
}

int main(void) {
    puts(classify(1) ? "positive" : "not positive");
    return 0;
}

Replace editor with an editor available on your system. Do not paste shell commands into the C file. Confirm that the source exists before compiling:

$ test -s "$work/main.c" && echo 'source ready'
source ready

3. Compile with coverage mapping

Use -fprofile-instr-generate and -fcoverage-mapping. The first links the profile runtime and arranges for execution data; the second embeds the source mapping that llvm-cov needs:

$ clang-18 -fprofile-instr-generate -fcoverage-mapping \
    "$work/main.c" -o "$work/build/demo"
$ test -x "$work/build/demo" && echo 'instrumented executable ready'
instrumented executable ready

The executable is now the coverage-mapping input. Keep it beside the source it was built from. If you later move the source, use llvm-cov show's -path-equivalence=<from>,<to> option rather than guessing at paths.

4. Run the program and collect a raw profile

Run the instrumented executable with LLVM_PROFILE_FILE set. The value names the raw profile file; using a dedicated build directory keeps generated data away from the source:

$ (cd "$work/build" && LLVM_PROFILE_FILE=demo.profraw ./demo)
positive
$ test -s "$work/build/demo.profraw" && echo 'raw profile ready'
raw profile ready

The manual describes default.profraw as the usual default when you do not set the variable. Naming it explicitly makes the input to the next step obvious. Running the program again can add another execution profile, so remove old profile files first when you need a clean measurement. That deletion is destructive to the stored measurements, so archive or copy them if they matter:

$ rm -- "$work/build/demo.profraw"
$ (cd "$work/build" && LLVM_PROFILE_FILE=demo.profraw ./demo)

For repeatable scripts, replace rm with a fresh build directory or a deliberately unique profile name. Do not remove a profile simply because a report looks surprising.

5. Merge the profile for llvm-cov

Merge the raw profile into the indexed format used by llvm-cov-18:

$ llvm-profdata-18 merge -sparse \
    "$work/build/demo.profraw" -o "$work/build/demo.profdata"
$ test -s "$work/build/demo.profdata" && echo 'merged profile ready'
merged profile ready

-sparse keeps the indexed profile compact. The merge command is separate from llvm-cov, so a missing or malformed .profdata file is a profile-preparation problem rather than a source-reporting problem.

6. Read the summary report

Pass the instrumented executable and the merged profile to report:

$ llvm-cov-18 report "$work/build/demo" \
    -instr-profile="$work/build/demo.profdata"
Filename                 Regions    Missed Regions     Cover   Functions  Missed Functions  Executed       Lines      Missed Lines     Cover    Branches   Missed Branches     Cover
--------------------------------------------------------------------------------------------------------------------------------
.../main.c                      8                 2    75.00%           2                 0   100.00%           9                 1    88.89%           4                 2    50.00%

The exact path and table width vary. The useful result here is that both functions ran, but one line and two branch outcomes were not covered. The report includes regions, functions, lines and branches. A high function percentage can therefore hide an untested branch.

To include a per-function breakdown, add -show-functions. To omit files whose paths match a pattern, use -ignore-filename-regex=<PATTERN>. These options filter the report; they do not change the collected profile.

7. Inspect annotated source

Use show when you need to locate missed lines. Supplying the source path restricts the output to that file:

$ llvm-cov-18 show "$work/build/demo" \
    -instr-profile="$work/build/demo.profdata" "$work/main.c" | sed -n '1,18p'
    3|      1|static int classify(int value) {
    4|      1|    if (value > 0)
    5|      1|        return 1;
    6|      0|    return 0;
    7|      1|}
    9|      1|int main(void) {
   10|      1|    puts(classify(1) ? "positive" : "not positive");

The count column is execution data, not a quality judgement. A zero on line 6 tells you exactly which input or test case is missing. Add -show-branches=count or -show-branches=percent when branch detail is more useful than line counts.

8. Export data for another tool

export writes JSON by default with -format=text, or lcov trace data with -format=lcov. Redirect it to a new file so an existing report is not truncated accidentally:

$ llvm-cov-18 export "$work/build/demo" \
    -instr-profile="$work/build/demo.profdata" \
    -format=lcov > "$work/build/demo.info"
$ sed -n '1,10p' "$work/build/demo.info"
SF:/tmp/llvm-cov-demo/main.c
FN:9,main
FNDA:1,main
FNF:2
FNH:2
DA:3,1

Use -summary-only when the consumer needs file summaries without function and region detail. Treat exported coverage as build output: it can disclose source paths and test behaviour, so do not upload it to a third party without checking the destination and retention policy.

Common failure points

  • No profile file: check that the instrumented executable actually ran and that LLVM_PROFILE_FILE points to a writable directory.
  • Missing source or zero coverage: use the executable built with coverage mapping and the profile produced by that executable. Mixing builds makes the data misleading or unusable.
  • Unexpected old counts: look for an existing .profraw file or a profile path shared by multiple runs. Preserve it before cleaning up.
  • Report output is hard to read: use show for annotated source, report -show-functions for function totals, or export for a machine-readable hand-off.

Done means

  • The program was compiled with both coverage instrumentation flags.
  • A fresh raw profile was created by running the instrumented executable.
  • llvm-profdata-18 merge produced a readable .profdata file.
  • llvm-cov-18 report showed line and branch totals.
  • llvm-cov-18 show identified the untested source line.
  • Any exported report was written to a deliberate destination and checked for source-path disclosure.