// SPDX-License-Identifier: GPL-2.0 /* * This code provides functions to handle gcc's coverage info format * introduced with gcc 4.7. * * This file is based heavily on gcc_3_4.c file. * * For a better understanding, refer to gcc source: * gcc/gcov-io.h * libgcc/libgcov.c * * Uses gcc-internal data definitions. */ // Modified based on https://github.com/torvalds/linux/blob/master/kernel/gcov/gcc_4_7.c #include #include #include #include #include #include //#define DEBUG #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)) #if (__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 meta data per function * @key: comdat key * @ident: unique ident of function * @lineno_checksum: function lineo_checksum * @cfg_checksum: function cfg checksum * @ctrs: instrumented counters * * This data is generated by gcc during compilation and doesn't change * at run-time. * * Information about a single function. This uses the trailing array * idiom. The number of counters is determined from the merge pointer * array in gcov_info. The key is used to detect which of a set of * comdat functions was selected -- it points to the gcov_info object * of the object file containing the selected comdat function. */ 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; }; /** * 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; }; struct gcov_info *gcov_info_head = NULL; /** where coverage data stored in after execute gcov_collect(0) */ struct gcov_data *gcov_data_head = NULL; /** * gcov_info_filename - return info filename * @info: coverage info set */ const char *gcov_info_filename(struct gcov_info *info) { return info->filename; } /** * gcov_info_version - return info version * @info: coverage info set */ unsigned int gcov_info_version(struct gcov_info *info) { return info->version; } /** * gcov_info_next - return next coverage info set * @info: coverage info set * * Returns next gcov_info following @info or first gcov_info in the chain if * @info is %NULL. */ struct gcov_info *gcov_info_next(struct gcov_info *info) { if (!info) return gcov_info_head; return info->next; } /** * 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; } /** * gcov_data_link - link/add coverage data set to the list * @info: coverage data set */ void gcov_data_link(struct gcov_data *data) { data->next = gcov_data_head; gcov_data_head = data; } /* * 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; } /** * 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; } /** * 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; } /* * 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); } } #define NUM_OCTETS_PER_LINE 20 #define FLUSH_OUTPUT() fflush(stdout) 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; } } // Modified based on https://github.com/astarasikov/lk/commit/2a4af09a894194dfaff3e05f6fd505241d54d074 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); } void gcov_dump(void) { struct gcov_info *info; if (!gcov_info_head) { return; } printf("\nDump coverage data start\n"); fflush(stdout); for (info = gcov_info_head; info != NULL; info = info->next) { dump_gcov_info(info); } printf("\nDump coverage data finish\n"); fflush(stdout); } /** * gcov_free - free all coverage data allocated memory * */ 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 * It need to malloc buffer from heap, so it may fail if your heap is not big enough * * Return 0, if all coverage data is collected and converted */ int gcov_collect(unsigned long interface) { struct gcov_info *info; struct gcov_data *data; size_t sz = 0, count = 0; char *bufptr = NULL; int fd = -1; // Make sure there are coverage information in it if (!gcov_info_head) { return -1; } // if you want to dump in console, just call gcov_dump() function if (interface > 1) { gcov_dump(); return 0; } // Free coverage data if present before // to make sure collect latest coverage data 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) { fd = open(data->filename, O_CREAT | O_TRUNC | O_WRONLY, 0666); if (fd > 0) { printf("Create and store coverage data in %s file\n", data->filename); write(fd, (const char*) data->buffer, (size_t)data->size); close(fd); } else { printf("Unable to open %s file\n", data->filename); } } count += 1; } if (count) { printf("%u files coverage data collected, see gcov_data_head=0x%x\n", count, gcov_data_head); } return 0; }