Measure AArch64 Test Coverage with gcov 13
aarch64-linux-gnu-gcov-13 turns a coverage-instrumented AArch64 build into a line-by-line report of what your tests actually exercised. You will compile with GCC coverage instrumentation, run the binary to collect profile data, and read line, branch and function coverage out of the result.
The route
Jump straight to the step you need, or tick off Done means at the end.
The installed tool is GCC 13.3.0 from Ubuntu's gcc-13-aarch64-linux-gnu package. Allow about fifteen minutes for a local build, or longer if the instrumented program must run on a separate AArch64 machine. You need the cross compiler, the source tree, and a way to run the resulting AArch64 program. The report is only as useful as the test run that produced its data.
1. Check the installed toolchain
Start with ordinary, read-only checks. These do not need elevated privileges:
$ command -v aarch64-linux-gnu-gcc-13
/usr/bin/aarch64-linux-gnu-gcc-13
$ aarch64-linux-gnu-gcov-13 --version
gcov (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
The manpage describes this binary as GCC 13.3.0 and gives gcov the same coverage interface as the unversioned cross-tool alias. Keep the compiler and gcov family aligned: a report can be misleading when the data files come from a different build or an incompatible compiler setup.
Checkpoint
If either command is missing, stop here and install or enable the appropriate toolchain through your normal system-management process. Do not use sudo merely to run gcov on files you can already read.
2. Compile with coverage enabled
Use a separate build directory so that generated objects and coverage files do not overwrite an ordinary build. The --coverage option supplies the instrumentation gcov needs and the runtime support needed to write execution data:
$ mkdir -p build-coverage
$ aarch64-linux-gnu-gcc-13 --coverage -O0 -g \
-o build-coverage/example \
src/example.c
Replace src/example.c with your source file. GCC places a notes file such as build-coverage/example.gcno beside the object or executable data, depending on the build arrangement. The notes describe the instrumented control flow; the runtime later creates the matching .gcda file.
Tip
-O0 keeps source lines easier to interpret for coverage work. Optimisation can combine lines or inline functions, so the resulting counts can be correct while looking surprising. This is a measurement choice, not a requirement for production compilation.
Checkpoint
Confirm that the build produced coverage metadata before running anything:
$ find build-coverage -maxdepth 1 -type f \( -name '*.gcno' -o -name 'example' \) -printf '%f\n'
example
example.gcno
3. Run the instrumented program
An AArch64 executable normally needs to run on an AArch64 host, container or emulator. Run the exact binary built for the test, using the same writable build tree when possible:
$ ./build-coverage/example
example test completed
When the program runs on another machine, copy the generated .gcda file back beside the matching .gcno file. Preserve the source paths, or arrange the build directory so gcov can find the source. Do not copy a data file from an unrelated build into this directory.
Checkpoint
A data file should now exist beside the notes file:
$ find build-coverage -maxdepth 1 -type f -name '*.gcda' -printf '%f\n'
example.gcda
If no .gcda appears, the program may have terminated before its profiling data was saved, the directory may not be writable, or you may be checking a different build directory. Fix that before interpreting a zero-coverage report.
4. Generate the readable report
Run gcov from the directory containing the source and coverage data, or give the data location with -o. This example asks for branch summaries, function summaries and demangled C++ names where relevant:
$ cd build-coverage
$ aarch64-linux-gnu-gcov-13 -b -f -m -o . ../src/example.c
File '../src/example.c'
Lines executed:80.00% of 10
Branches executed:75.00% of 4
Creating '../src/example.c.gcov'
The exact percentages and path formatting depend on the source and test run. The important output is a .gcov listing plus the summary on standard output. Each source line in the listing carries an execution count: - marks a line that is not executable source, ##### marks code that was never reached, and an asterisk can mean that only some blocks on a line ran.
- Branch detail:
-badds branch frequencies and a branch summary. - Raw counts:
-cshows counts when they are more useful than percentages. - Per-function view:
-fadds a summary for each function. - Readable names: the short
-moption requests demangled names; without it, the manual says names are mangled by default.
Checkpoint
Inspect the report without editing it:
$ sed -n '1,120p' ../src/example.c.gcov
5. Handle an out-of-tree build
The most common distraction is asking gcov to find data in the wrong directory. -o accepts the directory containing the .gcno and .gcda files, or an object path that identifies them. From the project root, use:
$ aarch64-linux-gnu-gcov-13 -b -f -o build-coverage src/example.c
File 'src/example.c'
Creating 'src/example.c.gcov'
If several files share the same basename, the default output name can collide. --preserve-paths keeps path components in generated names, translating directory separators to hashes. --hash-filenames is an alternative when preserved names would exceed filesystem limits. Use these when the output file name matters to an automated report.
6. Keep repeated runs and old data under control
Coverage counts are cumulative. Running the program again without removing its .gcda file adds the new counts to the previous run. That is useful for a complete test suite, but it can hide which test supplied a count.
For a clean measurement, stop the program and remove only the matching generated data file, then run the test again:
$ rm -- build-coverage/example.gcda
$ ./build-coverage/example
$ aarch64-linux-gnu-gcov-13 -b -f -o build-coverage src/example.c
Warning
rm is destructive. Check the path and filename first, and keep a copy if the accumulated profile is valuable. Never use a broad wildcard such as rm -rf build-coverage in a directory that contains other work. If you remove the wrong generated data, the source and executable remain intact, but you cannot reconstruct those old counts without rerunning the tests that produced them.
For a long-running program, GCC's runtime can reset counters and dump them at a chosen point with the __gcov_reset and __gcov_dump facilities. That requires a source change and a rebuild; it is not a gcov command-line option. Treat it as an instrumentation design decision, particularly for services.
7. Read failures without guessing
- Missing .gcno: the source was not compiled with coverage, or gcov was pointed at the wrong object directory.
- Missing .gcda: the instrumented program did not complete its profile write, could not write its directory, or a different binary ran.
- Mismatched build: compare the executable, source, notes file and data file as one set before trusting the numbers.
Coverage is not a pass or fail judgement by itself. It tells you which instrumented code the selected tests exercised. A high line percentage can still miss a branch, error path or important input. Use -b when branch behaviour matters, and keep the test command, compiler flags and data directory recorded alongside the report.
Done means
- Compiled correctly: the binary was built with
--coverageand the intended optimisation level. - Data present: a matching
.gcnoexisted before the test run and a matching.gcdaexisted afterwards. - Report generated:
aarch64-linux-gnu-gcov-13found the source and data and wrote a.gcovreport. - Numbers understood: you checked the line and branch summaries, understood any uncovered paths, and kept or deliberately removed the generated data.