Introducing source code instrumentation in GNATcoverage
by Pierre-Marie de Rodat –
This is the first post of a series about GNATcoverage and source code instrumentation.
In order to make GNATcoverage viable in more contexts, we planned several years ago to add instrumentation support in GNATcoverage for Ada sources. This feature reached maturation recently and is available in the last Continuous Release, so it is a good time to present it with a blog series!
GNATcoverage background
GNATcoverage is the tool developed by AdaCore to compute the code coverage of Ada/C programs, available on several platforms, both native and embedded. It is able to assess DO-178 criteria, up to MC/DC, on C and Ada programs, from Ada 95 to Ada 2012.
Its coverage analysis capabilities are versatile. Several output formats are available: “xcov”, which look like text coverage reports from GCC’s gcov tool, a set of static HTML pages, a single modern dynamic HTML page, a XML report for machine processing or a custom text report suitable for certification contexts. In addition, the tool features powerful consolidation capabilities, which allow combining the result of multiple executions into a single report in several fashions and let users specify which packages/sources are of actual relevance to an analysis.
The way it works so far is atypical for a code coverage tool working with programs compiled to machine code:
The source code is compiled unchanged to machine code (processor instructions), with a few special compiler options to ease the mapping of machine code back to source constructs and to generate a list of source constructs to cover: the SCOs (Source Coverage Obligations).
An “instrumented” execution environment runs the program and generates a “trace file”, which roughly contains the set of machine instructions executed. For native platforms, this execution environment could be Valgrind or DynamoRIO, while embedded programs would execute either in GNATemulator or on a physical board with a hardware probe attached.
GNATcoverage computes the coverage report from trace files, compiled programs, source files and the SCOs.
Unlike traditional code coverage tools, which generally inject code in the compiled program so that the program itself computes its coverage state, GNATcoverage works on unmodified programs. Instead, the execution environment builds the coverage state on behalf of the executed program.This has several advantages:
The main one is that executable code used for coverage analysis can also be used in production, or at least will be very close since the program itself is not modified to embed coverage measurement code and data structures.
It allows object coverage analysis (coverage of individual processor-level instructions), which is useful in source/object traceability studies.
So why do we need instrumentation?
Our original approach also comes with drawbacks. For instance, the execution environment is an emulator on native platforms (Valgrind, DynamoRIO) which incurs a non trivial performance penalty. For embedded targets, there is sometimes no possibility to use GNATemulator and no hardware probe available to create execution traces, or setting up such a probe can prove tricky enough to turn out impractical. Hence, depending on specific situations, instrumenting programs for code coverage can be a better fit than using unmodified programs.
One of the design goals for this instrumentation scheme was to be as close as possible to the original one. This facilitates transitions from one mode to the other, and makes most existing features, in particular in coverage analysis capabilities, applicable to both in a consistent manner.
The next post will present how the instrumentation scheme works in GNATcoverage with a simple example program.