mirror of
https://github.com/apache/nuttx.git
synced 2025-12-07 10:03:38 +08:00
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>
This commit is contained in:
committed by
Xiang Xiao
parent
1cb5077bc1
commit
3648e5db3f
114
include/gcov.h
114
include/gcov.h
@@ -29,63 +29,6 @@
|
|||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.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
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Public Types
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
struct gcov_fn_info;
|
|
||||||
typedef uint64_t gcov_type;
|
|
||||||
|
|
||||||
/** 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 */
|
|
||||||
};
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Public Data
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
extern FAR struct gcov_info *__gcov_info_start;
|
|
||||||
extern FAR struct gcov_info *__gcov_info_end;
|
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Public Function Prototypes
|
* Public Function Prototypes
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@@ -118,63 +61,6 @@ extern void __gcov_reset(void);
|
|||||||
|
|
||||||
extern void __gcov_dump(void);
|
extern void __gcov_dump(void);
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Name: __gcov_info_to_gcda
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Convert the gcov information referenced by INFO to a gcda data stream.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* info - Pointer to the gcov information.
|
|
||||||
* filename - Callback function to get the filename.
|
|
||||||
* dump - Callback function to write the gcda data.
|
|
||||||
* allocate - Callback function to allocate memory.
|
|
||||||
* arg - User-provided argument.
|
|
||||||
*
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
extern void __gcov_info_to_gcda(FAR const struct gcov_info *info,
|
|
||||||
FAR void (*filename)(FAR const char *,
|
|
||||||
FAR void *),
|
|
||||||
FAR void (*dump)(FAR const void *,
|
|
||||||
size_t, FAR void *),
|
|
||||||
FAR void *(*allocate)(unsigned int,
|
|
||||||
FAR void *),
|
|
||||||
FAR void *arg);
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Name: __gcov_filename_to_gcfn
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Convert the filename to a gcfn data stream.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* filename - Pointer to the filename.
|
|
||||||
* dump - Callback function to write the gcfn data.
|
|
||||||
* arg - User-provided argument.
|
|
||||||
*
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
extern void __gcov_filename_to_gcfn(FAR const char *filename,
|
|
||||||
FAR void (*dump)(FAR const void *,
|
|
||||||
unsigned int,
|
|
||||||
FAR void *),
|
|
||||||
FAR void *arg);
|
|
||||||
|
|
||||||
/****************************************************************************
|
|
||||||
* Name: __gcov_dump_to_memory
|
|
||||||
*
|
|
||||||
* Description:
|
|
||||||
* Dump gcov data directly to memory
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* ptr - Memory Address
|
|
||||||
* size - Memory block size
|
|
||||||
*
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
size_t __gcov_dump_to_memory(FAR void *ptr, size_t size);
|
|
||||||
|
|
||||||
#undef EXTERN
|
#undef EXTERN
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,16 +27,47 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
#include <nuttx/lib/lib.h>
|
#include <nuttx/streams.h>
|
||||||
#include <nuttx/reboot_notifier.h>
|
#include <nuttx/reboot_notifier.h>
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Pre-processor Definitions
|
* 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_DATA_MAGIC (0x67636461)
|
||||||
#define GCOV_NOTE_MAGIC (0x67636e6f)
|
#define GCOV_NOTE_MAGIC (0x67636e6f)
|
||||||
#define GCOV_FILENAME_MAGIC (0x6763666e)
|
#define GCOV_FILENAME_MAGIC (0x6763666e)
|
||||||
@@ -47,24 +78,35 @@
|
|||||||
#define GCOV_TAG_FOR_COUNTER(count) \
|
#define GCOV_TAG_FOR_COUNTER(count) \
|
||||||
(GCOV_TAG_COUNTER_BASE + ((uint32_t)(count) << 17))
|
(GCOV_TAG_COUNTER_BASE + ((uint32_t)(count) << 17))
|
||||||
|
|
||||||
#ifdef GCOV_12_FORMAT
|
#define GCOV_PATH_MAX_TOKENS 64
|
||||||
# define GCOV_TAG_FUNCTION_LENGTH 12
|
|
||||||
#else
|
|
||||||
# define GCOV_TAG_FUNCTION_LENGTH 3
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef GCOV_12_FORMAT
|
|
||||||
# define GCOV_UNIT_SIZE 4
|
|
||||||
#else
|
|
||||||
# define GCOV_UNIT_SIZE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Private Types
|
* Private Types
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
typedef uint64_t gcov_type;
|
||||||
typedef unsigned int gcov_unsigned_t;
|
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
|
/* Information about counters for a single function
|
||||||
*
|
*
|
||||||
* This data is generated by gcc during compilation and doesn't change
|
* This data is generated by gcc during compilation and doesn't change
|
||||||
@@ -92,6 +134,20 @@ struct gcov_fn_info
|
|||||||
struct gcov_ctr_info ctrs[1]; /* Instrumented counters */
|
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
|
* Public Data
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@@ -123,6 +179,28 @@ static void dump_unsigned(FAR void *buffer, FAR size_t *off, uint32_t v)
|
|||||||
*off += sizeof(uint32_t);
|
*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,
|
static size_t gcov_convert(FAR uint8_t *buffer,
|
||||||
FAR const struct gcov_info *info)
|
FAR const struct gcov_info *info)
|
||||||
{
|
{
|
||||||
@@ -182,120 +260,14 @@ static size_t gcov_convert(FAR uint8_t *buffer,
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gcov_process_path(FAR char *prefix, int strip,
|
static int gcov_mkdir(FAR const char *path)
|
||||||
FAR char *path, FAR char *new_path,
|
|
||||||
size_t len)
|
|
||||||
{
|
{
|
||||||
FAR char *tokens[64];
|
if (access(path, F_OK) != 0)
|
||||||
FAR char *filename;
|
|
||||||
FAR char *token;
|
|
||||||
int token_count = 0;
|
|
||||||
int prefix_count;
|
|
||||||
int level = 0;
|
|
||||||
int ret;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
token = strtok(prefix, "/");
|
|
||||||
while (token != NULL)
|
|
||||||
{
|
{
|
||||||
tokens[token_count++] = token;
|
return mkdir(path, 0777);
|
||||||
token = strtok(NULL, "/");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Split the path into directories and filename */
|
return OK;
|
||||||
|
|
||||||
prefix_count = token_count;
|
|
||||||
token = strtok(path, "/");
|
|
||||||
if (token == NULL)
|
|
||||||
{
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (token != NULL)
|
|
||||||
{
|
|
||||||
filename = token;
|
|
||||||
if (level++ >= strip)
|
|
||||||
{
|
|
||||||
/* Skip the specified number of leading directories */
|
|
||||||
|
|
||||||
if (token_count >= sizeof(tokens) / sizeof(tokens[0]))
|
|
||||||
{
|
|
||||||
return -ENAMETOOLONG;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens[token_count++] = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
token = strtok(NULL, "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add the filename */
|
|
||||||
|
|
||||||
if (prefix_count == token_count)
|
|
||||||
{
|
|
||||||
tokens[token_count++] = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_path[0] = '\0';
|
|
||||||
tokens[token_count] = NULL;
|
|
||||||
|
|
||||||
/* Check and create directories */
|
|
||||||
|
|
||||||
for (i = 0; i < token_count - 1; i++)
|
|
||||||
{
|
|
||||||
strcat(new_path, "/");
|
|
||||||
strcat(new_path, tokens[i]);
|
|
||||||
if (access(new_path, F_OK) != 0)
|
|
||||||
{
|
|
||||||
ret = mkdir(new_path, 0777);
|
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strcat(new_path, "/");
|
|
||||||
strcat(new_path, filename);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int gcov_write_file(FAR const char *filename,
|
|
||||||
FAR const struct gcov_info *info)
|
|
||||||
{
|
|
||||||
FAR uint8_t *buffer;
|
|
||||||
size_t written;
|
|
||||||
int ret = OK;
|
|
||||||
size_t size;
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
fd = _NX_OPEN(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
|
||||||
if (fd < 0)
|
|
||||||
{
|
|
||||||
syslog(LOG_ERR, "open %s failed!", filename);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
size = gcov_convert(NULL, info);
|
|
||||||
buffer = lib_malloc(size);
|
|
||||||
if (buffer == NULL)
|
|
||||||
{
|
|
||||||
syslog(LOG_ERR, "gcov alloc failed!");
|
|
||||||
_NX_CLOSE(fd);
|
|
||||||
return -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
gcov_convert(buffer, info);
|
|
||||||
written = _NX_WRITE(fd, buffer, size);
|
|
||||||
if (written != size)
|
|
||||||
{
|
|
||||||
syslog(LOG_ERR, "gcov write file failed!");
|
|
||||||
ret = -errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
_NX_CLOSE(fd);
|
|
||||||
lib_free(buffer);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
|
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
|
||||||
@@ -303,10 +275,136 @@ static int gcov_reboot_notify(FAR struct notifier_block *nb,
|
|||||||
unsigned long action, FAR void *data)
|
unsigned long action, FAR void *data)
|
||||||
{
|
{
|
||||||
__gcov_dump();
|
__gcov_dump();
|
||||||
return 0;
|
return OK;
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
* Public Functions
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@@ -332,6 +430,7 @@ void __gcov_init(FAR struct gcov_info *info)
|
|||||||
tm_info);
|
tm_info);
|
||||||
|
|
||||||
setenv("GCOV_PREFIX_STRIP", CONFIG_COVERAGE_DEFAULT_PREFIX_STRIP, 1);
|
setenv("GCOV_PREFIX_STRIP", CONFIG_COVERAGE_DEFAULT_PREFIX_STRIP, 1);
|
||||||
|
setenv("GCOV_DUMP_ONEFILE", "1", 1);
|
||||||
setenv("GCOV_PREFIX", path, 1);
|
setenv("GCOV_PREFIX", path, 1);
|
||||||
|
|
||||||
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
|
#ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT
|
||||||
@@ -373,66 +472,68 @@ pid_t __gcov_fork(void)
|
|||||||
|
|
||||||
void __gcov_dump(void)
|
void __gcov_dump(void)
|
||||||
{
|
{
|
||||||
FAR struct gcov_info *info;
|
int ret = -1;
|
||||||
FAR const char *strip = getenv("GCOV_PREFIX_STRIP");
|
bool onefile;
|
||||||
FAR const char *prefix = getenv("GCOV_PREFIX");
|
FAR char *prefix;
|
||||||
FAR char *new_path;
|
struct dump_args args =
|
||||||
FAR char *prefix2;
|
{
|
||||||
int ret;
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
prefix = getenv("GCOV_PREFIX");
|
||||||
if (prefix == NULL)
|
if (prefix == NULL)
|
||||||
{
|
{
|
||||||
syslog(LOG_ERR, "No path prefix specified");
|
syslog(LOG_ERR, "No path prefix specified");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix2 = strdup(prefix);
|
args.path_buffer = lib_get_tempbuffer(PATH_MAX);
|
||||||
if (prefix2 == NULL)
|
if (args.path_buffer == NULL)
|
||||||
{
|
{
|
||||||
syslog(LOG_ERR, "gcov alloc failed!");
|
syslog(LOG_ERR, "gcov alloc failed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_path = lib_get_tempbuffer(PATH_MAX);
|
onefile = strcmp(getenv("GCOV_DUMP_ONEFILE"), "1") == 0;
|
||||||
for (info = __gcov_info_start; info; info = info->next)
|
if (onefile)
|
||||||
{
|
{
|
||||||
FAR char *filename;
|
struct lib_rawoutstream_s stream;
|
||||||
|
int fd = _NX_OPEN(prefix, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||||
filename = strdup(info->filename);
|
if (fd < 0)
|
||||||
if (filename == NULL)
|
|
||||||
{
|
{
|
||||||
syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename);
|
syslog(LOG_ERR, "open %s failed! ret: %d\n", prefix, ret);
|
||||||
continue;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Process the path, add the prefix and strip the leading directories */
|
lib_rawoutstream(&stream, fd);
|
||||||
|
args.stream = &stream.common;
|
||||||
|
|
||||||
strcpy(prefix2, prefix);
|
ret = gcov_dump_foreach(gcov_onefile_dump, &args);
|
||||||
ret = gcov_process_path(prefix2, atoi(strip), filename,
|
|
||||||
new_path, PATH_MAX);
|
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
syslog(LOG_ERR, "gcov process path failed! skip %s",
|
|
||||||
new_path);
|
|
||||||
lib_free(filename);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert the data and write to the file */
|
_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_write_file(new_path, info);
|
ret = gcov_dump_foreach(gcov_standard_dump, &args);
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
syslog(LOG_ERR, "gcov write file failed! skip %s", new_path);
|
|
||||||
lib_free(filename);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lib_free(filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lib_put_tempbuffer(new_path);
|
exit:
|
||||||
lib_free(prefix2);
|
|
||||||
|
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)
|
void __gcov_reset(void)
|
||||||
@@ -461,47 +562,3 @@ void __gcov_reset(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void __gcov_filename_to_gcfn(FAR const char *filename,
|
|
||||||
FAR void (*dump_fn)(FAR const void *,
|
|
||||||
unsigned, FAR void *),
|
|
||||||
FAR void *arg)
|
|
||||||
{
|
|
||||||
if (dump_fn)
|
|
||||||
{
|
|
||||||
size_t len = strlen(filename);
|
|
||||||
dump_fn(filename, len, arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void __gcov_info_to_gcda(FAR const struct gcov_info *info,
|
|
||||||
FAR void (*filename_fn)(FAR const char *,
|
|
||||||
FAR void *),
|
|
||||||
FAR void (*dump_fn)(FAR const void *, size_t,
|
|
||||||
FAR void *),
|
|
||||||
FAR void *(*allocate_fn)(unsigned int, FAR void *),
|
|
||||||
FAR void *arg)
|
|
||||||
{
|
|
||||||
FAR const char *name = info->filename;
|
|
||||||
FAR uint8_t *buffer;
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
filename_fn(name, arg);
|
|
||||||
|
|
||||||
/* Get the size of the buffer */
|
|
||||||
|
|
||||||
size = gcov_convert(NULL, info);
|
|
||||||
|
|
||||||
buffer = lib_malloc(size);
|
|
||||||
if (!buffer)
|
|
||||||
{
|
|
||||||
syslog(LOG_ERR, "gcov alloc failed!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert the data */
|
|
||||||
|
|
||||||
gcov_convert(buffer, info);
|
|
||||||
dump_fn(buffer, size, arg);
|
|
||||||
lib_free(buffer);
|
|
||||||
}
|
|
||||||
|
|||||||
271
tools/gcov.py
271
tools/gcov.py
@@ -30,6 +30,9 @@ def parse_gcda_data(path):
|
|||||||
with open(path, "r") as file:
|
with open(path, "r") as file:
|
||||||
lines = file.read().strip().splitlines()
|
lines = file.read().strip().splitlines()
|
||||||
|
|
||||||
|
gcda_path = path + "_covert"
|
||||||
|
os.makedirs(gcda_path, exist_ok=True)
|
||||||
|
|
||||||
started = False
|
started = False
|
||||||
filename = ""
|
filename = ""
|
||||||
output = ""
|
output = ""
|
||||||
@@ -47,70 +50,147 @@ def parse_gcda_data(path):
|
|||||||
if not started:
|
if not started:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line.startswith("gcov end"):
|
try:
|
||||||
started = False
|
if line.startswith("gcov end"):
|
||||||
if size != len(output) // 2:
|
started = False
|
||||||
print(
|
if size != len(output) // 2:
|
||||||
f"Size mismatch for {filename}: expected {size} bytes, got {len(output) // 2} bytes"
|
raise ValueError(
|
||||||
)
|
f"Size mismatch for {filename}: expected {size} bytes, got {len(output) // 2} bytes"
|
||||||
|
|
||||||
match = re.search(r"checksum:\s*(0x[0-9a-fA-F]+)", line)
|
|
||||||
if match:
|
|
||||||
checksum = int(match.group(1), 16)
|
|
||||||
output = bytearray.fromhex(output)
|
|
||||||
expected = sum(output) % 65536
|
|
||||||
if checksum != expected:
|
|
||||||
print(
|
|
||||||
f"Checksum mismatch for {filename}: expected {checksum}, got {expected}"
|
|
||||||
)
|
)
|
||||||
continue
|
|
||||||
|
|
||||||
with open(filename, "wb") as fp:
|
match = re.search(r"checksum:\s*(0x[0-9a-fA-F]+)", line)
|
||||||
fp.write(output)
|
if match:
|
||||||
print(f"write {filename} success")
|
checksum = int(match.group(1), 16)
|
||||||
output = ""
|
output = bytearray.fromhex(output)
|
||||||
else:
|
expected = sum(output) % 65536
|
||||||
output += line.strip()
|
if checksum != expected:
|
||||||
|
raise ValueError(
|
||||||
|
f"Checksum mismatch for {filename}: expected {checksum}, got {expected}"
|
||||||
|
)
|
||||||
|
|
||||||
|
outfile = os.path.join(gcda_path, "./" + filename)
|
||||||
|
os.makedirs(os.path.dirname(outfile), exist_ok=True)
|
||||||
|
|
||||||
|
with open(outfile, "wb") as fp:
|
||||||
|
fp.write(output)
|
||||||
|
print(f"write {outfile} success")
|
||||||
|
output = ""
|
||||||
|
else:
|
||||||
|
output += line.strip()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing {path}: {e}")
|
||||||
|
print(f"gcov start filename:{filename} size:{size}")
|
||||||
|
print(output)
|
||||||
|
print(f"gcov end filename:{filename} checksum:{checksum}")
|
||||||
|
|
||||||
|
return gcda_path
|
||||||
|
|
||||||
|
|
||||||
def correct_content_path(file, newpath):
|
def correct_content_path(file, shield: list, newpath):
|
||||||
with open(file, "r", encoding="utf-8") as f:
|
with open(file, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
pattern = r"SF:([^\s]*?)/nuttx/include/nuttx"
|
for i in shield:
|
||||||
matches = re.findall(pattern, content)
|
content = content.replace(i, "")
|
||||||
|
|
||||||
if matches:
|
new_content = content
|
||||||
new_content = content.replace(matches[0], newpath)
|
if newpath is not None:
|
||||||
|
pattern = r"SF:([^\s]*?)/nuttx/include/nuttx"
|
||||||
|
matches = re.findall(pattern, content)
|
||||||
|
|
||||||
with open(file, "w", encoding="utf-8") as f:
|
if matches:
|
||||||
f.write(new_content)
|
new_content = content.replace(matches[0], newpath)
|
||||||
|
|
||||||
|
with open(file, "w", encoding="utf-8") as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
|
||||||
def copy_file_endswith(endswith, source_dir, target_dir, skip_dir):
|
def copy_file_endswith(endswith, source, target):
|
||||||
print(f"Collect {endswith} files {source_dir} -> {target_dir}")
|
for root, dirs, files in os.walk(source, topdown=True):
|
||||||
|
if target in root:
|
||||||
if not os.path.exists(target_dir):
|
|
||||||
os.makedirs(target_dir)
|
|
||||||
|
|
||||||
for root, _, files in os.walk(source_dir):
|
|
||||||
if skip_dir in root:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.endswith(endswith):
|
if file.endswith(endswith):
|
||||||
source_file = os.path.join(root, file)
|
src_file = os.path.join(root, file)
|
||||||
target_file = os.path.join(target_dir, file)
|
dst_file = os.path.join(target, os.path.relpath(src_file, source))
|
||||||
shutil.copy2(source_file, target_file)
|
os.makedirs(os.path.dirname(dst_file), exist_ok=True)
|
||||||
|
shutil.copy2(src_file, dst_file)
|
||||||
|
|
||||||
|
|
||||||
|
def run_lcov(data_dir, gcov_tool):
|
||||||
|
output = data_dir + ".info"
|
||||||
|
# lcov collect coverage data to coverage.info
|
||||||
|
command = [
|
||||||
|
"lcov",
|
||||||
|
"-c",
|
||||||
|
"-o",
|
||||||
|
output,
|
||||||
|
"--rc",
|
||||||
|
"lcov_branch_coverage=1",
|
||||||
|
"--gcov-tool",
|
||||||
|
gcov_tool,
|
||||||
|
"--ignore-errors",
|
||||||
|
"gcov",
|
||||||
|
"--directory",
|
||||||
|
data_dir,
|
||||||
|
]
|
||||||
|
print(command)
|
||||||
|
subprocess.run(
|
||||||
|
command,
|
||||||
|
check=True,
|
||||||
|
stdout=sys.stdout,
|
||||||
|
stderr=sys.stdout,
|
||||||
|
)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def run_genhtml(info, report):
|
||||||
|
cmd = [
|
||||||
|
"genhtml",
|
||||||
|
"--branch-coverage",
|
||||||
|
"-o",
|
||||||
|
report,
|
||||||
|
"--ignore-errors",
|
||||||
|
"source",
|
||||||
|
info,
|
||||||
|
]
|
||||||
|
print(cmd)
|
||||||
|
subprocess.run(
|
||||||
|
cmd,
|
||||||
|
check=True,
|
||||||
|
stdout=sys.stdout,
|
||||||
|
stderr=sys.stdout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_merge(gcda_dir1, gcda_dir2, output, merge_tool):
|
||||||
|
command = [
|
||||||
|
merge_tool,
|
||||||
|
"merge",
|
||||||
|
gcda_dir1,
|
||||||
|
gcda_dir2,
|
||||||
|
"-o",
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
print(command)
|
||||||
|
subprocess.run(
|
||||||
|
command,
|
||||||
|
check=True,
|
||||||
|
stdout=sys.stdout,
|
||||||
|
stderr=sys.stdout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def arg_parser():
|
def arg_parser():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Code coverage generation tool.", add_help=False
|
description="Code coverage generation tool.", add_help=False
|
||||||
)
|
)
|
||||||
parser.add_argument("-i", "--input", help="Input dump data")
|
|
||||||
parser.add_argument("-t", dest="gcov_tool", help="Path to gcov tool")
|
parser.add_argument("-t", dest="gcov_tool", help="Path to gcov tool")
|
||||||
parser.add_argument("-b", dest="base_dir", help="Compile base directory")
|
parser.add_argument("-b", dest="base_dir", help="Compile base directory")
|
||||||
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
||||||
|
parser.add_argument("--delete", action="store_true", help="Delete gcda files")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s",
|
"-s",
|
||||||
dest="gcno_dir",
|
dest="gcno_dir",
|
||||||
@@ -124,7 +204,6 @@ def arg_parser():
|
|||||||
nargs="+",
|
nargs="+",
|
||||||
help="Directory containing gcda files",
|
help="Directory containing gcda files",
|
||||||
)
|
)
|
||||||
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-x",
|
"-x",
|
||||||
dest="only_copy",
|
dest="only_copy",
|
||||||
@@ -133,7 +212,7 @@ def arg_parser():
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-o",
|
"-o",
|
||||||
dest="gcov_dir",
|
dest="result_dir",
|
||||||
default="gcov",
|
default="gcov",
|
||||||
help="Directory to store gcov data and report",
|
help="Directory to store gcov data and report",
|
||||||
)
|
)
|
||||||
@@ -145,41 +224,19 @@ def main():
|
|||||||
args = arg_parser()
|
args = arg_parser()
|
||||||
|
|
||||||
root_dir = os.getcwd()
|
root_dir = os.getcwd()
|
||||||
gcov_dir = os.path.abspath(args.gcov_dir)
|
|
||||||
gcno_dir = os.path.abspath(args.gcno_dir)
|
gcno_dir = os.path.abspath(args.gcno_dir)
|
||||||
|
result_dir = os.path.abspath(args.result_dir)
|
||||||
|
|
||||||
os.makedirs(gcov_dir, exist_ok=True)
|
os.makedirs(result_dir, exist_ok=True)
|
||||||
|
merge_tool = args.gcov_tool + "-tool"
|
||||||
gcda_dir = []
|
data_dir = os.path.join(result_dir, "data")
|
||||||
for i in args.gcda_dir:
|
report_dir = os.path.join(result_dir, "report")
|
||||||
gcda_dir.append(os.path.abspath(i))
|
coverage_file = os.path.join(result_dir, "coverage.info")
|
||||||
|
|
||||||
coverage_file = os.path.join(gcov_dir, "coverage.info")
|
|
||||||
result_dir = os.path.join(gcov_dir, "result")
|
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
debug_file = os.path.join(gcov_dir, "debug.log")
|
debug_file = os.path.join(result_dir, "debug.log")
|
||||||
sys.stdout = open(debug_file, "w+")
|
sys.stdout = open(debug_file, "w+")
|
||||||
|
|
||||||
if args.input:
|
|
||||||
parse_gcda_data(os.path.join(root_dir, args.input))
|
|
||||||
|
|
||||||
gcov_data_dir = []
|
|
||||||
|
|
||||||
# Collect gcno, gcda files
|
|
||||||
for i in gcda_dir:
|
|
||||||
|
|
||||||
dir = os.path.join(gcov_dir + "/data", os.path.basename(i))
|
|
||||||
gcov_data_dir.append(dir)
|
|
||||||
os.makedirs(dir)
|
|
||||||
|
|
||||||
copy_file_endswith(".gcno", gcno_dir, dir, gcov_dir)
|
|
||||||
copy_file_endswith(".gcda", i, dir, gcov_dir)
|
|
||||||
|
|
||||||
# Only copy files
|
|
||||||
if args.only_copy:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
# lcov tool is required
|
# lcov tool is required
|
||||||
if shutil.which("lcov") is None:
|
if shutil.which("lcov") is None:
|
||||||
print(
|
print(
|
||||||
@@ -187,57 +244,35 @@ def main():
|
|||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
gcda_dirs = []
|
||||||
|
for i in args.gcda_dir:
|
||||||
|
if os.path.isfile(i):
|
||||||
|
gcda_dirs.append(parse_gcda_data(os.path.join(root_dir, i)))
|
||||||
|
if args.delete:
|
||||||
|
os.remove(i)
|
||||||
|
else:
|
||||||
|
gcda_dirs.append(os.path.abspath(i))
|
||||||
|
|
||||||
|
# Merge all gcda files
|
||||||
|
shutil.copytree(gcda_dirs[0], data_dir)
|
||||||
|
for gcda_dir in gcda_dirs[1:]:
|
||||||
|
run_merge(data_dir, gcda_dir, data_dir, merge_tool)
|
||||||
|
|
||||||
|
# Copy gcno files and run lcov generate coverage info file
|
||||||
|
copy_file_endswith(".gcno", gcno_dir, data_dir)
|
||||||
|
coverage_file = run_lcov(data_dir, args.gcov_tool)
|
||||||
|
|
||||||
|
# Only copy files
|
||||||
|
if args.only_copy:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
run_genhtml(coverage_file, report_dir)
|
||||||
# lcov collect coverage data to coverage_file
|
|
||||||
command = [
|
|
||||||
"lcov",
|
|
||||||
"-c",
|
|
||||||
"-o",
|
|
||||||
coverage_file,
|
|
||||||
"--rc",
|
|
||||||
"lcov_branch_coverage=1",
|
|
||||||
"--gcov-tool",
|
|
||||||
args.gcov_tool,
|
|
||||||
"--ignore-errors",
|
|
||||||
"gcov",
|
|
||||||
]
|
|
||||||
for i in gcov_data_dir:
|
|
||||||
command.append("-d")
|
|
||||||
command.append(i)
|
|
||||||
|
|
||||||
print(command)
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
command,
|
|
||||||
check=True,
|
|
||||||
stdout=sys.stdout,
|
|
||||||
stderr=sys.stdout,
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.base_dir:
|
|
||||||
correct_content_path(coverage_file, args.base_dir)
|
|
||||||
|
|
||||||
# genhtml generate coverage report
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
"genhtml",
|
|
||||||
"--branch-coverage",
|
|
||||||
"-o",
|
|
||||||
result_dir,
|
|
||||||
coverage_file,
|
|
||||||
"--ignore-errors",
|
|
||||||
"source",
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
stdout=sys.stdout,
|
|
||||||
stderr=sys.stdout,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"Copy the following link and open it in the browser to view the coverage report:"
|
"Copy the following link and open it in the browser to view the coverage report:"
|
||||||
)
|
)
|
||||||
print(f"file://{os.path.join(result_dir, 'index.html')}")
|
print(f"file://{os.path.join(report_dir, 'index.html')}")
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
print("Failed to generate coverage file.")
|
print("Failed to generate coverage file.")
|
||||||
|
|||||||
Reference in New Issue
Block a user