Files
MSE-SoftwEng/pico-sensor/McuLib/src/McuCoverage.c
2025-05-06 13:07:01 +00:00

237 lines
6.8 KiB
C

/*!
* Copyright (c) 2024, Erich Styger
*
* SPDX-License-Identifier: BSD-3-Clause
*
* \brief Implementation of the McuCoverage module.
*/
#include "McuCoverage.h"
#if McuCoverage_CONFIG_IS_ENABLED
#include "McuLib.h"
#include <stdint.h>
#include <stdio.h>
#include "McuLog.h"
#if McuLib_CONFIG_SDK_USE_FREERTOS
#include "McuRTOS.h"
#endif
#include <gcov.h>
int McuCoverage_Check(void) {
FILE *file = NULL;
/*
* Creating a file without absolute path.
* With J-Link and MCUXpresso IDE 11.4.1, this file gets created in the IDE installation directory (C:\NXP\MCUXpressoIDE_11.4.1_6260\ide).
* Where the file gets created (current directory of the semihosting process on the host) really depends on the probe firmware and is non-standard.
* See as well:
* https://developer.arm.com/documentation/dui0058/d/semihosting/semihosting-swis/sys-open--0x01-?lang=en
*/
file = fopen ("gcov_text.txt", "w"); /* on RP2040, file is present in project root folder. If using external gdb server: in the current directory of the GDB server */
if (file!=NULL) {
fputs("hello world with file I/O\r\n", file);
fclose(file);
} else {
return 0; /* failed */
}
file = fopen ("c:\\tmp\\test.txt", "w");
if (file!=NULL) {
fputs("hello world with file I/O\r\n", file);
(void)fwrite("hello\r\n", sizeof("hello\r\n")-1, 1, file);
fclose(file);
return 1; /* ok */
}
return 0; /* failed */
}
void McuCoverage_Deinit(void) {
/* nothing needed */
}
/* call the coverage initializers if not done by startup code */
void McuCoverage_Init(void) {
#if McuCoverage_CONFIG_USE_FREESTANDING
/* In a freestanding environment, we use custom hooks to write data. The constructors/destructors are *not* used,
* instead we rely on the .gcov_info in the binary.
*/
#else
#if McuLib_CONFIG_CPU_IS_RPxxxx
/* constructor calls for coverage already done in C:\Raspy\pico\pico-sdk\src\rp2_common\pico_runtime/runtime.c */
#else
void (**p)(void);
extern uint32_t __init_array_start, __init_array_end; /* linker defined symbols, array of function pointers */
uint32_t beg = (uint32_t)&__init_array_start;
uint32_t end = (uint32_t)&__init_array_end;
while(beg<end) {
p = (void(**)(void))beg; /* get function pointer */
(*p)(); /* call constructor */
beg += sizeof(p); /* next pointer */
}
#endif
#endif
}
/* custom exit function */
void exit_(int i) {
for(;;) {}
}
/* see https://gcc.gnu.org/onlinedocs/gcc/Freestanding-Environments.html#Tutorial */
#if McuCoverage_CONFIG_USE_FREESTANDING
static McuCoverage_OutputCharFct_t OutputCharFct = NULL;
void McuCoverage_OutputString(const unsigned char *str) {
if (OutputCharFct==NULL) {
return;
}
while(*str!='\0') {
OutputCharFct(*str);
str++;
}
}
void McuCoverage_SetOuputCharCallback(McuCoverage_OutputCharFct_t callback) {
OutputCharFct = callback;
}
/* The start and end symbols are provided by the linker script. We use the
array notation to avoid issues with a potential small-data area. */
/* Use the following in the linker file:
.gcov_info:
{
PROVIDE (__gcov_info_start = .);
KEEP (*(.gcov_info))
PROVIDE (__gcov_info_end = .);
} > FLASH
Additionally, set the following compiler option:
-fprofile-info-section
See https://github.com/gcc-mirror/gcc/blob/master/libgcc/gcov.h
*/
extern const struct gcov_info *const __gcov_info_start[];
extern const struct gcov_info *const __gcov_info_end[];
static const unsigned char a = 'a';
/* each 8bit binary data value c gets encoded into two characters, each in rang 'a'-'p' (p is 'a'+16):
buf[0]: 'a' + LowNibble(c)
buf[1]: 'a' + HighNibble(c)
*/
static inline unsigned char *encode(unsigned char c, unsigned char buf[2]) {
buf[0] = a + c % 16;
buf[1] = a + (c / 16) % 16;
return buf;
}
#if 0
/* The application reads a character stream encoded by encode() from stdin,
decodes it, and writes the decoded characters to stdout. Characters other
than the 16 characters 'a' to 'p' are ignored. */
static int can_decode(unsigned char c) {
return (unsigned char)(c - a) < 16;
}
/* application which reads from stdin a previously encoded data stream and decodes it to stdout. */
void application_decode(void) {
int first = 1;
int i;
unsigned char c;
while ((i = fgetc(stdin)) != EOF) {
unsigned char x = (unsigned char)i;
if (can_decode (x)) {
if (first) {
c = x - a;
} else {
fputc (c + 16 * (x - a), stdout);
}
first = !first;
} else {
first = 1;
}
}
}
#endif
/* This function shall produce a reliable in order byte stream to transfer the
gcov information from the target to the host system. */
static void dump(const void *d, unsigned n, void *arg) {
(void)arg;
const unsigned char *c = d;
unsigned char buf[2];
for(unsigned int i = 0; i<n; i++) {
if (OutputCharFct!=NULL) {
encode(c[i], buf);
OutputCharFct(buf[0]);
OutputCharFct(buf[1]);
}
}
}
/* The filename is serialized to a gcfn data stream by the
__gcov_filename_to_gcfn() function. The gcfn data is used by the
"merge-stream" subcommand of the "gcov-tool" to figure out the filename
associated with the gcov information. */
static void filename(const char *f, void *arg) {
__gcov_filename_to_gcfn(f, dump, arg);
}
/* The __gcov_info_to_gcda() function may have to allocate memory under
certain conditions. Simply try it out if it is needed for your application
or not. */
static void *allocate(unsigned length, void *arg) {
(void)arg;
void *p;
#if McuLib_CONFIG_SDK_USE_FREERTOS
p = pvPortMalloc(length);
#else
p = malloc (length);
#endif
if (p==NULL) {
McuLog_fatal("malloc failed");
for(;;) {} /* error */
}
}
/* Dump the gcov information of all translation units. */
static void dump_gcov_info (void) {
const struct gcov_info *const *info = __gcov_info_start;
const struct gcov_info *const *end = __gcov_info_end;
__asm__ ("" : "+r" (info)); /* Obfuscate variable to prevent compiler optimizations. */
while (info != end) { /* iterate through all infos (or modules instrumented) */
void *arg = NULL;
__gcov_info_to_gcda(*info, filename, dump, allocate, arg); /* write record*/
if (OutputCharFct!=NULL) { /* each record is terminated with a newline character */
OutputCharFct('\n');
}
info++;
}
}
#endif /* McuCoverage_CONFIG_USE_FREESTANDING */
void McuCoverage_WriteFiles(void) {
#if McuCoverage_CONFIG_USE_FREESTANDING
dump_gcov_info();
#else
#if __GNUC__ < 11
// void __gcov_flush(void); /* internal gcov function to write data */
__gcov_flush(); /* __gcov_flush() has been removed in the libraries for GCC11 */
#else
// void __gcov_dump(void); /* from GCC11 on, __gcov_flush() has been replaced by __gcov_dump() */
__gcov_dump(); /* from GCC11 on, use __gcov_dump() */
#endif
#endif
}
#endif /* #if McuCoverage_CONFIG_IS_ENABLED */