mirror of
https://github.com/apache/nuttx.git
synced 2025-12-07 01:44:23 +08:00
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>
565 lines
14 KiB
C
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++;
|
|
}
|
|
}
|
|
}
|
|
}
|