// SPDX-License-Identifier: GPL-2.0 /* * This code provides functions to handle gcc and llvm coverage info format * introduced with gcc 4.7 and llvm clang compiled with -coverage option. * * This file is based heavily on gcc_4_7.c and clang.c file. * - https://github.com/torvalds/linux/blob/master/kernel/gcov/gcc_4_7.c * - https://github.com/torvalds/linux/commits/master/kernel/gcov/clang.c */ #include #include #include #include #if BITS_PER_LONG >= 64 typedef long gcov_type; #else typedef long long gcov_type; #endif typedef uint64_t u64; typedef uint32_t u32; /* * Profiling data types used for gcc 3.4 and above - these are defined by * gcc and need to be kept as close to the original definition as possible to * remain compatible. */ #define GCOV_DATA_MAGIC ((unsigned int) 0x67636461) #define GCOV_TAG_FUNCTION ((unsigned int) 0x01000000) #define GCOV_TAG_COUNTER_BASE ((unsigned int) 0x01a10000) #define GCOV_TAG_FOR_COUNTER(count) \ (GCOV_TAG_COUNTER_BASE + ((unsigned int) (count) << 17)) #ifndef __clang__ /* * GCC -coverage support * * Data structures and runtime functions for handling coverage information * generated by GCC with -coverage option. Supports GCC 4.7+ format. * Based on Linux kernel's gcov/gcc_4_7.c implementation. */ #if (__GNUC__ >= 15) #define GCOV_COUNTERS 10 #elif (__GNUC__ >= 14) #define GCOV_COUNTERS 9 #elif (__GNUC__ >= 10) #define GCOV_COUNTERS 8 #elif (__GNUC__ >= 7) #define GCOV_COUNTERS 9 #elif (__GNUC__ > 5) || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1) #define GCOV_COUNTERS 10 #else #define GCOV_COUNTERS 9 #endif #define GCOV_TAG_FUNCTION_LENGTH 3 /* Since GCC 12.1 sizes are in BYTES and not in WORDS (4B). */ #if (__GNUC__ >= 12) #define GCOV_UNIT_SIZE 4 #else #define GCOV_UNIT_SIZE 1 #endif /** * struct gcov_ctr_info - information about counters for a single function * @num: number of counter values for this type * @values: array of counter values for this type * * This data is generated by gcc during compilation and doesn't change * at run-time with the exception of the values array. */ struct gcov_ctr_info { unsigned int num; gcov_type *values; }; /** * struct gcov_fn_info - profiling metadata per function for GCC * @key: comdat key for detecting selected comdat function * @ident: unique identifier of the function * @lineno_checksum: function line number checksum * @cfg_checksum: control flow graph checksum * @ctrs: instrumented counters (flexible array member) * * This data is generated by gcc during compilation and doesn't change * at run-time. Uses trailing array idiom for counters. */ struct gcov_fn_info { const struct gcov_info *key; unsigned int ident; unsigned int lineno_checksum; unsigned int cfg_checksum; struct gcov_ctr_info ctrs[]; }; /** * struct gcov_info - coverage info per object file * @version: gcov version magic indicating the gcc version used for compilation * @next: list head for a singly-linked list * @stamp: uniquifying time stamp * @checksum: unique object checksum * @filename: name of the associated gcov data file * @merge: merge functions (null for unused counter type) * @n_functions: number of instrumented functions * @functions: pointer to pointers to function information * * 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; struct gcov_info *next; unsigned int stamp; /* Since GCC 12.1 a checksum field is added. */ #if (__GNUC__ >= 12) unsigned int checksum; #endif const char *filename; void (*merge[GCOV_COUNTERS])(gcov_type *, unsigned int); unsigned int n_functions; struct gcov_fn_info **functions; }; #else /* * LLVM/Clang -coverage support * * Data structures and runtime callbacks for handling coverage information * generated by LLVM/Clang with -coverage option. Based on Linux kernel's * gcov/clang.c implementation. */ /** * llvm_gcov_callback - LLVM coverage callback function type * * Callback function type used by LLVM for coverage data writeout and flush operations. */ typedef void (*llvm_gcov_callback)(void); /** * struct gcov_fn_info - profiling metadata per function for LLVM/Clang * @next: pointer to next function info in linked list * @ident: unique identifier of the function * @checksum: function checksum for validation * @cfg_checksum: control flow graph checksum for validation * @num_counters: number of coverage counters for this function * @counters: array of 64-bit counter values * * This structure stores coverage information for a single function when * compiled with LLVM/Clang using the -coverage option. The data is generated * during compilation and updated at runtime by LLVM's coverage instrumentation. */ struct gcov_fn_info { struct gcov_fn_info *next; u32 ident; u32 checksum; u32 cfg_checksum; u32 num_counters; u64 *counters; }; /** * struct gcov_info - coverage info per object file for LLVM/Clang * @next: pointer to next gcov_info in linked list * @filename: name of the associated gcov data file * @version: gcov version magic indicating the compiler version used * @checksum: unique object checksum * @functions: linked list of function-level coverage information * * This structure stores coverage information for an entire object file when * compiled with LLVM/Clang. It serves as the container for all functions' * coverage data within that compilation unit. The data is generated during * compilation and the counters are updated at runtime. */ struct gcov_info { struct gcov_info *next; const char *filename; unsigned int version; u32 checksum; struct gcov_fn_info *functions; }; /* Pointer to currently active gcov_info during LLVM gcov information initialization */ static struct gcov_info *current_info; /* Pointer to currently active gcov_fn_info during function processing (also serves as tail pointer) */ static struct gcov_fn_info *current_function; #endif /** * struct gcov_data - analyzed coverage data * @next: list head for a singly-linked list * @filename: name of the associated gcov data file * @buffer: buffer pointer to save gcda data via convert_to_gcda * @size: buffer size in bytes * */ struct gcov_data { struct gcov_data *next; const char *filename; char *buffer; size_t size; }; /* Head of linked list containing all gcov_info from compiled object files */ struct gcov_info *gcov_info_head = NULL; /* Head of linked list storing collected coverage data after gcov_collect(0) */ struct gcov_data *gcov_data_head = NULL; /** * store_gcov_u32 - store 32 bit number in gcov format to buffer * @buffer: target buffer or NULL * @off: offset into the buffer * @v: value to be stored * * Number format defined by gcc: numbers are recorded in the 32 bit * unsigned binary form of the endianness of the machine generating the * file. Returns the number of bytes stored. If @buffer is %NULL, doesn't * store anything. */ size_t store_gcov_u32(void *buffer, size_t off, u32 v) { u32 *data; if (buffer) { data = buffer + off; *data = v; } return sizeof(*data); } /** * store_gcov_u64 - store 64 bit number in gcov format to buffer * @buffer: target buffer or NULL * @off: offset into the buffer * @v: value to be stored * * Number format defined by gcc: numbers are recorded in the 32 bit * unsigned binary form of the endianness of the machine generating the * file. 64 bit numbers are stored as two 32 bit numbers, the low part * first. Returns the number of bytes stored. If @buffer is %NULL, doesn't store * anything. */ size_t store_gcov_u64(void *buffer, size_t off, u64 v) { u32 *data; if (buffer) { data = buffer + off; data[0] = (v & 0xffffffffUL); data[1] = (v >> 32); } return sizeof(*data) * 2; } /** * gcov_data_link - link/add coverage data set to the global list * @data: pointer to gcov_data structure to be linked * * Adds the given coverage data structure to the head of the global * gcov_data_head linked list. This function is called after collecting * and converting coverage data from gcov_info structures. */ void gcov_data_link(struct gcov_data *data) { data->next = gcov_data_head; gcov_data_head = data; } #ifndef __clang__ /* * GCC-specific stub functions * * These functions are called by GCC-generated profiling code but are * not used in this bare-metal implementation. They are provided as * stubs to satisfy linker references. */ /** * gcov_info_link - link/add coverage info set to the list * @info: coverage info set */ void gcov_info_link(struct gcov_info *info) { info->next = gcov_info_head; gcov_info_head = info; } /** * gcov_info_unlink - unlink/remove coverage info set from the list * @prev: previous coverage info set * @info: coverage info set */ void gcov_info_unlink(struct gcov_info *prev, struct gcov_info *info) { if (prev) prev->next = info->next; else gcov_info_head = info->next; } /* * Determine whether a counter is active. Doesn't change at run-time. */ static int counter_active(struct gcov_info *info, unsigned int type) { return info->merge[type] ? 1 : 0; } /* * These functions may be referenced by gcc-generated profiling code but serve * no function for kernel profiling. */ void __gcov_flush(void) { /* Unused. */ } void __gcov_merge_add(gcov_type *counters, unsigned int n_counters) { /* Unused. */ } void __gcov_merge_single(gcov_type *counters, unsigned int n_counters) { /* Unused. */ } void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters) { /* Unused. */ } void __gcov_merge_ior(gcov_type *counters, unsigned int n_counters) { /* Unused. */ } void __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters) { /* Unused. */ } void __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters) { /* Unused. */ } void __gcov_exit(void) { /* Unused. */ } /* * __gcov_init is called by gcc-generated constructor code for each object * file compiled with -fprofile-arcs. */ void __gcov_init(struct gcov_info *info) { if (info) { gcov_info_link(info); } } /** * convert_to_gcda - convert coverage info set to gcda file format * @buffer: the buffer to store file data or %NULL if no data should be stored * @info: coverage info set to be converted * * Returns the number of bytes that were/would have been stored into the buffer. */ size_t convert_to_gcda(char *buffer, struct gcov_info *info) { struct gcov_fn_info *fi_ptr; struct gcov_ctr_info *ci_ptr; unsigned int fi_idx; unsigned int ct_idx; unsigned int cv_idx; size_t pos = 0; /* File header. */ pos += store_gcov_u32(buffer, pos, GCOV_DATA_MAGIC); pos += store_gcov_u32(buffer, pos, info->version); pos += store_gcov_u32(buffer, pos, info->stamp); #if (__GNUC__ >= 12) /* Use zero as checksum of the compilation unit. */ pos += store_gcov_u32(buffer, pos, 0); #endif for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { fi_ptr = info->functions[fi_idx]; /* Function record. */ pos += store_gcov_u32(buffer, pos, GCOV_TAG_FUNCTION); pos += store_gcov_u32(buffer, pos, GCOV_TAG_FUNCTION_LENGTH * GCOV_UNIT_SIZE); pos += store_gcov_u32(buffer, pos, fi_ptr->ident); pos += store_gcov_u32(buffer, pos, fi_ptr->lineno_checksum); pos += store_gcov_u32(buffer, pos, fi_ptr->cfg_checksum); ci_ptr = fi_ptr->ctrs; for (ct_idx = 0; ct_idx < GCOV_COUNTERS; ct_idx++) { if (!counter_active(info, ct_idx)) continue; /* Counter record. */ pos += store_gcov_u32(buffer, pos, GCOV_TAG_FOR_COUNTER(ct_idx)); pos += store_gcov_u32(buffer, pos, ci_ptr->num * 2 * GCOV_UNIT_SIZE); for (cv_idx = 0; cv_idx < ci_ptr->num; cv_idx++) { pos += store_gcov_u64(buffer, pos, ci_ptr->values[cv_idx]); } ci_ptr++; } } return pos; } #else /* * LLVM/Clang -coverage runtime stub functions * * These functions are called by Clang-generated profiling code when * compiled with -coverage option. They are provided as stubs to * satisfy linker references in this bare-metal implementation. */ /** * llvm_gcov_init - initialize LLVM coverage runtime * @writeout: callback for writing coverage data * @flush: callback for flushing coverage data * * Called by Clang runtime to initialize coverage data structures. */ void llvm_gcov_init(llvm_gcov_callback writeout, llvm_gcov_callback flush) { struct gcov_info *info = (struct gcov_info *)malloc(sizeof(*info)); if (!info) { return; } info->next = NULL; info->functions = NULL; current_info = info; current_function = NULL; if (gcov_info_head == NULL) { gcov_info_head = current_info; } else { gcov_info_head->next = current_info; } writeout(); current_info = NULL; } /** * llvm_gcda_start_file - start coverage data for a source file * @orig_filename: original source file name * @version: gcov version magic * @checksum: compilation unit checksum * * Called by Clang runtime at the start of coverage data emission. */ void llvm_gcda_start_file(const char *orig_filename, u32 version, u32 checksum) { if (!current_info) { return; } current_info->filename = orig_filename; current_info->version = version; current_info->checksum = checksum; } /** * llvm_gcda_emit_function - emit coverage data for a function * @ident: function unique identifier * @func_checksum: function checksum * @cfg_checksum: control flow graph checksum * * Called by Clang runtime to emit coverage data for each function. */ void llvm_gcda_emit_function(u32 ident, u32 func_checksum, u32 cfg_checksum) { struct gcov_fn_info *info = (struct gcov_fn_info *)malloc(sizeof(*info)); if (!info) { return; } info->ident = ident; info->checksum = func_checksum; info->cfg_checksum = cfg_checksum; info->next = NULL; if (current_info->functions == NULL) { current_info->functions = info; } else { current_function->next = info; } current_function = info; } /** * llvm_gcda_emit_arcs - emit coverage counters for function arcs * @num_counters: number of arc counters * @counters: array of 64-bit counter values * * Called by Clang runtime to emit arc coverage counters. */ void llvm_gcda_emit_arcs(u32 num_counters, u64 *counters) { if (current_function) { current_function->num_counters = num_counters; current_function->counters = counters; } } /** * llvm_gcda_summary_info - emit summary information * * Stub function for coverage summary (not used in this implementation). */ void llvm_gcda_summary_info(void) { } /** * llvm_gcda_end_file - end coverage data emission for a file * * Called by Clang runtime at the end of coverage data emission. */ void llvm_gcda_end_file(void) { } /** * convert_to_gcda - convert profiling data set to gcda file format * @buffer: the buffer to store file data or %NULL if no data should be stored * @info: profiling data set to be converted * * Returns the number of bytes that were/would have been stored into the buffer. */ size_t convert_to_gcda(char *buffer, struct gcov_info *info) { struct gcov_fn_info *fi_ptr; size_t pos = 0; /* File header. */ pos += store_gcov_u32(buffer, pos, GCOV_DATA_MAGIC); pos += store_gcov_u32(buffer, pos, info->version); pos += store_gcov_u32(buffer, pos, info->checksum); for (fi_ptr = info->functions; fi_ptr != NULL; fi_ptr = fi_ptr->next) { u32 i; pos += store_gcov_u32(buffer, pos, GCOV_TAG_FUNCTION); pos += store_gcov_u32(buffer, pos, 3); pos += store_gcov_u32(buffer, pos, fi_ptr->ident); pos += store_gcov_u32(buffer, pos, fi_ptr->checksum); pos += store_gcov_u32(buffer, pos, fi_ptr->cfg_checksum); pos += store_gcov_u32(buffer, pos, GCOV_TAG_COUNTER_BASE); pos += store_gcov_u32(buffer, pos, fi_ptr->num_counters * 2); for (i = 0; i < fi_ptr->num_counters; i++) { pos += store_gcov_u64(buffer, pos, fi_ptr->counters[i]); } } return pos; } #endif /* Dump buffer in hex format, 20 bytes per line */ #define NUM_OCTETS_PER_LINE 20 #define FLUSH_OUTPUT() fflush(stdout) /** * hexdumpbuf - dump buffer content as hex to stdout * @buf: pointer to buffer to dump * @sz: size of buffer in bytes * * Dumps buffer content in hexadecimal format, 20 bytes per line. * Modified based on: https://github.com/astarasikov/lk/commit/2a4af09a894194dfaff3e05f6fd505241d54d074 */ static void hexdumpbuf(char *buf, unsigned long sz) { unsigned long rem, cur = 0, i = 0; FLUSH_OUTPUT(); while (cur < sz) { rem = ((sz - cur) < NUM_OCTETS_PER_LINE) ? (sz - cur) : NUM_OCTETS_PER_LINE; for (i = 0; i < rem; i++) { printf("%02x", buf[cur + i]); } printf("\n"); FLUSH_OUTPUT(); cur += rem; } } /** * dump_gcov_info - convert and dump single gcov_info as hex * @info: pointer to gcov_info structure to dump * * Converts coverage data to gcda format and prints as hex to console * for debugging purposes. */ static void dump_gcov_info(struct gcov_info *info) { size_t sz = 0; char *bufptr = NULL; if (!info) { return; } sz = convert_to_gcda(NULL, info); bufptr = (char *)malloc(sz); if (bufptr == NULL) { printf("ERROR: Can't allocate gcda buffer for %s\n", info->filename); return; } sz = convert_to_gcda(bufptr, info); hexdumpbuf(bufptr, sz); free(bufptr); printf("\nCREATE: %s\n", info->filename); } /** * gcov_dump - dump all coverage data to console * * Iterates through gcov_info_head linked list and converts each gcov_info * to gcda format, then prints as hexadecimal to stdout for debugging. */ void gcov_dump(void) { struct gcov_info *info; if (!gcov_info_head) { return; } printf("\nDump coverage data start\n"); FLUSH_OUTPUT(); for (info = gcov_info_head; info != NULL; info = info->next) { dump_gcov_info(info); } printf("\nDump coverage data finish\n"); FLUSH_OUTPUT(); } /** * gcov_free - free all collected coverage data * * Frees all gcov_data structures and their associated buffers from * the gcov_data_head linked list. Should be called before collecting * new coverage data to avoid memory leaks. */ void gcov_free(void) { struct gcov_data *data; if (gcov_data_head == NULL) { return; } for (data = gcov_data_head; data != NULL; data = data->next) { if (data->buffer) { free(data->buffer); } free(data); } gcov_data_head = NULL; } /** * gcov_collect - collect and convert coverage data from gcov_info_head * @interface: output interface selector * - 0: collect only, store in gcov_data_head * - 1: collect and save to .gcda files * - >1: collect and dump to console via gcov_dump() * * Collects coverage data from gcov_info_head, converts to gcda format, * and stores in gcov_data_head linked list. May fail if heap is insufficient. * * Returns: 0 on success, -1 on failure */ int gcov_collect(unsigned long interface) { struct gcov_info *info; struct gcov_data *data; size_t sz = 0, count = 0; char *bufptr = NULL; /* Check if coverage info exists */ if (!gcov_info_head) { return -1; } /* Dump to console if interface > 1 */ if (interface > 1) { gcov_dump(); return 0; } /* Free previous coverage data to collect latest */ gcov_free(); for (info = gcov_info_head; info != NULL; info = info->next) { sz = convert_to_gcda(NULL, info); bufptr = (char *)malloc(sz); data = (struct gcov_data *)malloc(sizeof(struct gcov_data)); if ((bufptr == NULL) || (data == NULL)) { printf("Can't allocate gcda buffer for %s\n", info->filename); return -1; } data->filename = info->filename; data->buffer = bufptr; data->size = sz; convert_to_gcda(bufptr, info); gcov_data_link(data); if (interface == 1) { FILE *fp = fopen(data->filename, "wb"); if (fp != NULL) { printf("Create and store coverage data in %s file\n", data->filename); fwrite(data->buffer, 1, (size_t)data->size, fp); fclose(fp); } else { printf("Unable to open %s file\n", data->filename); } } count += 1; } if (count) { printf("%lu files coverage data collected, see gcov_data_head=0x%lx\n", (unsigned long)count, (unsigned long)gcov_data_head); } return 0; }