1
0
Fork 0
forked from nuttx/nuttx-update

libs: add gcov framework support

In devices without storage media, you can export data to the
command line and then generate the corresponding gcda file

It can save the result output by calling __gcov_info_to_gcda
The usage is similar to:
https://gcc.gnu.org/onlinedocs/gcc/Freestanding-Environments.html#Profiling-and-Test-Coverage-in-Freestanding-Environments

Usage:
 ./tools/configure.sh qemu-armv7a:nsh
Modify the configuration
+CONFIG_COVERAGE_ALL=y
+CONFIG_COVERAGE_MINI=y
+CONFIG_SYSTEM_GCOV=y
Run:
qemu-system-arm -cpu cortex-a7 -nographic -smp 4 \
     -machine virt,virtualization=off,gic-version=2 \
     -net none -chardev stdio,id=con,mux=on -serial chardev:con \
     -mon chardev=con,mode=readline -kernel ./nuttx/nuttx -semihosting -s | tee gcov.txt
./nuttx/tools/gcov_convert.py -i ./gcov.txt
./nuttx/tools/gcov.sh -t arm-none-eabi-gcov

Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
This commit is contained in:
yinshengkai 2024-11-18 22:21:53 +08:00 committed by Xiang Xiao
parent 0194c2f88a
commit 0413d74f31
7 changed files with 708 additions and 7 deletions

169
include/gcov.h Normal file
View file

@ -0,0 +1,169 @@
/****************************************************************************
* include/gcov.h
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.
*
****************************************************************************/
#ifndef __INCLUDE_GCOV_H
#define __INCLUDE_GCOV_H
/****************************************************************************
* Included Files
****************************************************************************/
#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
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Name: __gcov_reset
*
* Description:
* Set all counters to zero.
*
****************************************************************************/
extern void __gcov_reset(void);
/****************************************************************************
* Name: __gcov_dump
*
* Description:
* Write profile information to a file.
*
****************************************************************************/
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 *,
unsigned int, 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);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __INCLUDE_GCOV_H */

View file

@ -130,7 +130,7 @@ if(CONFIG_COVERAGE_COMPILER_RT)
target_sources(rt.profile PRIVATE ${RT_PROFILE_SRCS})
elseif(CONFIG_COVERAGE_MINI)
elseif(CONFIG_COVERAGE_MINI AND CONFIG_ARCH_TOOLCHAIN_CLANG)
nuttx_add_system_library(rt.miniprofile)
target_compile_options(rt.miniprofile -fno-profile-instr-generate

View file

@ -106,7 +106,7 @@ CSRCS += $(wildcard compiler-rt/compiler-rt/lib/profile/*.c)
CPPSRCS += $(wildcard compiler-rt/compiler-rt/lib/profile/*.cpp)
CSRCS += InstrProfilingPlatform.c
else ifeq ($(CONFIG_COVERAGE_MINI),y)
else ifeq ($(CONFIG_COVERAGE_MINI)$(CONFIG_ARCH_TOOLCHAIN_CLANG),yy)
FLAGS += -fno-profile-instr-generate -fno-coverage-mapping

View file

@ -19,6 +19,11 @@
# ##############################################################################
if(CONFIG_PROFILE_MINI)
nuttx_add_system_library(libgprof)
target_sources(libgprof PRIVATE profile.c)
nuttx_add_system_library(libprofile)
target_sources(libprofile PRIVATE profile.c)
endif()
if(CONFIG_COVERAGE_MINI AND CONFIG_ARCH_TOOLCHAIN_GCC)
nuttx_add_system_library(libcoverage)
target_sources(libcoverage PRIVATE gcov.c)
endif()

View file

@ -19,10 +19,12 @@
############################################################################
ifeq ($(CONFIG_PROFILE_MINI),y)
CSRCS += profile.c
endif
ifeq ($(CONFIG_COVERAGE_MINI)$(CONFIG_ARCH_TOOLCHAIN_GCC),yy)
CSRCS += gcov.c
endif
DEPPATH += --dep-path libgcc
VPATH += :libgcc
endif

View file

@ -0,0 +1,431 @@
/****************************************************************************
* 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 <errno.h>
#include <gcov.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <nuttx/lib/lib.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#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))
#ifdef GCOV_12_FORMAT
# 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
****************************************************************************/
typedef unsigned int gcov_unsigned_t;
/* 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 */
};
/****************************************************************************
* 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 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_process_path(FAR char *prefix, int strip,
FAR char *path, FAR char *new_path,
size_t len)
{
FAR char *tokens[64];
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;
token = strtok(NULL, "/");
}
/* Split the path into directories and filename */
prefix_count = token_count;
token = strtok(path, "/");
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, 0644);
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;
}
/****************************************************************************
* Public Functions
****************************************************************************/
void __gcov_init(FAR struct gcov_info *info)
{
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_dump(void)
{
FAR struct gcov_info *info;
FAR const char *strip = getenv("GCOV_PREFIX_STRIP");
FAR const char *prefix = getenv("GCOV_PREFIX");
FAR char *prefix2 = strdup(prefix);
FAR char new_path[PATH_MAX];
int ret;
for (info = __gcov_info_start; info; info = info->next)
{
FAR char *filename;
filename = strdup(info->filename);
if (filename == NULL)
{
syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename);
continue;
}
/* Process the path, add the prefix and strip the leading directories */
strcpy(prefix2, prefix);
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 */
ret = gcov_write_file(new_path, info);
if (ret != 0)
{
syslog(LOG_ERR, "gcov write file failed! skip %s", new_path);
lib_free(filename);
continue;
}
lib_free(filename);
}
lib_free(prefix2);
}
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++;
}
}
}
}
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 *, unsigned int,
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);
}

94
tools/gcov_convert.py Executable file
View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
############################################################################
# tools/gcov_convert.py
#
# 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.
#
############################################################################
import argparse
import re
# Parse gcda data from stdout dump format to binary format
# The stdout dump format is:
# gcov start filename:<filename> size: <size>Byte
# <hex dump data>
# gcov end filename:<filename> checksum: <checksum>
#
# The hex dump data will be converted to binary and written to <filename>
# The checksum is calculated by summing all bytes and taking modulo 65536
def parse_gcda_data(input_file):
with open(input_file, "r") as file:
lines = file.read().strip().splitlines()
for line in lines:
if not line.startswith("gcov start"):
continue
match = re.search(r"filename:(.*?)\s+size:\s*(\d+)Byte", line)
if not match:
continue
hex_dump = ""
filename = match.group(1)
size = int(match.group(2))
# Read the hex dump until the end of the file
next_line_index = lines.index(line) + 1
while next_line_index < len(lines) and not lines[next_line_index].startswith(
"gcov end"
):
hex_dump += lines[next_line_index].strip()
next_line_index += 1
if size != len(hex_dump) // 2:
print(
f"Size mismatch for {filename}: expected {size} bytes, got {len(hex_dump) // 2} bytes"
)
checksum_match = (
re.search(r"checksum:\s*(0x[0-9a-fA-F]+)", lines[next_line_index])
if next_line_index < len(lines)
else None
)
if not checksum_match:
continue
checksum = int(checksum_match.group(1), 16)
calculated_checksum = sum(bytearray.fromhex(hex_dump)) % 65536
if calculated_checksum != checksum:
print(
f"Checksum mismatch for {filename}: expected {checksum}, got {calculated_checksum}"
)
continue
with open(filename, "wb") as fp:
fp.write(bytes.fromhex(hex_dump))
print(f"write {filename} success")
print(
"Execute the 'nuttx/tools/gcov.sh -t arm-none-eabi-gcov' command to view the coverage report"
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", required=True, help="Input dump data")
args = parser.parse_args()
parse_gcda_data(args.input)