blob: 084aaec4f7c15c36381992db8e1d5d63142dd680 [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include "coverage.h"
K_HEAP_DEFINE(gcov_heap, CONFIG_COVERAGE_GCOV_HEAP_SIZE);
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. */
}
void __gcov_exit(void)
{
/* Unused. */
}
/**
* print_u8 - Print 8 bit of gcov data
*/
static inline void print_u8(uint8_t v)
{
printk("%02x", v);
}
/**
* print_u32 - Print 32 bit of gcov data
*/
static inline void print_u32(uint32_t v)
{
uint8_t *ptr = (uint8_t *)&v;
print_u8(*ptr);
print_u8(*(ptr+1));
print_u8(*(ptr+2));
print_u8(*(ptr+3));
}
/**
* write_u64 - Store 64 bit data on a buffer and return the size
*/
static inline void write_u64(void *buffer, size_t *off, uint64_t v)
{
if (buffer != NULL) {
memcpy((uint8_t *)buffer + *off, (uint8_t *)&v, sizeof(v));
} else {
print_u32(*((uint32_t *)&v));
print_u32(*(((uint32_t *)&v) + 1));
}
*off = *off + sizeof(uint64_t);
}
/**
* write_u32 - Store 32 bit data on a buffer and return the size
*/
static inline void write_u32(void *buffer, size_t *off, uint32_t v)
{
if (buffer != NULL) {
memcpy((uint8_t *)buffer + *off, (uint8_t *)&v, sizeof(v));
} else {
print_u32(v);
}
*off = *off + sizeof(uint32_t);
}
size_t gcov_calculate_buff_size(struct gcov_info *info)
{
uint32_t iter;
uint32_t iter_1;
uint32_t iter_2;
/* Few fixed values at the start: magic number,
* version, stamp, and checksum.
*/
#ifdef GCOV_12_FORMAT
uint32_t size = sizeof(uint32_t) * 4;
#else
uint32_t size = sizeof(uint32_t) * 3;
#endif
for (iter = 0U; iter < info->n_functions; iter++) {
/* space for TAG_FUNCTION and FUNCTION_LENGTH
* ident
* lineno_checksum
* cfg_checksum
*/
size += (sizeof(uint32_t) * 5);
for (iter_1 = 0U; iter_1 < GCOV_COUNTERS; iter_1++) {
if (!info->merge[iter_1]) {
continue;
}
/* for function counter and number of values */
size += (sizeof(uint32_t) * 2);
for (iter_2 = 0U;
iter_2 < info->functions[iter]->ctrs->num;
iter_2++) {
/* Iter for values which is uint64_t */
size += (sizeof(uint64_t));
}
}
}
return size;
}
/**
* gcov_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 gcov_populate_buffer(uint8_t *buffer, struct gcov_info *info)
{
struct gcov_fn_info *functions;
struct gcov_ctr_info *counters_per_func;
uint32_t iter_functions;
uint32_t iter_counts;
uint32_t iter_counter_values;
size_t buffer_write_position = 0;
/* File header. */
write_u32(buffer,
&buffer_write_position,
GCOV_DATA_MAGIC);
write_u32(buffer,
&buffer_write_position,
info->version);
write_u32(buffer,
&buffer_write_position,
info->stamp);
#ifdef GCOV_12_FORMAT
write_u32(buffer,
&buffer_write_position,
info->checksum);
#endif
for (iter_functions = 0U;
iter_functions < info->n_functions;
iter_functions++) {
functions = info->functions[iter_functions];
write_u32(buffer,
&buffer_write_position,
GCOV_TAG_FUNCTION);
write_u32(buffer,
&buffer_write_position,
GCOV_TAG_FUNCTION_LENGTH);
write_u32(buffer,
&buffer_write_position,
functions->ident);
write_u32(buffer,
&buffer_write_position,
functions->lineno_checksum);
write_u32(buffer,
&buffer_write_position,
functions->cfg_checksum);
counters_per_func = functions->ctrs;
for (iter_counts = 0U;
iter_counts < GCOV_COUNTERS;
iter_counts++) {
if (!info->merge[iter_counts]) {
continue;
}
write_u32(buffer,
&buffer_write_position,
GCOV_TAG_FOR_COUNTER(iter_counts));
#ifdef GCOV_12_FORMAT
/* GCOV 12 counts the length by bytes */
write_u32(buffer,
&buffer_write_position,
counters_per_func->num * 2U * 4);
#else
write_u32(buffer,
&buffer_write_position,
counters_per_func->num * 2U);
#endif
for (iter_counter_values = 0U;
iter_counter_values < counters_per_func->num;
iter_counter_values++) {
write_u64(buffer,
&buffer_write_position,
counters_per_func->values[iter_counter_values]);
}
counters_per_func++;
}
}
return buffer_write_position;
}
void gcov_reset_counts(struct gcov_info *info)
{
struct gcov_fn_info *functions;
struct gcov_ctr_info *counters_per_func;
uint32_t iter_functions;
uint32_t iter_counts;
uint32_t iter_counter_values;
for (iter_functions = 0U;
iter_functions < info->n_functions;
iter_functions++) {
functions = info->functions[iter_functions];
counters_per_func = functions->ctrs;
for (iter_counts = 0U;
iter_counts < GCOV_COUNTERS;
iter_counts++) {
for (iter_counter_values = 0U;
iter_counter_values < counters_per_func->num;
iter_counter_values++) {
counters_per_func->values[iter_counter_values] = 0;
}
}
}
}
void gcov_reset_all_counts(void)
{
struct gcov_info *gcov_list = NULL;
#ifdef CONFIG_MULTITHREADING
k_sched_lock();
#endif
gcov_list = gcov_get_list_head();
while (gcov_list) {
gcov_reset_counts(gcov_list);
gcov_list = gcov_list->next;
}
#ifdef CONFIG_MULTITHREADING
k_sched_unlock();
#endif
}
void dump_on_console_start(const char *filename)
{
printk("\n%c", FILE_START_INDICATOR);
while (*filename != '\0') {
printk("%c", *filename++);
}
printk("%c", GCOV_DUMP_SEPARATOR);
}
void dump_on_console_data(char *ptr, size_t len)
{
if (ptr != NULL) {
for (size_t iter = 0U; iter < len; iter++) {
print_u8((uint8_t)*ptr++);
}
}
}
/**
* Retrieves gcov coverage data and sends it over the given interface.
*/
void gcov_coverage_dump(void)
{
uint8_t *buffer;
size_t size;
size_t written_size;
struct gcov_info *gcov_list_first = gcov_info_head;
struct gcov_info *gcov_list = gcov_info_head;
if (!k_is_in_isr()) {
#ifdef CONFIG_MULTITHREADING
k_sched_lock();
#endif
}
printk("\nGCOV_COVERAGE_DUMP_START");
while (gcov_list) {
dump_on_console_start(gcov_list->filename);
size = gcov_calculate_buff_size(gcov_list);
buffer = k_heap_alloc(&gcov_heap, size, K_NO_WAIT);
if (CONFIG_COVERAGE_GCOV_HEAP_SIZE > 0 && !buffer) {
printk("No Mem available to continue dump\n");
goto coverage_dump_end;
}
written_size = gcov_populate_buffer(buffer, gcov_list);
if (written_size != size) {
printk("Write Error on buff\n");
goto coverage_dump_end;
}
dump_on_console_data(buffer, size);
k_heap_free(&gcov_heap, buffer);
gcov_list = gcov_list->next;
if (gcov_list_first == gcov_list) {
goto coverage_dump_end;
}
}
coverage_dump_end:
printk("\nGCOV_COVERAGE_DUMP_END\n");
if (!k_is_in_isr()) {
#ifdef CONFIG_MULTITHREADING
k_sched_unlock();
#endif
}
return;
}
struct gcov_info *gcov_get_list_head(void)
{
/* Locking someway before getting this is recommended. */
return gcov_info_head;
}
/* Initialize the gcov by calling the required constructors */
void gcov_static_init(void)
{
extern uintptr_t __init_array_start, __init_array_end;
uintptr_t func_pointer_start = (uintptr_t) &__init_array_start;
uintptr_t func_pointer_end = (uintptr_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);
}
}