From e223cfa9dd107d01991cfe282992b5dcce6094cc Mon Sep 17 00:00:00 2001 From: Adithya Baglody Date: Wed, 29 Aug 2018 17:31:46 +0530 Subject: [PATCH] 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 and Adithya Baglody Signed-off-by: Adithya Baglody --- include/misc/gcov.h | 19 +++ tests/CMakeLists.txt | 1 + tests/Kconfig | 15 +- tests/coverage/CMakeLists.txt | 1 + tests/coverage/coverage.c | 269 ++++++++++++++++++++++++++++++++++ tests/coverage/coverage.h | 94 ++++++++++++ 6 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 include/misc/gcov.h create mode 100644 tests/coverage/CMakeLists.txt create mode 100644 tests/coverage/coverage.c create mode 100644 tests/coverage/coverage.h diff --git a/include/misc/gcov.h b/include/misc/gcov.h new file mode 100644 index 00000000000..2288f8e7329 --- /dev/null +++ b/include/misc/gcov.h @@ -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_ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f2c562e829..5326a0df85c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory_if_kconfig(ztest) zephyr_include_directories_ifdef(CONFIG_TEST $ENV{ZEPHYR_BASE}/tests/include ) +add_subdirectory_ifdef(CONFIG_COVERAGE_GCOV coverage) diff --git a/tests/Kconfig b/tests/Kconfig index 61d45735bc5..28d8fa3a794 100644 --- a/tests/Kconfig +++ b/tests/Kconfig @@ -31,12 +31,25 @@ config TEST_EXTRA_STACKSIZE config COVERAGE bool "Create coverage data" - depends on NATIVE_APPLICATION + default n help This option will build your application with the -coverage option which will generate data that can be used to create coverage reports. 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 bool "Enable userspace if available" depends on ARCH_HAS_USERSPACE diff --git a/tests/coverage/CMakeLists.txt b/tests/coverage/CMakeLists.txt new file mode 100644 index 00000000000..4f593aec66a --- /dev/null +++ b/tests/coverage/CMakeLists.txt @@ -0,0 +1 @@ +zephyr_sources_ifdef(CONFIG_COVERAGE_GCOV coverage.c) diff --git a/tests/coverage/coverage.c b/tests/coverage/coverage.c new file mode 100644 index 00000000000..187963f8093 --- /dev/null +++ b/tests/coverage/coverage.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#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); + } +} diff --git a/tests/coverage/coverage.h b/tests/coverage/coverage.h new file mode 100644 index 00000000000..9c896a1ed49 --- /dev/null +++ b/tests/coverage/coverage.h @@ -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 + . */ + +/** + * 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_ */