diff --git a/.github/linters/setup.cfg b/.github/linters/setup.cfg new file mode 100644 index 00000000000..6ab13e1e45b --- /dev/null +++ b/.github/linters/setup.cfg @@ -0,0 +1,10 @@ +[flake8] +ignore = W503,W605,E203,E704 +max-complexity=27 +max-line-length =125 +multi line_output=3 +show-source = True +statistics = True + +[isort] +profile = black diff --git a/boards/boardctl.c b/boards/boardctl.c index 9e652560d6c..4b882efc609 100644 --- a/boards/boardctl.c +++ b/boards/boardctl.c @@ -24,11 +24,12 @@ #include -#include #include +#include +#include #include #include -#include +#include #include #include diff --git a/include/gcov.h b/include/gcov.h new file mode 100644 index 00000000000..3c6251b82ce --- /dev/null +++ b/include/gcov.h @@ -0,0 +1,183 @@ +/**************************************************************************** + * 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 *, + 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 +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_GCOV_H */ diff --git a/libs/libbuiltin/Kconfig b/libs/libbuiltin/Kconfig index 4d8c581ce4f..bdcc0539e5b 100644 --- a/libs/libbuiltin/Kconfig +++ b/libs/libbuiltin/Kconfig @@ -65,6 +65,25 @@ config COVERAGE_NONE endchoice +config COVERAGE_GCOV_DUMP_REBOOT + bool "Dump gcov data on reboot" + default n + ---help--- + Dump gcov data on reboot, this will cause the gcov data to be + dumped to the default path when the system is rebooted. + +config COVERAGE_DEFAULT_PREFIX_STRIP + string "Default gcov dump path prefix strip" + default "99" + ---help--- + The default prefix strip of the gcov data + +config COVERAGE_DEFAULT_PREFIX + string "Default gcov dump path prefix" + default "/data" + ---help--- + The default prefix to store the gcov data + choice prompt "Builtin profile support" default PROFILE_NONE diff --git a/libs/libbuiltin/compiler-rt/CMakeLists.txt b/libs/libbuiltin/compiler-rt/CMakeLists.txt index 96731c6c82b..ab6c36763a2 100644 --- a/libs/libbuiltin/compiler-rt/CMakeLists.txt +++ b/libs/libbuiltin/compiler-rt/CMakeLists.txt @@ -52,77 +52,57 @@ if(CONFIG_LIB_COMPILER_RT) endif() - if(CONFIG_ARCH_ARM) - set(ARCH arm) - elseif(CONFIG_ARCH_RISCV) - set(ARCH riscv) - elseif(CONFIG_ARCH_X86_64) - set(ARCH x86_64) - elseif(CONFIG_ARCH_ARM64) - set(ARCH aarch64) - endif() - - list(APPEND INCDIR ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/include) - - if(CONFIG_BUILTIN_COMPILER_RT) - - nuttx_add_system_library(rt.buitlins) - - target_include_directories( - rt.buitlins PRIVATE ${INCDIR} - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins) +endif() - target_compile_options(rt.buitlins PRIVATE -Wno-undef -Wno-macro-redefined) +if(CONFIG_ARCH_ARM) + set(ARCH arm) +elseif(CONFIG_ARCH_RISCV) + set(ARCH riscv) +elseif(CONFIG_ARCH_X86_64) + set(ARCH x86_64) +elseif(CONFIG_ARCH_ARM64) + set(ARCH aarch64) +endif() - set(SRCSTMP) - set(RT_BUILTINS_SRCS) - file(GLOB RT_BUILTINS_SRCS - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/*.c) +list(APPEND INCDIR ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/include) - file(GLOB SRCSTMP - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/${ARCH}/*.S) - list(APPEND RT_BUILTINS_SRCS ${SRCSTMP}) +if(CONFIG_BUILTIN_COMPILER_RT) - file(GLOB SRCSTMP - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/${ARCH}/*.c) - list(APPEND RT_BUILTINS_SRCS ${SRCSTMP}) + nuttx_add_system_library(rt.buitlins) - if(NOT CONFIG_LIB_COMPILER_RT_HAS_BFLOAT16) - set(RT_BUILTINS_BFLOAT16_SRCS - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/truncdfbf2.c - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/truncsfbf2.c) - list(REMOVE_ITEM RT_BUILTINS_SRCS ${RT_BUILTINS_BFLOAT16_SRCS}) - endif() + target_include_directories( + rt.buitlins PRIVATE ${INCDIR} + ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins) - target_sources(rt.buitlins PRIVATE ${RT_BUILTINS_SRCS}) + target_compile_options(rt.buitlins PRIVATE -Wno-undef -Wno-macro-redefined) + set(SRCSTMP) + set(RT_BUILTINS_SRCS) + file(GLOB RT_BUILTINS_SRCS + ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/*.c) + + file(GLOB SRCSTMP + ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/${ARCH}/*.S) + list(APPEND RT_BUILTINS_SRCS ${SRCSTMP}) + + file(GLOB SRCSTMP + ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/${ARCH}/*.c) + list(APPEND RT_BUILTINS_SRCS ${SRCSTMP}) + + if(NOT CONFIG_LIB_COMPILER_RT_HAS_BFLOAT16) + set(RT_BUILTINS_BFLOAT16_SRCS + ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/truncdfbf2.c + ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/builtins/truncsfbf2.c) + list(REMOVE_ITEM RT_BUILTINS_SRCS ${RT_BUILTINS_BFLOAT16_SRCS}) endif() - if(CONFIG_COVERAGE_COMPILER_RT) - - nuttx_add_system_library(rt.profile) - - target_include_directories( - rt.profile PRIVATE ${INCDIR} - ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/profile) - - target_compile_options( - rt.profile PRIVATE -DCOMPILER_RT_HAS_UNAME -Wno-undef - -Wno-strict-prototypes -Wno-shadow) - - set(SRCSTMP) - set(RT_PROFILE_SRCS InstrProfilingPlatform.c) - - file(GLOB SRCSTMP ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/profile/*.c) - list(APPEND RT_PROFILE_SRCS ${SRCSTMP}) - - file(GLOB SRCSTMP ${CMAKE_CURRENT_LIST_DIR}/compiler-rt/lib/profile/*.cpp) - list(APPEND RT_PROFILE_SRCS ${SRCSTMP}) - - target_sources(rt.profile PRIVATE ${RT_PROFILE_SRCS}) - + if(NOT CONFIG_COVERAGE_NONE) + target_compile_options(rt.buitlins -fno-profile-instr-generate + -fno-coverage-mapping) endif() + target_sources(rt.buitlins PRIVATE ${RT_BUILTINS_SRCS}) + endif() if(CONFIG_COVERAGE_COMPILER_RT) @@ -150,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 93faaf892b1..c9fedfe74f6 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/compiler-rt/coverage.c b/libs/libbuiltin/compiler-rt/coverage.c index df85b49a0ed..ab24d5f7b2f 100644 --- a/libs/libbuiltin/compiler-rt/coverage.c +++ b/libs/libbuiltin/compiler-rt/coverage.c @@ -33,13 +33,13 @@ #include #include +#include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define INSTR_PROF_RAW_VERSION 8 -#define INSTR_PROF_RAW_VERSION_VAR __llvm_profile_raw_version #define INSTR_PROF_PROFILE_RUNTIME_VAR __llvm_profile_runtime /* Magic number to detect file format and endianness. @@ -98,12 +98,6 @@ typedef struct __llvm_profile_header enum value_kind value_kind_last; }__llvm_profile_header; -/**************************************************************************** - * Private Data - ****************************************************************************/ - -static uint64_t INSTR_PROF_RAW_VERSION_VAR = INSTR_PROF_RAW_VERSION; - /**************************************************************************** * Public Data ****************************************************************************/ @@ -138,7 +132,7 @@ static uint64_t __llvm_profile_get_magic(void) static uint64_t __llvm_profile_get_version(void) { - return __llvm_profile_raw_version; + return INSTR_PROF_RAW_VERSION; } static uint64_t __llvm_profile_get_num_counters(const char *begin, @@ -225,14 +219,10 @@ void __llvm_profile_register_names_function(void *names_start, * llvm-prof. See the clang profiling documentation for details. */ -void __llvm_profile_dump(const char *path) +size_t __llvm_profile_dump(FAR struct lib_outstream_s *stream) { - int fd; - int ret; - - /* Header: __llvm_profile_header from InstrProfData.inc */ - - const char *filename = path; + const char c = '\0'; + size_t size = 0; /* Calculate size of sections. */ @@ -266,61 +256,83 @@ void __llvm_profile_dump(const char *path) hdr.names_delta = (uintptr_t)names_begin; hdr.value_kind_last = IPVK_LAST; - fd = _NX_OPEN(filename, O_WRONLY | O_CREAT); - if (fd < 0) - { - _NX_SETERRNO(fd); - return; - } - - /* Header */ - - ret = _NX_WRITE(fd, &hdr, sizeof(hdr)); - if (ret != sizeof(hdr)) + size += sizeof(hdr); + if (sizeof(hdr) != stream->puts(stream, &hdr, sizeof(hdr))) { - _NX_SETERRNO(ret); goto exit; } - /* Data */ - - ret = _NX_WRITE(fd, data_begin, sizeof(__llvm_profile_data) * num_data); - if (ret != sizeof(__llvm_profile_data) * num_data) + size += sizeof(__llvm_profile_data) * num_data; + if (sizeof(__llvm_profile_data) * num_data != + stream->puts(stream, data_begin, + sizeof(__llvm_profile_data) * num_data)) { - _NX_SETERRNO(ret); goto exit; } - /* Counters */ - - ret = _NX_WRITE(fd, counters_begin, sizeof(uint64_t) * num_counters); - if (ret != sizeof(uint64_t) * num_counters) + size += sizeof(uint64_t) * num_counters; + if (sizeof(uint64_t) * num_counters != + stream->puts(stream, counters_begin, + sizeof(uint64_t) * num_counters)) { - _NX_SETERRNO(ret); goto exit; } - /* Names */ - - ret = _NX_WRITE(fd, names_begin, names_size); - if (ret != names_size) + size += names_size; + if (names_size != stream->puts(stream, names_begin, names_size)) { - _NX_SETERRNO(ret); goto exit; } - /* Padding */ - for (; padding_bytes_after_names != 0; --padding_bytes_after_names) { - ret = _NX_WRITE(fd, "\0", 1); - if (ret != 1) + size += 1; + if (1 != stream->puts(stream, &c, 1)) { - _NX_SETERRNO(ret); break; } } exit: + + return size; +} + +void __gcov_dump(void) +{ + struct lib_rawoutstream_s stream; + FAR char *path; + int fd; + + path = getenv("GCOV_PREFIX"); + if (!path) + { + return; + } + + fd = _NX_OPEN(path, O_WRONLY | O_CREAT); + if (fd < 0) + { + _NX_SETERRNO(fd); + return; + } + + lib_rawoutstream(&stream, fd); + + __llvm_profile_dump(&stream.common); + _NX_CLOSE(fd); } + +size_t __gcov_dump_to_memory(FAR void *ptr, size_t size) +{ + struct lib_memoutstream_s stream; + + lib_memoutstream(&stream, ptr, size); + + return __llvm_profile_dump(&stream.common); +} + +void __gcov_reset(void) +{ +} diff --git a/libs/libbuiltin/libgcc/CMakeLists.txt b/libs/libbuiltin/libgcc/CMakeLists.txt index ad5624c4f4b..52f3faa7ba8 100644 --- a/libs/libbuiltin/libgcc/CMakeLists.txt +++ b/libs/libbuiltin/libgcc/CMakeLists.txt @@ -19,6 +19,14 @@ # ############################################################################## 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_compile_options( + libcoverage PRIVATE -fno-profile-arcs -fno-test-coverage + -fno-stack-protector) + target_sources(libcoverage PRIVATE gcov.c) endif() diff --git a/libs/libbuiltin/libgcc/Make.defs b/libs/libbuiltin/libgcc/Make.defs index 6d1a9260831..0cdb47efa16 100644 --- a/libs/libbuiltin/libgcc/Make.defs +++ b/libs/libbuiltin/libgcc/Make.defs @@ -19,10 +19,19 @@ ############################################################################ ifeq ($(CONFIG_PROFILE_MINI),y) - CSRCS += profile.c +endif -DEPPATH += --dep-path libgcc -VPATH += :libgcc +ifeq ($(CONFIG_COVERAGE_MINI)$(CONFIG_ARCH_TOOLCHAIN_GCC),yy) +CSRCS += gcov.c + +GCOV_CFLAGS += -fno-profile-arcs -fno-test-coverage +GCOV_CFLAGS += -fno-stack-protector + +bin/gcov.o: CFLAGS += $(GCOV_CFLAGS) +kbin/gcov.o: CFLAGS += $(GCOV_CFLAGS) endif + +DEPPATH += --dep-path libgcc +VPATH += :libgcc diff --git a/libs/libbuiltin/libgcc/gcov.c b/libs/libbuiltin/libgcc/gcov.c new file mode 100644 index 00000000000..2b0edec71a4 --- /dev/null +++ b/libs/libbuiltin/libgcc/gcov.c @@ -0,0 +1,505 @@ +/**************************************************************************** + * 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 +#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, "/"); + 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 +static int gcov_reboot_notify(FAR struct notifier_block *nb, + unsigned long action, FAR void *data) +{ + __gcov_dump(); + return 0; +} +#endif + +/**************************************************************************** + * 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_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) +{ + FAR struct gcov_info *info; + FAR const char *strip = getenv("GCOV_PREFIX_STRIP"); + FAR const char *prefix = getenv("GCOV_PREFIX"); + FAR char new_path[PATH_MAX]; + FAR char *prefix2; + int ret; + + if (prefix == NULL) + { + syslog(LOG_ERR, "No path prefix specified"); + return; + } + + prefix2 = strdup(prefix); + if (prefix2 == NULL) + { + syslog(LOG_ERR, "gcov alloc failed!"); + return; + } + + 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 *, 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); +} diff --git a/tools/gcov_convert.py b/tools/gcov_convert.py new file mode 100755 index 00000000000..7648784d853 --- /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)