Antithesis logomark
DOCS

C/C++ Instrumentation

Prerequisites

Instrumentation for C and C++ is packaged with the C++ SDK. Instrumentation is supported by the header file antithesis_instrumentation.h. This file must be included in one, and only one translation unit at link time. Coverage instrumentation requires Clang 13 or higher, and either C++ 11 or higher or C89 or higher.

We recommended that you include the instrumentation header in the file where you define the main function, as follows:

#include "antithesis_instrumentation.h"

Build Requirements

Your instrumented binary must be built with libraries and dependencies that it will use at runtime.

Add the following compile flag:

-fsanitize-coverage=trace-pc-guard,pc-table -g

And add the following link flag:

--build-id

If your build process includes a single compile/link step, then add both compile and link flags with the following:

-fsanitize-coverage=trace-pc-guard,pc-table -g -Wl,--build-id

If you build with -flto or -flto=thin, instrument at link time rather than at compile time. The -fsanitize-coverage compile flag inserts the coverage callbacks before link-time optimization runs, and the optimizer then inlines, merges, and tail-calls the instrumented code in ways Antithesis cannot symbolize.

Instead, let the linker’s optimization pipeline instrument the fully optimized program:

  1. Compile every translation unit with -flto (or -flto=thin) and -g, and without -fsanitize-coverage.
  2. Compile the one translation unit that includes antithesis_instrumentation.h without -flto. Keep that translation unit to just the include, so the rest of your program still gets link-time optimization. The header’s runtime shim has to be a regular object file: it must stay uninstrumented, and it defines the coverage hooks that the link step’s instrumentation will call.
  3. Link with lld and these flags:
-fuse-ld=lld \
-Xlinker '--lto-newpm-passes=lto<O2>,sancov-module' \
-Wl,-mllvm,-sanitizer-coverage-level=3 \
-Wl,-mllvm,-sanitizer-coverage-trace-pc-guard \
-Wl,-mllvm,-sanitizer-coverage-pc-table \
-Wl,--build-id

Match the optimization level in lto<O2> to your -O flag (lto<O3> for -O3, and so on). With -flto=thin, use thinlto<O2> in place of lto<O2>.

For a program with two translation units, the whole build looks like this:

clang++ -O2 -flto -g -c main.cpp -o main.o
clang++ -O2 -flto -g -c worker.cpp -o worker.o
clang++ -O2 -c antithesis_instrumentation.cpp -o antithesis_instrumentation.o
clang++ -O2 -flto -fuse-ld=lld main.o worker.o antithesis_instrumentation.o \
-Xlinker '--lto-newpm-passes=lto<O2>,sancov-module' \
-Wl,-mllvm,-sanitizer-coverage-level=3 \
-Wl,-mllvm,-sanitizer-coverage-trace-pc-guard \
-Wl,-mllvm,-sanitizer-coverage-pc-table \
-Wl,--build-id -o main

where antithesis_instrumentation.cpp contains only #include "antithesis_instrumentation.h".

To confirm that instrumentation ran after optimization, check that the coverage hook is only ever called, never jumped to:

$ llvm-objdump -d main | grep -c 'jmp.*__sanitizer_cov_trace_pc_guard'
0

Validation

To confirm that the instrumentation process was successful, you can run the command nm to list all the symbols in a binary, and then grep the string “antithesis_load_libvoidstar”.

$ nm client_binary | grep "antithesis_load_libvoidstar"
T antithesis_load_libvoidstar

Notice the “T” character in the previous code block: it means the symbol is in the text (code) section.

If this does not work you probably failed to include the header file.

Symbolization

The LLVM compiler infrastructure will output debug symbols in the DWARF format. Antithesis needs either the original unstripped binary, or the binary together with its debug info (as produced by objcopy --only-keep-debug and --add-gnu-debuglink). Symlink (or move) these files into a directory named /symbols in the root of the appropriate container image.

Antithesis labels the coverage it reports for each binary with the binary’s file name.

Help

Contact us if you need help with this.