Files
nuttx/libs/libbuiltin/libgcc/gcov.c
wangmingrong1 3648e5db3f gcov: Refactoring the implementation framework of gcov
All implementations of gcov are sunk to the kernel implementation
1. Support three dump modes: serial port output, single file output, standard output

Signed-off-by: wangmingrong1 <wangmingrong1@xiaomi.com>
2025-07-03 00:17:58 +08:00

565 lines
14 KiB
C

/****************************************************************************
* libs/libbuiltin/libgcc/gcov.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <gcov.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <nuttx/streams.h>
#include <nuttx/reboot_notifier.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* The GCOV 12 gcno/gcda format has slight change,
* Please refer to gcov-io.h in the GCC 12 for
* more details.
*/
#if __GNUC__ >= 12
# define GCOV_12_FORMAT
#endif
#ifdef GCOV_12_FORMAT
# define GCOV_UNIT_SIZE 4
#else
# define GCOV_UNIT_SIZE 1
#endif
#ifdef GCOV_12_FORMAT
# define GCOV_TAG_FUNCTION_LENGTH 12
#else
# define GCOV_TAG_FUNCTION_LENGTH 3
#endif
#if __GNUC__ >= 14
# define GCOV_COUNTERS 9u
#elif __GNUC__ >= 10
# define GCOV_COUNTERS 8u
#elif __GNUC__ >= 8
# define GCOV_COUNTERS 9u
#else
# define GCOV_COUNTERS 10u
#endif
#define GCOV_DATA_MAGIC (0x67636461)
#define GCOV_NOTE_MAGIC (0x67636e6f)
#define GCOV_FILENAME_MAGIC (0x6763666e)
#define GCOV_TAG_FUNCTION (0x01000000)
#define GCOV_TAG_COUNTER_BASE (0x01a10000)
#define GCOV_TAG_FOR_COUNTER(count) \
(GCOV_TAG_COUNTER_BASE + ((uint32_t)(count) << 17))
#define GCOV_PATH_MAX_TOKENS 64
/****************************************************************************
* Private Types
****************************************************************************/
typedef uint64_t gcov_type;
typedef unsigned int gcov_unsigned_t;
/** 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) */
FAR struct gcov_info *next; /* List head for a singly-linked list */
unsigned int stamp; /* Uniquifying time stamp */
#ifdef GCOV_12_FORMAT
unsigned int checksum; /* unique object checksum */
#endif
FAR const char *filename; /* Name of the associated gcda data file */
void (*merge[GCOV_COUNTERS])(FAR gcov_type *, unsigned int);
unsigned int n_functions; /* number of instrumented functions */
FAR struct gcov_fn_info **functions; /* function information */
};
/* Information about counters for a single function
*
* This data is generated by gcc during compilation and doesn't change
* at run-time.
*/
struct gcov_ctr_info
{
unsigned int num; /* Number of counter values for this type */
FAR 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.
*/
struct gcov_fn_info
{
FAR const struct gcov_info *key; /* Comdat key */
unsigned int ident; /* Unique ident of function */
unsigned int lineno_checksum; /* Function lineno checksum */
unsigned int cfg_checksum; /* Function cfg checksum */
struct gcov_ctr_info ctrs[1]; /* Instrumented counters */
};
struct dump_args
{
FAR CODE int (*mkdir)(FAR const char *);
FAR struct lib_outstream_s *stream;
FAR char *prefix;
FAR char *tokens[GCOV_PATH_MAX_TOKENS];
size_t token_cnt;
FAR char *path_buffer;
int strip;
};
typedef CODE int (*dump_fn_t)(FAR struct dump_args *,
FAR char *, FAR uint8_t *, size_t);
/****************************************************************************
* Public Data
****************************************************************************/
FAR struct gcov_info *__gcov_info_start;
FAR struct gcov_info *__gcov_info_end;
/****************************************************************************
* Private Functions
****************************************************************************/
static void dump_counter(FAR void *buffer, FAR size_t *off, uint64_t v)
{
if (buffer)
{
*(FAR uint64_t *)((FAR uint8_t *)buffer + *off) = v;
}
*off += sizeof(uint64_t);
}
static void dump_unsigned(FAR void *buffer, FAR size_t *off, uint32_t v)
{
if (buffer)
{
*(FAR uint32_t *)((FAR uint8_t *)buffer + *off) = v;
}
*off += sizeof(uint32_t);
}
static int get_path_tokens(FAR char *path,
FAR char **tokens, size_t max_tokens)
{
size_t token_count = 0;
FAR char *token;
token = strtok(path, "/");
while (token != NULL)
{
if (token_count >= max_tokens)
{
return -ENAMETOOLONG;
}
tokens[token_count++] = token;
token = strtok(NULL, "/");
}
tokens[token_count] = NULL;
return token_count;
}
static size_t gcov_convert(FAR uint8_t *buffer,
FAR const struct gcov_info *info)
{
FAR struct gcov_fn_info *gfi;
FAR struct gcov_ctr_info *gci;
uint32_t functions;
uint32_t counts;
uint32_t value;
size_t pos = 0;
/* File header. */
dump_unsigned(buffer, &pos, GCOV_DATA_MAGIC);
dump_unsigned(buffer, &pos, info->version);
dump_unsigned(buffer, &pos, info->stamp);
#ifdef GCOV_12_FORMAT
dump_unsigned(buffer, &pos, info->checksum);
#endif
/* Function headers. */
for (functions = 0; functions < info->n_functions; functions++)
{
gfi = info->functions[functions];
/* Function record. */
dump_unsigned(buffer, &pos, GCOV_TAG_FUNCTION);
dump_unsigned(buffer, &pos, GCOV_TAG_FUNCTION_LENGTH);
dump_unsigned(buffer, &pos, gfi->ident);
dump_unsigned(buffer, &pos, gfi->lineno_checksum);
dump_unsigned(buffer, &pos, gfi->cfg_checksum);
gci = gfi->ctrs;
for (counts = 0; counts < GCOV_COUNTERS; counts++)
{
if (!info->merge[counts])
{
continue;
}
/* Counter record. */
dump_unsigned(buffer, &pos, GCOV_TAG_FOR_COUNTER(counts));
dump_unsigned(buffer, &pos, gci->num * 2 * GCOV_UNIT_SIZE);
for (value = 0; value < gci->num; value++)
{
dump_counter(buffer, &pos, gci->values[value]);
}
gci++;
}
}
return pos;
}
static int gcov_mkdir(FAR const char *path)
{
if (access(path, F_OK) != 0)
{
return mkdir(path, 0777);
}
return OK;
}
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
static int gcov_reboot_notify(FAR struct notifier_block *nb,
unsigned long action, FAR void *data)
{
__gcov_dump();
return OK;
}
#endif
static int gcov_standard_dump(FAR struct dump_args *args,
FAR char *path, FAR uint8_t *data, size_t size)
{
int written;
int fd;
fd = _NX_OPEN(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0)
{
return -errno;
}
written = _NX_WRITE(fd, data, size);
if (written != size)
{
return -errno;
}
_NX_CLOSE(fd);
return OK;
}
static int gcov_onefile_dump(FAR struct dump_args *args,
FAR char *path,
FAR uint8_t *data, size_t size)
{
FAR struct lib_outstream_s *stream = args->stream;
struct lib_hexdumpstream_s hexstream;
uint16_t checksum = 0;
int i;
lib_hexdumpstream(&hexstream, stream);
for (i = 0; i < size; i++)
{
checksum += ((FAR const uint8_t *)data)[i];
}
lib_sprintf(stream,
"gcov start filename:%s size: %zuByte\n",
path, size);
lib_stream_puts(&hexstream, data, size);
lib_stream_flush(&hexstream);
lib_sprintf(stream,
"gcov end filename:%s checksum: %#0x\n",
path, checksum);
lib_stream_flush(stream);
return OK;
}
static int gcov_dump_foreach(dump_fn_t handler, FAR struct dump_args *args)
{
FAR struct gcov_info *info;
FAR char *filename;
FAR uint8_t *data;
int token_count;
int size;
int i;
for (info = __gcov_info_start; info; info = info->next)
{
memset(args->path_buffer, 0x00, PATH_MAX);
size = gcov_convert(NULL, info);
data = lib_malloc(size);
if (data == NULL)
{
syslog(LOG_ERR, "gcov alloc failed!");
return -ERROR;
}
gcov_convert(data, info);
filename = strdup(info->filename);
if (filename == NULL)
{
syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename);
lib_free(data);
return -ERROR;
}
token_count = args->token_cnt +
get_path_tokens(filename,
&args->tokens[args->token_cnt],
GCOV_PATH_MAX_TOKENS);
if (token_count < args->token_cnt)
{
syslog(LOG_ERR, "gcov get path tokens failed! skip %s",
info->filename);
goto exit;
}
for (i = 0; i < token_count - 1; i++)
{
strcat(args->path_buffer, "/");
strcat(args->path_buffer, args->tokens[i]);
if (args->mkdir)
{
args->mkdir(args->path_buffer);
}
}
strcat(args->path_buffer, "/");
strcat(args->path_buffer, args->tokens[token_count - 1]);
if (handler(args, args->path_buffer, data, size) < 0)
{
syslog(LOG_ERR, "gcov dump %s failed!\n", args->path_buffer);
goto exit;
}
lib_free(filename);
lib_free(data);
}
return OK;
exit:
lib_free(filename);
lib_free(data);
return -ERROR;
}
/****************************************************************************
* Public Functions
****************************************************************************/
void __gcov_init(FAR struct gcov_info *info)
{
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
static struct notifier_block nb;
#endif
char path[PATH_MAX] = CONFIG_COVERAGE_DEFAULT_PREFIX;
static int inited = 0;
struct tm *tm_info;
time_t cur;
if (!inited)
{
cur = time(NULL);
tm_info = localtime(&cur);
strftime(path + strlen(path),
PATH_MAX,
"/gcov_%Y%m%d_%H%M%S",
tm_info);
setenv("GCOV_PREFIX_STRIP", CONFIG_COVERAGE_DEFAULT_PREFIX_STRIP, 1);
setenv("GCOV_DUMP_ONEFILE", "1", 1);
setenv("GCOV_PREFIX", path, 1);
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
nb.notifier_call = gcov_reboot_notify;
register_reboot_notifier(&nb);
#endif
inited++;
}
info->next = __gcov_info_start;
__gcov_info_start = info;
}
void __gcov_merge_add(FAR gcov_type *counters, unsigned int n_counters)
{
}
void __gcov_exit(void)
{
}
void __gcov_execve(void)
{
}
void __gcov_execl(void)
{
}
void __gcov_execv(void)
{
}
pid_t __gcov_fork(void)
{
return fork();
}
void __gcov_dump(void)
{
int ret = -1;
bool onefile;
FAR char *prefix;
struct dump_args args =
{
0
};
prefix = getenv("GCOV_PREFIX");
if (prefix == NULL)
{
syslog(LOG_ERR, "No path prefix specified");
}
args.path_buffer = lib_get_tempbuffer(PATH_MAX);
if (args.path_buffer == NULL)
{
syslog(LOG_ERR, "gcov alloc failed!");
return;
}
onefile = strcmp(getenv("GCOV_DUMP_ONEFILE"), "1") == 0;
if (onefile)
{
struct lib_rawoutstream_s stream;
int fd = _NX_OPEN(prefix, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
syslog(LOG_ERR, "open %s failed! ret: %d\n", prefix, ret);
goto exit;
}
lib_rawoutstream(&stream, fd);
args.stream = &stream.common;
ret = gcov_dump_foreach(gcov_onefile_dump, &args);
_NX_CLOSE(fd);
}
else
{
args.mkdir = gcov_mkdir;
args.strip = atoi(getenv("GCOV_PREFIX_STRIP"));
args.token_cnt = get_path_tokens(prefix,
args.tokens,
GCOV_PATH_MAX_TOKENS);
ret = gcov_dump_foreach(gcov_standard_dump, &args);
}
exit:
if (ret < 0)
{
syslog(LOG_INFO, "Gcov dump failed\n");
}
else
{
syslog(LOG_INFO, "Gcov dump complete\n");
}
lib_put_tempbuffer(args.path_buffer);
}
void __gcov_reset(void)
{
FAR struct gcov_info *info;
FAR struct gcov_ctr_info *gci;
uint32_t functions;
uint32_t counts;
for (info = __gcov_info_start; info; info = info->next)
{
for (functions = 0; functions < info->n_functions; functions++)
{
gci = info->functions[functions]->ctrs;
for (counts = 0; counts < GCOV_COUNTERS; counts++)
{
if (!info->merge[counts])
{
continue;
}
memset(gci->values, 0, gci->num * sizeof(gcov_type));
gci++;
}
}
}
}