/**************************************************************************** * 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 ****************************************************************************/ /* The GCOV 12 gcno/gcda format has slight change, * Please refer to gcov-io.h in the GCC 12 for * more details. */ #if __GNUC__ >= 12 # define GCOV_12_FORMAT #endif #ifdef GCOV_12_FORMAT # define GCOV_UNIT_SIZE 4 #else # define GCOV_UNIT_SIZE 1 #endif #ifdef GCOV_12_FORMAT # define GCOV_TAG_FUNCTION_LENGTH 12 #else # define GCOV_TAG_FUNCTION_LENGTH 3 #endif #if __GNUC__ >= 14 # define GCOV_COUNTERS 9u #elif __GNUC__ >= 10 # define GCOV_COUNTERS 8u #elif __GNUC__ >= 8 # define GCOV_COUNTERS 9u #else # define GCOV_COUNTERS 10u #endif #define GCOV_DATA_MAGIC (0x67636461) #define GCOV_NOTE_MAGIC (0x67636e6f) #define GCOV_FILENAME_MAGIC (0x6763666e) #define GCOV_TAG_FUNCTION (0x01000000) #define GCOV_TAG_COUNTER_BASE (0x01a10000) #define GCOV_TAG_FOR_COUNTER(count) \ (GCOV_TAG_COUNTER_BASE + ((uint32_t)(count) << 17)) #define GCOV_PATH_MAX_TOKENS 64 /**************************************************************************** * Private Types ****************************************************************************/ typedef uint64_t gcov_type; typedef unsigned int gcov_unsigned_t; /** Profiling data per object file * * This data is generated by gcc during compilation and doesn't change * at run-time with the exception of the next pointer. */ struct gcov_info { unsigned int version; /* Gcov version (same as GCC version) */ FAR struct gcov_info *next; /* List head for a singly-linked list */ unsigned int stamp; /* Uniquifying time stamp */ #ifdef GCOV_12_FORMAT unsigned int checksum; /* unique object checksum */ #endif FAR const char *filename; /* Name of the associated gcda data file */ void (*merge[GCOV_COUNTERS])(FAR gcov_type *, unsigned int); unsigned int n_functions; /* number of instrumented functions */ FAR struct gcov_fn_info **functions; /* function information */ }; /* Information about counters for a single function * * This data is generated by gcc during compilation and doesn't change * at run-time. */ struct gcov_ctr_info { unsigned int num; /* Number of counter values for this type */ FAR gcov_type *values; /* Array of counter values for this type */ }; /* Profiling meta data per function * * This data is generated by gcc during compilation and doesn't change * at run-time. */ struct gcov_fn_info { FAR const struct gcov_info *key; /* Comdat key */ unsigned int ident; /* Unique ident of function */ unsigned int lineno_checksum; /* Function lineno checksum */ unsigned int cfg_checksum; /* Function cfg checksum */ struct gcov_ctr_info ctrs[1]; /* Instrumented counters */ }; struct dump_args { FAR CODE int (*mkdir)(FAR const char *); FAR struct lib_outstream_s *stream; FAR char *prefix; FAR char *tokens[GCOV_PATH_MAX_TOKENS]; size_t token_cnt; FAR char *path_buffer; int strip; }; typedef CODE int (*dump_fn_t)(FAR struct dump_args *, FAR char *, FAR uint8_t *, size_t); /**************************************************************************** * Public Data ****************************************************************************/ FAR struct gcov_info *__gcov_info_start; FAR struct gcov_info *__gcov_info_end; /**************************************************************************** * Private Functions ****************************************************************************/ static void dump_counter(FAR void *buffer, FAR size_t *off, uint64_t v) { if (buffer) { *(FAR uint64_t *)((FAR uint8_t *)buffer + *off) = v; } *off += sizeof(uint64_t); } static void dump_unsigned(FAR void *buffer, FAR size_t *off, uint32_t v) { if (buffer) { *(FAR uint32_t *)((FAR uint8_t *)buffer + *off) = v; } *off += sizeof(uint32_t); } static int get_path_tokens(FAR char *path, FAR char **tokens, size_t max_tokens) { size_t token_count = 0; FAR char *token; token = strtok(path, "/"); while (token != NULL) { if (token_count >= max_tokens) { return -ENAMETOOLONG; } tokens[token_count++] = token; token = strtok(NULL, "/"); } tokens[token_count] = NULL; return token_count; } static size_t gcov_convert(FAR uint8_t *buffer, FAR const struct gcov_info *info) { FAR struct gcov_fn_info *gfi; FAR struct gcov_ctr_info *gci; uint32_t functions; uint32_t counts; uint32_t value; size_t pos = 0; /* File header. */ dump_unsigned(buffer, &pos, GCOV_DATA_MAGIC); dump_unsigned(buffer, &pos, info->version); dump_unsigned(buffer, &pos, info->stamp); #ifdef GCOV_12_FORMAT dump_unsigned(buffer, &pos, info->checksum); #endif /* Function headers. */ for (functions = 0; functions < info->n_functions; functions++) { gfi = info->functions[functions]; /* Function record. */ dump_unsigned(buffer, &pos, GCOV_TAG_FUNCTION); dump_unsigned(buffer, &pos, GCOV_TAG_FUNCTION_LENGTH); dump_unsigned(buffer, &pos, gfi->ident); dump_unsigned(buffer, &pos, gfi->lineno_checksum); dump_unsigned(buffer, &pos, gfi->cfg_checksum); gci = gfi->ctrs; for (counts = 0; counts < GCOV_COUNTERS; counts++) { if (!info->merge[counts]) { continue; } /* Counter record. */ dump_unsigned(buffer, &pos, GCOV_TAG_FOR_COUNTER(counts)); dump_unsigned(buffer, &pos, gci->num * 2 * GCOV_UNIT_SIZE); for (value = 0; value < gci->num; value++) { dump_counter(buffer, &pos, gci->values[value]); } gci++; } } return pos; } static int gcov_mkdir(FAR const char *path) { if (access(path, F_OK) != 0) { return mkdir(path, 0777); } return OK; } #ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT static int gcov_reboot_notify(FAR struct notifier_block *nb, unsigned long action, FAR void *data) { __gcov_dump(); return OK; } #endif static int gcov_standard_dump(FAR struct dump_args *args, FAR char *path, FAR uint8_t *data, size_t size) { int written; int fd; fd = _NX_OPEN(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { return -errno; } written = _NX_WRITE(fd, data, size); if (written != size) { return -errno; } _NX_CLOSE(fd); return OK; } static int gcov_onefile_dump(FAR struct dump_args *args, FAR char *path, FAR uint8_t *data, size_t size) { FAR struct lib_outstream_s *stream = args->stream; struct lib_hexdumpstream_s hexstream; uint16_t checksum = 0; int i; lib_hexdumpstream(&hexstream, stream); for (i = 0; i < size; i++) { checksum += ((FAR const uint8_t *)data)[i]; } lib_sprintf(stream, "gcov start filename:%s size: %zuByte\n", path, size); lib_stream_puts(&hexstream, data, size); lib_stream_flush(&hexstream); lib_sprintf(stream, "gcov end filename:%s checksum: %#0x\n", path, checksum); lib_stream_flush(stream); return OK; } static int gcov_dump_foreach(dump_fn_t handler, FAR struct dump_args *args) { FAR struct gcov_info *info; FAR char *filename; FAR uint8_t *data; int token_count; int size; int i; for (info = __gcov_info_start; info; info = info->next) { memset(args->path_buffer, 0x00, PATH_MAX); size = gcov_convert(NULL, info); data = lib_malloc(size); if (data == NULL) { syslog(LOG_ERR, "gcov alloc failed!"); return -ERROR; } gcov_convert(data, info); filename = strdup(info->filename); if (filename == NULL) { syslog(LOG_ERR, "gcov alloc failed! skip %s", info->filename); lib_free(data); return -ERROR; } token_count = args->token_cnt + get_path_tokens(filename, &args->tokens[args->token_cnt], GCOV_PATH_MAX_TOKENS); if (token_count < args->token_cnt) { syslog(LOG_ERR, "gcov get path tokens failed! skip %s", info->filename); goto exit; } for (i = 0; i < token_count - 1; i++) { strcat(args->path_buffer, "/"); strcat(args->path_buffer, args->tokens[i]); if (args->mkdir) { args->mkdir(args->path_buffer); } } strcat(args->path_buffer, "/"); strcat(args->path_buffer, args->tokens[token_count - 1]); if (handler(args, args->path_buffer, data, size) < 0) { syslog(LOG_ERR, "gcov dump %s failed!\n", args->path_buffer); goto exit; } lib_free(filename); lib_free(data); } return OK; exit: lib_free(filename); lib_free(data); return -ERROR; } /**************************************************************************** * Public Functions ****************************************************************************/ void __gcov_init(FAR struct gcov_info *info) { #ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT static struct notifier_block nb; #endif char path[PATH_MAX] = CONFIG_COVERAGE_DEFAULT_PREFIX; static int inited = 0; struct tm *tm_info; time_t cur; if (!inited) { cur = time(NULL); tm_info = localtime(&cur); strftime(path + strlen(path), PATH_MAX, "/gcov_%Y%m%d_%H%M%S", tm_info); setenv("GCOV_PREFIX_STRIP", CONFIG_COVERAGE_DEFAULT_PREFIX_STRIP, 1); setenv("GCOV_DUMP_ONEFILE", "1", 1); setenv("GCOV_PREFIX", path, 1); #ifdef CONFIG_COVERAGE_GCOV_DUMP_REBOOT nb.notifier_call = gcov_reboot_notify; register_reboot_notifier(&nb); #endif inited++; } info->next = __gcov_info_start; __gcov_info_start = info; } void __gcov_merge_add(FAR gcov_type *counters, unsigned int n_counters) { } void __gcov_exit(void) { } void __gcov_execve(void) { } void __gcov_execl(void) { } void __gcov_execv(void) { } pid_t __gcov_fork(void) { return fork(); } void __gcov_dump(void) { int ret = -1; bool onefile; FAR char *prefix; struct dump_args args = { 0 }; prefix = getenv("GCOV_PREFIX"); if (prefix == NULL) { syslog(LOG_ERR, "No path prefix specified"); } args.path_buffer = lib_get_tempbuffer(PATH_MAX); if (args.path_buffer == NULL) { syslog(LOG_ERR, "gcov alloc failed!"); return; } onefile = strcmp(getenv("GCOV_DUMP_ONEFILE"), "1") == 0; if (onefile) { struct lib_rawoutstream_s stream; int fd = _NX_OPEN(prefix, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) { syslog(LOG_ERR, "open %s failed! ret: %d\n", prefix, ret); goto exit; } lib_rawoutstream(&stream, fd); args.stream = &stream.common; ret = gcov_dump_foreach(gcov_onefile_dump, &args); _NX_CLOSE(fd); } else { args.mkdir = gcov_mkdir; args.strip = atoi(getenv("GCOV_PREFIX_STRIP")); args.token_cnt = get_path_tokens(prefix, args.tokens, GCOV_PATH_MAX_TOKENS); ret = gcov_dump_foreach(gcov_standard_dump, &args); } exit: if (ret < 0) { syslog(LOG_INFO, "Gcov dump failed\n"); } else { syslog(LOG_INFO, "Gcov dump complete\n"); } lib_put_tempbuffer(args.path_buffer); } void __gcov_reset(void) { FAR struct gcov_info *info; FAR struct gcov_ctr_info *gci; uint32_t functions; uint32_t counts; for (info = __gcov_info_start; info; info = info->next) { for (functions = 0; functions < info->n_functions; functions++) { gci = info->functions[functions]->ctrs; for (counts = 0; counts < GCOV_COUNTERS; counts++) { if (!info->merge[counts]) { continue; } memset(gci->values, 0, gci->num * sizeof(gcov_type)); gci++; } } } }