diff --git a/include/gcov.h b/include/gcov.h new file mode 100644 index 0000000000..e494fe1e50 --- /dev/null +++ b/include/gcov.h @@ -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 + +/**************************************************************************** + * 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 */ diff --git a/libs/libbuiltin/compiler-rt/CMakeLists.txt b/libs/libbuiltin/compiler-rt/CMakeLists.txt index 44725917b5..ab6c36763a 100644 --- a/libs/libbuiltin/compiler-rt/CMakeLists.txt +++ b/libs/libbuiltin/compiler-rt/CMakeLists.txt @@ -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 diff --git a/libs/libbuiltin/compiler-rt/Make.defs b/libs/libbuiltin/compiler-rt/Make.defs index 93faaf892b..c9fedfe74f 100644 --- a/libs/libbuiltin/compiler-rt/Make.defs +++ b/libs/libbuiltin/compiler-rt/Make.defs @@ -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 diff --git a/libs/libbuiltin/libgcc/CMakeLists.txt b/libs/libbuiltin/libgcc/CMakeLists.txt index ad5624c4f4..b944bc183d 100644 --- a/libs/libbuiltin/libgcc/CMakeLists.txt +++ b/libs/libbuiltin/libgcc/CMakeLists.txt @@ -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() diff --git a/libs/libbuiltin/libgcc/Make.defs b/libs/libbuiltin/libgcc/Make.defs index 6d1a926083..ec8a2101a9 100644 --- a/libs/libbuiltin/libgcc/Make.defs +++ b/libs/libbuiltin/libgcc/Make.defs @@ -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 diff --git a/libs/libbuiltin/libgcc/gcov.c b/libs/libbuiltin/libgcc/gcov.c new file mode 100644 index 0000000000..a98ae36cd8 --- /dev/null +++ b/libs/libbuiltin/libgcc/gcov.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include + +/**************************************************************************** + * 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); +} diff --git a/tools/gcov_convert.py b/tools/gcov_convert.py new file mode 100755 index 0000000000..7648784d85 --- /dev/null +++ b/tools/gcov_convert.py @@ -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: size: Byte +# +# gcov end filename: checksum: +# +# The hex dump data will be converted to binary and written to +# 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)