-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add processing of code coverage for optee core #4192
base: master
Are you sure you want to change the base?
Conversation
This patch adds the flags to configure the use of gcov: - CFG_CORE_GCOV_SUPPORT: enable the coverage for the core - CFG_TA_GCOV_SUPPORT: enable the coverage for TAs If any of them is enabled, CFG_GCOV_SUPPORT is enabled These flags are exported to the devkit. Signed-off-by: Franck LENORMAND <[email protected]>
For imx 6 qnd 7 targets, the build fails because TEE_RAM_VA_SIZE is too small. This patch increase it from 512ko to 2 Mo when coverage is enabled. Signed-off-by: Franck LENORMAND <[email protected]>
The code coverage is a metric measuring the lines of code executed in a code base during execution. If the code coverage is generated after a test suite is ran, it will give the coverage of code tested by the test suite. It gives the possibility to identify the code which is not tested. The gcov code coverage is supported by different compiler: - GCC (gcc.gnu.org/onlinedocs/gcc/Gcov.html) - clang compiler (clang.llvm.org/docs/SourceBasedCodeCoverage.html) It allows to analyze which part of the code is executed. The interface with a specific implementation of gcov is defined usign the structure gcov_impl_t. The library implements the symbol required for gcov: - __gcov_init: called by each coverage unit - __gcov_merge_add - __gcov_exit The calls to these symbols are redirected to the implementation for the compiler used. The library role is to transform the coverage data in program memory to a valid gcov data file. For this it interacts with: - user/TA: Control (Reset or Dump) the coverage data - Function to store data: Send the data to it for storage in REE FS In order to retrieve the coverage (*.gcda) for a specific UC, it is recommended to do the following actions: - clear the coverage - perform the UC - save the coverage Note: The core and the TAs are linked against different instances of the libgcov. The libgcov of the core can be controlled by the gcov PTA. The TA instances are controlled calling the libgcov API. Signed-off-by: Franck LENORMAND <[email protected]>
The compiler GCC implements gcov code coverage and this patch adds the support to handle the structure in memory generated by the compiler. It also handles the transformation of the structure in memory into gcov data files. Signed-off-by: Franck LENORMAND <[email protected]>
The coverage data are generated during execution, in order to exploit it, the data can be stored to REE FS in clear text which is done by the writer. The writer receives the data to store and call the dedicated RPC to instruct the TEE client to store it in REE FS. Signed-off-by: Franck LENORMAND <[email protected]>
The PTA has two main purposes: - Control the code coverage of the core: - PTA_CMD_GCOV_GET_VERSION - PTA_CMD_GCOV_CORE_RESET - PTA_CMD_GCOV_CORE_DUMP_ALL - Write code coverage data from a TA (called by libgcov) - PTA_CMD_GCOV_DUMP Signed-off-by: Franck LENORMAND <[email protected]>
The code coverage is performed at runtime by keeping in memory structures holding the releveant data: In order to generate the code coverage, two steps are required: - Instruct the compiler to generate coverage information structure and functions to initialize them: flag --coverage. - Call the constructors added to initialize the coverage information and register them to the libgcov (modification of boot.c) When an object file (.o) is created with code coverage support, a note (.gcno) file is created with it. The note file contains informations used when creating the code coverage report. The support of code coverage in compiled code increases its size (~15%) and the compilation time (~25%). Signed-off-by: Franck LENORMAND <[email protected]>
This script ease the generation of code coverage report. A report is based on a codebase, note files (generated at compilation) and data files (generated at runtime). The report is generated using lcov and present the code coverage as a percentage of lines executed compared to the total number of lines in the codebase. The script will copy all the note and data files in a temporary directory allowing to reuse the generated artifact and checks that the note and data file are correctly organised (the files must be in the same directory). The script also allow to generate an html output using genhtml and to view it. Signed-off-by: Franck LENORMAND <[email protected]>
0b0e93e
to
08d97f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments for commit "compil: Fix link error TEE_RAM_VA_SIZE is too small"
Perhaps we could make a generic solution instead? This is bound to be needed for other platforms too.
# = BIT(CORE_MMU_PGDIR_SHIFT) | ||
# with LPAE disabled: #define CORE_MMU_PGDIR_SHIFT 20 | ||
# = BIT(20) | ||
# = 0x80000 (512 Ko) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That should be 0x100000
(1MB)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In effect, my bad
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments for commit "libgcov: Add library adding the gcov support"
It seems that some of the dynamic registration adds quite a bit of code just to check that stuff is properly registered.
/* | ||
* Macro to register the implementation of gcov | ||
*/ | ||
#define REGISTR_GCOV_IMPL(p_impl) const struct gcov_impl_t *g_gcov_impl = p_impl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need dynamic registration? Isn't this know when at compile time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This regitration of the gcov implementation is static.
The macro is used to set "extern const struct gcov_impl_t *g_gcov_impl;"
Is the dynamic registration you are speaking about is for "s_writer_fn" () using register_gcov_dump_writer (./lib/libgcov/gcov.c)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why all these function pointers if everything is static? It makes it harder to follow the code. If there's good reason it's fine.
return TEE_ERROR_BAD_PARAMETERS; | ||
} | ||
|
||
filepath_size = strlen(core) + strlen(desc) + strlen(filepath) + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer something based on snprintf()
for simplicity, perhaps like:
if (snprintf(buf, sizeof(buf), "core/%s/%s", desc, fname) >= sizeof(buf))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will use snprintf to simplify the code however not this exact line as I want to check I handle the NULL teminating character properly.
* Sends the coverage data to the writer | ||
* | ||
* @desc Description for storage, null terminated string | ||
* @filepath Name of the coverage file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's the name of the file wouldn't fname
be more appropriate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The filepath passed to the function is a patch corresponding to the absolute path of the source file used at compilation. I will replace the "Name" by "Path".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by absolute path? Is that the dirname()
part of the absolute file name?
uint32_t filepath_size = 0; | ||
|
||
if (!desc || !filepath || !cov_data) { | ||
EMSG("Wrong parameters"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DMSG()
, if we need this at all.
Perhaps a plain assert(desc && fname && cov_data);
instead of the if and everything would be more useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
|
||
filepath_size = strlen(core) + strlen(desc) + strlen(filepath) + 1; | ||
if (filepath_size >= COVERAGE_PATH_MAX) { | ||
EMSG("Not enough space to store new filepath"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DMSG()
, if we need this at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments for "libgcov: Add implementation of gcov for GCC"
#include "int_gcov.h" | ||
|
||
#define MAGIC_VALUE(c1, c2, c3, c4) \ | ||
(((uint32_t)c1 << 24) + ((uint32_t)c2 << 16) + ((uint32_t)c3 << 8) + \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a the SHIFT_U32()
for this, SHIFT_U32(c1, 24)
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for the pointer
}; | ||
|
||
static const uint32_t ctr_tags[LAST_CTR_SUPPORTED] = { | ||
[CTR_ARCS] = 0x01a10000, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does 0x01a10000
mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is the ID for the counter arcs used in gcov implementation in GCC. I will document the array.
}; | ||
|
||
/* | ||
* Structure create by gcc to hold code coverade data of an object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/create/created/
s/coverade/coverage/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx
/* Pointer to object containing the function */ | ||
const struct object_data *object; | ||
/* Identifier */ | ||
uint32_t id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint32_t
or unsigned int
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"unsigned int" is not defined strictly as a 32 bit value by the C spec. GCC is using a 32 bit value (ref get_gcov_type in gcc/coverage.c).
/* Number of counters of the type */ | ||
uint32_t nb_ctrs; | ||
/* Array of counters value */ | ||
uint64_t *ctrs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uint64_t
or unsigned long
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rechecked looking at gcc code for gcov, for arm, it is uint64_t however for other architectures, there could be some issues, I will look into it.
struct object_data *od = s_int_data.linked_list_objects; | ||
const struct func_data *fd = NULL; | ||
const struct ctr_data *cd = NULL; | ||
uint32_t cur_fn = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer unsigned int
, same at next line below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the advantage of "unsigned int"? I like uint32_t because it gives precisely the range of the variable and is shorter to write.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm open to define u_int
in <types_ext.h>
if it's too tedious to type unsigned int
.
When you use a fixed width integer you signal that this specific width is important, often because it's supposed to fit in a hardware defined register or some ABI. I'm not sure why exactly 32 bits should be important here. When using an unsigned int
it's often not the exact range of the type that's important, but we know that the capacity has to be large enough for what's needed though. If you suspect that a unsigned int
isn't large enough the next choice is often size_t
, or perhaps unsigned long
depending on the case.
sizeof(*cd->ctrs) * cd->nb_ctrs); | ||
} | ||
} | ||
} while ((od = od->next)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We try to avoid assignments inside expressions. Consider:
for (od = s_int_data.linked_list_objects; od; od = od->next) {
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, it is cleaner
* | ||
* From gcc/gcc/gcov-dump.c | ||
* | ||
* A FUNCTION block is generally foloowed by blocks for counters, 1 bloc per |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/foloowed/followed/
s/bloc /block /
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx
* counter | ||
* | ||
* struct gcda_file { | ||
* uint32_t magic; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gcov-dump.c seems to generally not use fixed width integers. unsigned int
is used instead of uint32_t
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of these values is to map as close to possible to the binary representation of the file.
* uint32_t cfg_checksum; | ||
* } gcda_functions; | ||
* { | ||
* uint64_t values; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that means implicit padding after values, but I can't see anything of that below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No padding, The block can either contains a "gcda_functions" or "gcda_counters" structure.
Is there some confusion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sizeof(*gcda_block)
is the same regardless if gcda_block->gcda_functions
or gcda_block->gcda_counters
is used.
/* | ||
* static Implementation of gcov to use | ||
*/ | ||
extern const struct gcov_impl_t *g_gcov_impl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please drop the g_
prefix.
* Define the functions an implementation shall provide to process the coverage | ||
* data | ||
*/ | ||
struct gcov_impl_t { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the _t
suffix for?
/* | ||
* static Implementation of dump function to use | ||
*/ | ||
extern gcov_dump_file_fn g_gcov_dump_fn; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please drop the g_
prefix.
* | ||
* @cov_unit_info The coverage data to initialize | ||
*/ | ||
void __gcov_init(void *cov_unit_info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does these function need to have a __
prefix?
* @ctrs List of counters to merge | ||
* @nb_ctrs Number of counters to merge | ||
*/ | ||
void __gcov_merge_add(void *ctrs, uint32_t nb_ctrs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use unsigned int
or size_t
instead of uint32_t
.
This pull request has been marked as a stale pull request because it has been open (more than) 30 days with no activity. Remove the stale label or add a comment, otherwise this pull request will automatically be closed in 5 days. Note, that you can always re-open a closed issue at any time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is still in progress.
Can we add an enhancement label to this P-R so that it does not audo close?
* [in] memref[0] description | ||
* [none] | ||
* [none] | ||
* [none] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer have this ABI description in lib/libutee/include/pta_gcov.h.
* @psess The Session | ||
* @cmd The command to execute | ||
* @ptypes The types of the parameters | ||
* @params The parameters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument description not needed, implicit since PTA format (pseudo_ta_register()
below)
* These functions are created automatically by the compiler. | ||
* Each constructor function will call __gcov_int() for their function. | ||
*/ | ||
#if defined(CFG_CORE_SANITIZE_KADDRESS) || defined(CFG_CORE_GCOV_SUPPORT) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could remove this #if
, add __aybe_unused attribute to the function
and in init_asan()
below:
static void init_asan(void)
{
if (IS_ENABLED(CFG_CORE_GCOV_SUPPORT))
init_run_constructors();
}
* [in] memref[1] filepath | ||
* [in] memref[2] code coverage data | ||
*/ | ||
#define OPTEE_RPC_CMD_GCOV 12 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indent with 2 tabs to align with above OPTEE_RPC_CMD_FTRACE
.
|
||
/* | ||
* Name of the TA | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can remove this inline comment
return pta_gcov_core_dump_all(ptypes, params); | ||
default: | ||
EMSG("Command %d not supported", cmd); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: return TEE_ERROR_BAD_PARAMETERS;
straight here?
uint32_t cov_data_size) | ||
{ | ||
TEE_Result res = TEE_SUCCESS; | ||
struct thread_param params[2] = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/{}/{ }/
@@ -0,0 +1,61 @@ | |||
/* SPDX-License-Identifier: BSD-2-Clause */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// SPDX-License-...
@@ -0,0 +1,81 @@ | |||
/* SPDX-License-Identifier: BSD-2-Clause */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// SPDX-...
@@ -0,0 +1,141 @@ | |||
/* SPDX-License-Identifier: BSD-2-Clause */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// SPDX-...
I have removed the stale label and added enhancement. |
Code coverage is the notion of knowing which part of the code is executed at runtime during a scenario or normal execution.
Code coverage is a useful tool with different purposes:
The code coverage processing is already available for user code or other projects like the Linux kernel using its implementation in different compiler like GCC or Clang.
It was not available in optee for 3 reasons:
This pull request adresses these issues in order to add code coverage analysis to optee.
The code coverage is added using 3 main components:
-- keep a list of the structure added by the compiler containing code coverage data
-- reset all coverage data
-- write all coverage data
-- Transform the code coverage structures into data to be written following gcov gcda format
Code coverage support should be enabled only when developping/debugging as it adds overhead at runtime.
It also increases the size of the binary generated (gcc 9.2.0):
The time to build is also increased by ~25%
Limitations:
Supporting PR:
ta_from_ta.txt.txt
core_from_ca.txt.txt
core_from_ta.txt.txt