tests: coverage: Add Gcov support.

This is a custom Gcov implementation. Taking excerpts from gcc
gcc libgcc/libgcov.h and gcc/gcov-io.h.
Ported to zephyr by Ramakrishna Pallala <ramakrishna.pallala@intel.com>
and Adithya Baglody <adithya.nagaraj.baglody@intel.com>

Signed-off-by: Adithya Baglody <adithya.nagaraj.baglody@intel.com>
This commit is contained in:
Adithya Baglody 2018-08-29 17:31:46 +05:30 committed by Anas Nashif
commit e223cfa9dd
6 changed files with 398 additions and 1 deletions

19
include/misc/gcov.h Normal file
View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_MISC_GCOV_H_
#define ZEPHYR_INCLUDE_MISC_GCOV_H_
#ifdef CONFIG_COVERAGE_GCOV
void gcov_coverage_dump(void);
void gcov_static_init(void);
#else
void gcov_coverage_dump(void) { }
void gcov_static_init(void) { }
#endif /* CONFIG_COVERAGE */
#endif /* ZEPHYR_INCLUDE_MISC_GCOV_H_ */

View file

@ -3,3 +3,4 @@ add_subdirectory_if_kconfig(ztest)
zephyr_include_directories_ifdef(CONFIG_TEST zephyr_include_directories_ifdef(CONFIG_TEST
$ENV{ZEPHYR_BASE}/tests/include $ENV{ZEPHYR_BASE}/tests/include
) )
add_subdirectory_ifdef(CONFIG_COVERAGE_GCOV coverage)

View file

@ -31,12 +31,25 @@ config TEST_EXTRA_STACKSIZE
config COVERAGE config COVERAGE
bool "Create coverage data" bool "Create coverage data"
depends on NATIVE_APPLICATION default n
help help
This option will build your application with the -coverage option This option will build your application with the -coverage option
which will generate data that can be used to create coverage reports. which will generate data that can be used to create coverage reports.
Currently this is fully supported only on the native POSIX port. Currently this is fully supported only on the native POSIX port.
if COVERAGE
config COVERAGE_GCOV
bool "Create Coverage data from hardware platform"
select NEWLIB_LIBC
depends on !NATIVE_APPLICATION
default y
help
This option will select the custom gcov library. The reports will
be available over serial. This serial dump can be passed to
gen_gcov_files.py which creates the required .gcda files. These
can be read by gcov utility. For more details see gcovr.com .
endif
config TEST_USERSPACE config TEST_USERSPACE
bool "Enable userspace if available" bool "Enable userspace if available"
depends on ARCH_HAS_USERSPACE depends on ARCH_HAS_USERSPACE

View file

@ -0,0 +1 @@
zephyr_sources_ifdef(CONFIG_COVERAGE_GCOV coverage.c)

269
tests/coverage/coverage.c Normal file
View file

@ -0,0 +1,269 @@
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include "coverage.h"
#ifdef CONFIG_X86
#define MALLOC_MAX_HEAP_SIZE 32768
#define MALLOC_MIN_BLOCK_SIZE 128
#else
#define MALLOC_MAX_HEAP_SIZE 16384
#define MALLOC_MIN_BLOCK_SIZE 64
#endif
K_MEM_POOL_DEFINE(gcov_heap_mem_pool,
MALLOC_MIN_BLOCK_SIZE,
MALLOC_MAX_HEAP_SIZE, 1, 4);
static struct gcov_info *gcov_info_head;
/**
* Is called by gcc-generated constructor code for each object file compiled
* with -fprofile-arcs.
*/
void __gcov_init(struct gcov_info *info)
{
info->next = gcov_info_head;
gcov_info_head = info;
}
void __gcov_merge_add(gcov_type *counters, unsigned int n_counters)
{
/* Unused. */
}
/**
* buff_write_u64 - Store 64 bit data on a buffer and return the size
*/
#define MASK_32BIT (0xffffffffUL)
static inline void buff_write_u64(void *buffer, size_t *off, uint64_t v)
{
*((u32_t *)((u8_t *)buffer + *off) + 0) = (u32_t)(v & MASK_32BIT);
*((u32_t *)((u8_t *)buffer + *off) + 1) = (u32_t)((v >> 32) &
MASK_32BIT);
*off = *off + sizeof(u64_t);
}
/**
* buff_write_u32 - Store 32 bit data on a buffer and return the size
*/
static inline void buff_write_u32(void *buffer, size_t *off, u32_t v)
{
*((u32_t *)((u8_t *)buffer + *off)) = v;
*off = *off + sizeof(u32_t);
}
size_t calculate_buff_size(struct gcov_info *info)
{
u32_t iter;
u32_t iter_1;
u32_t iter_2;
/* few Fixed values at the start version, stamp and magic number. */
u32_t size = sizeof(u32_t) * 3;
for (iter = 0; iter < info->n_functions; iter++) {
/* space for TAG_FUNCTION and FUNCTION_LENGTH
* ident
* lineno_checksum
* cfg_checksum
*/
size += (sizeof(u32_t) * 5);
for (iter_1 = 0; iter_1 < GCOV_COUNTERS; iter_1++) {
if (!info->merge[iter_1]) {
continue;
}
/* for function counter and number of values */
size += (sizeof(u32_t) * 2);
for (iter_2 = 0;
iter_2 < info->functions[iter]->ctrs->num;
iter_2++) {
/* Iter for values which is u64_t */
size += (sizeof(u64_t));
}
}
}
return size;
}
/**
* populate_buffer - convert from gcov data set (info) to
* .gcda file format.
* This buffer will now have info similar to a regular gcda
* format.
*/
size_t populate_buffer(u8_t *buffer, struct gcov_info *info)
{
struct gcov_fn_info *functions;
struct gcov_ctr_info *counters_per_func;
u32_t iter_functions;
u32_t iter_counts;
u32_t iter_counter_values;
size_t buffer_write_position = 0;
/* File header. */
buff_write_u32(buffer,
&buffer_write_position,
GCOV_DATA_MAGIC);
buff_write_u32(buffer,
&buffer_write_position,
info->version);
buff_write_u32(buffer,
&buffer_write_position,
info->stamp);
for (iter_functions = 0;
iter_functions < info->n_functions;
iter_functions++) {
functions = info->functions[iter_functions];
buff_write_u32(buffer,
&buffer_write_position,
GCOV_TAG_FUNCTION);
buff_write_u32(buffer,
&buffer_write_position,
GCOV_TAG_FUNCTION_LENGTH);
buff_write_u32(buffer,
&buffer_write_position,
functions->ident);
buff_write_u32(buffer,
&buffer_write_position,
functions->lineno_checksum);
buff_write_u32(buffer,
&buffer_write_position,
functions->cfg_checksum);
counters_per_func = functions->ctrs;
for (iter_counts = 0;
iter_counts < GCOV_COUNTERS;
iter_counts++) {
if (!info->merge[iter_counts]) {
continue;
}
buff_write_u32(buffer,
&buffer_write_position,
GCOV_TAG_FOR_COUNTER(iter_counts));
buff_write_u32(buffer,
&buffer_write_position,
counters_per_func->num * 2);
for (iter_counter_values = 0;
iter_counter_values < counters_per_func->num;
iter_counter_values++) {
buff_write_u64(buffer,
&buffer_write_position,
counters_per_func->\
values[iter_counter_values]);
}
counters_per_func++;
}
}
return buffer_write_position;
}
void dump_on_console(const char *filename, char *ptr, size_t len)
{
u32_t iter;
printk("\n%c", FILE_START_INDICATOR);
while (*filename != '\0') {
printk("%c", *filename++);
}
printk("%c", GCOV_DUMP_SEPARATOR);
/* Data dump */
for (iter = 0; iter < len; iter++) {
printk(" %02x", (u8_t)*ptr++);
}
}
/**
* Retrieves gcov coverage data and sends it over the given interface.
*/
void gcov_coverage_dump(void)
{
u8_t *buffer;
size_t size;
size_t written_size;
struct gcov_info *gcov_list = gcov_info_head;
k_sched_lock();
printk("\nGCOV_COVERAGE_DUMP_START");
while (gcov_list) {
size = calculate_buff_size(gcov_list);
buffer = (u8_t *) k_mem_pool_malloc(&gcov_heap_mem_pool, size);
if (!buffer) {
printk("No Mem available to continue dump\n");
goto coverage_dump_end;
}
written_size = populate_buffer(buffer, gcov_list);
if (written_size != size) {
printk("Write Error on buff\n");
goto coverage_dump_end;
}
dump_on_console(gcov_list->filename, buffer, size);
k_free(buffer);
gcov_list = gcov_list->next;
}
coverage_dump_end:
printk("\nGCOV_COVERAGE_DUMP_END\n");
k_sched_unlock();
return;
}
/* Initialize the gcov by calling the required constructors */
void gcov_static_init(void)
{
extern u32_t __init_array_start, __init_array_end;
u32_t func_pointer_start = (u32_t) &__init_array_start;
u32_t func_pointer_end = (u32_t) &__init_array_end;
while (func_pointer_start < func_pointer_end) {
void (**p)(void);
/* get function pointer */
p = (void (**)(void)) func_pointer_start;
(*p)();
func_pointer_start += sizeof(p);
}
}

94
tests/coverage/coverage.h Normal file
View file

@ -0,0 +1,94 @@
/* Copyright (C) 1996-2018 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/**
* This file has excerpts from gcc libgcc/libgcov.h and gcc/gcov-io.h.
* Which is governed by section 7 of additional permissions.
*/
#ifndef _COVERAGE_H_
#define _COVERAGE_H_
#define GCOV_COUNTERS 10U
typedef u64_t gcov_type;
#define GCOV_TAG_FUNCTION_LENGTH 3
#define GCOV_DATA_MAGIC (0x67636461)
#define GCOV_TAG_FUNCTION (0x01000000)
#define GCOV_TAG_COUNTER_BASE (0x01a10000)
#define GCOV_TAG_FOR_COUNTER(count) \
(GCOV_TAG_COUNTER_BASE + ((u32_t) (count) << 17))
#define FILE_START_INDICATOR '*'
#define GCOV_DUMP_SEPARATOR '<'
/**Information about counters for a single function
*
* This data is generated by gcc during compilation and doesn't change
* at run-time with the exception of the values array.
*/
struct gcov_ctr_info {
unsigned int num; /* number of counter values for this type */
gcov_type *values; /* array of counter values for this type */
};
/**
* Profiling meta data per function
*
* This data is generated by gcc during compilation and doesn't change
* at run-time.
*
* Information about a single function. This uses the trailing array
* idiom. The number of counters is determined from the merge pointer
* array in gcov_info. The key is used to detect which of a set of
* comdat functions was selected -- it points to the gcov_info object
* of the object file containing the selected comdat function.
*/
struct gcov_fn_info {
const struct gcov_info *key; /* comdat key */
unsigned int ident; /* unique ident of function */
unsigned int lineno_checksum; /* function lineo_checksum */
unsigned int cfg_checksum; /* function cfg checksum */
struct gcov_ctr_info ctrs[0]; /* instrumented counters */
};
/** Profiling data per object file
*
* This data is generated by gcc during compilation and doesn't change
* at run-time with the exception of the next pointer.
*/
struct gcov_info {
unsigned int version; /* Gcov version (same as GCC version) */
struct gcov_info *next; /* List head for a singly-linked list */
unsigned int stamp; /* Uniquifying time stamp */
const char *filename; /* Name of the associated gcda data file */
/* merge functions, null for unused*/
void (*merge[GCOV_COUNTERS])(gcov_type *, unsigned int);
unsigned int n_functions; /* number of instrumented functions */
struct gcov_fn_info **functions; /* function information */
};
#endif /* _COVERAGE_H_ */