This document explains the overall design approach to the trace-based code coverage tool.
The primary motivation for this code coverage tool is driven by the fact that there are no commercial off-the-shelf (COTS) tools that can be readily used for doing code coverage measurement for firmware components - especially those meant for memory constraint platforms. Most of the tools rely on the traditional approach where the code is instrumented to enable the coverage measurement. In the case of firmware components designed for memory constraint platforms, code size is a key consideration and the need to change memory maps to accomodate the instrumented code for enabling coverage measurement is seen as a pain point. A possible alternative is to perform the coverage measurement on emulation platforms which could free up the constraints of memory limitations. However this adds the need to have more platform specific code to be supported in the firmware for the emulation platform.
The above factors led to a design approach to measure the code coverage based on execution trace, without the need for any code instrumentation. This approach provides the following benefits:
The following limitations are understood to exist with the trace-based coverage tool
sparse
in nature, especially when the generated code is optimised for size. Ideally this solution works best when there is no compiler optimisation turned ON.The following diagram outlines the individual components involved in the trace-based coverage tool.
The following changes are needed at each of the stages to enable this code coverage measurement tool to work.
The coverage tool relies on the DWARF signatures embedded within the binaries generated for the firmware that runs as part of the coverage run. In case of GCC toolchain we enable it by adding -g flag during the compilation.
The -g flag generates DWARF signatures embedded within the binaries as see in the example below:
100005b0 <tfm_plat_get_rotpk_hash>: tfm_plat_get_rotpk_hash(): /workspace/workspace/tf-m-build-config/trusted-firmware-m/platform/ext/common/template/crypto_keys.c:173 100005b0: b510 push {r4, lr} /workspace/workspace/tf-m-build-config/trusted-firmware-m/platform/ext/common/template/crypto_keys.c:174 100005b2: 6814 ldr r4, [r2, #0]
The coverage tool relies on the generation of the execution trace from the target platform (in our case FVP). It relies on the coverage trace plugin which is an MTI based custom plugin that registers for trace source type INST
and dumps a filtered set of instruction data that got executed during the coverage run. In case of silicon platforms it expects to use trace capture with tools like DSTREAM-ST.
See Coverage Plugin documentation to know more about the use of this custom plugin.
The following diagram shows an example trace capture output from the coverage trace plugin:
[PC address, times executed, opcode size] 0010065c 1 4 00100660 1 4 00100664 1 2 00100666 1 2 ...
The exception level tracing is implemented by registering a callback which will be called for each exception level change. Each time the exception level has changed, we check if the new exception level is enabled in the trace-mode. When it is enabled we register the inst trace callback. If the exception level is not enabled we disable the callback. This will result in instruction only being recorded for the enabled exception levels.
In this stage coverage information is generated by: