/* * This code provides functions to handle gcc's profiling data format * introduced with gcc 3.4. Future versions of gcc may change the gcov * format (as happened before), so all format-specific information needs * to be kept modular and easily exchangeable. * * This file is based on gcc-internal definitions. Functions and data * structures are defined to be compatible with gcc counterparts. * For a better understanding, refer to gcc source: gcc/gcov-io.h. * * Copyright IBM Corp. 2009 * Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com> * * Uses gcc-internal data definitions. */ #include <linux/errno.h> #include <linux/slab.h> #include <linux/string.h> #include <linux/seq_file.h> #include <linux/vmalloc.h> #include "gcov.h" #define GCOV_COUNTERS 5 static struct gcov_info *gcov_info_head; /** * struct gcov_fn_info - profiling meta data per function * @ident: object file-unique function identifier * @checksum: function checksum * @n_ctrs: number of values per counter type belonging to this function * * This data is generated by gcc during compilation and doesn't change * at run-time. */ struct gcov_fn_info { unsigned int ident; unsigned int checksum; unsigned int n_ctrs[0]; }; /** * struct gcov_ctr_info - profiling data per counter type * @num: number of counter values for this type * @values: array of counter values for this type * @merge: merge function for counter values of this type (unused) * * 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; void (*merge)(gcov_type *, unsigned int); }; /** * struct gcov_info - profiling data per object file * @version: gcov version magic indicating the gcc version used for compilation * @next: list head for a singly-linked list * @stamp: time stamp * @filename: name of the associated gcov data file * @n_functions: number of instrumented functions * @functions: function data * @ctr_mask: mask specifying which counter types are active * @counts: counter data per counter type * * 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; const char *filename; unsigned int n_functions; const struct gcov_fn_info *functions; unsigned int ctr_mask; struct gcov_ctr_info counts[0]; }; /** * gcov_info_filename - return info filename * @info: profiling data set */ const char *gcov_info_filename(struct gcov_info *info) { return info->filename; } /** * gcov_info_version - return info version * @info: profiling data set */ unsigned int gcov_info_version(struct gcov_info *info) { return info->version; } /** * gcov_info_next - return next profiling data set * @info: profiling data 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 profiling data set to the list * @info: profiling data set */ void gcov_info_link(struct gcov_info *info) { info->next = gcov_info_head; gcov_info_head = info; } /** * gcov_info_unlink - unlink/remove profiling data set from the list * @prev: previous profiling data set * @info: profiling data 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; } /* Symbolic links to be created for each profiling data file. */ const struct gcov_link gcov_link[] = { { OBJ_TREE, "gcno" }, /* Link to .gcno file in $(objtree). */ { 0, NULL}, }; /* * Determine whether a counter is active. Based on gcc magic. Doesn't change * at run-time. */ static int counter_active(struct gcov_info *info, unsigned int type) { return (1 << type) & info->ctr_mask; } /* Determine number of active counters. Based on gcc magic. */ static unsigned int num_counter_active(struct gcov_info *info) { unsigned int i; unsigned int result = 0; for (i = 0; i < GCOV_COUNTERS; i++) { if (counter_active(info, i)) result++; } return result; } /** * gcov_info_reset - reset profiling data to zero * @info: profiling data set */ void gcov_info_reset(struct gcov_info *info) { unsigned int active = num_counter_active(info); unsigned int i; for (i = 0; i < active; i++) { memset(info->counts[i].values, 0, info->counts[i].num * sizeof(gcov_type)); } } /** * gcov_info_is_compatible - check if profiling data can be added * @info1: first profiling data set * @info2: second profiling data set * * Returns non-zero if profiling data can be added, zero otherwise. */ int gcov_info_is_compatible(struct gcov_info *info1, struct gcov_info *info2) { return (info1->stamp == info2->stamp); } /** * gcov_info_add - add up profiling data * @dest: profiling data set to which data is added * @source: profiling data set which is added * * Adds profiling counts of @source to @dest. */ void gcov_info_add(struct gcov_info *dest, struct gcov_info *source) { unsigned int i; unsigned int j; for (i = 0; i < num_counter_active(dest); i++) { for (j = 0; j < dest->counts[i].num; j++) { dest->counts[i].values[j] += source->counts[i].values[j]; } } } /* Get size of function info entry. Based on gcc magic. */ static size_t get_fn_size(struct gcov_info *info) { size_t size; size = sizeof(struct gcov_fn_info) + num_counter_active(info) * sizeof(unsigned int); if (__alignof__(struct gcov_fn_info) > sizeof(unsigned int)) size = ALIGN(size, __alignof__(struct gcov_fn_info)); return size; } /* Get address of function info entry. Based on gcc magic. */ static struct gcov_fn_info *get_fn_info(struct gcov_info *info, unsigned int fn) { return (struct gcov_fn_info *) ((char *) info->functions + fn * get_fn_size(info)); } /** * gcov_info_dup - duplicate profiling data set * @info: profiling data set to duplicate * * Return newly allocated duplicate on success, %NULL on error. */ struct gcov_info *gcov_info_dup(struct gcov_info *info) { struct gcov_info *dup; unsigned int i; unsigned int active; /* Duplicate gcov_info. */ active = num_counter_active(info); dup = kzalloc(sizeof(struct gcov_info) + sizeof(struct gcov_ctr_info) * active, GFP_KERNEL); if (!dup) return NULL; dup->version = info->version; dup->stamp = info->stamp; dup->n_functions = info->n_functions; dup->ctr_mask = info->ctr_mask; /* Duplicate filename. */ dup->filename = kstrdup(info->filename, GFP_KERNEL); if (!dup->filename) goto err_free; /* Duplicate table of functions. */ dup->functions = kmemdup(info->functions, info->n_functions * get_fn_size(info), GFP_KERNEL); if (!dup->functions) goto err_free; /* Duplicate counter arrays. */ for (i = 0; i < active ; i++) { struct gcov_ctr_info *ctr = &info->counts[i]; size_t size = ctr->num * sizeof(gcov_type); dup->counts[i].num = ctr->num; dup->counts[i].merge = ctr->merge; dup->counts[i].values = vmalloc(size); if (!dup->counts[i].values) goto err_free; memcpy(dup->counts[i].values, ctr->values, size); } return dup; err_free: gcov_info_free(dup); return NULL; } /** * gcov_info_free - release memory for profiling data set duplicate * @info: profiling data set duplicate to free */ void gcov_info_free(struct gcov_info *info) { unsigned int active = num_counter_active(info); unsigned int i; for (i = 0; i < active ; i++) vfree(info->counts[i].values); kfree(info->functions); kfree(info->filename); kfree(info); } /** * struct type_info - iterator helper array * @ctr_type: counter type * @offset: index of the first value of the current function for this type * * This array is needed to convert the in-memory data format into the in-file * data format: * * In-memory: * for each counter type * for each function * values * * In-file: * for each function * for each counter type * values * * See gcc source gcc/gcov-io.h for more information on data organization. */ struct type_info { int ctr_type; unsigned int offset; }; /** * struct gcov_iterator - specifies current file position in logical records * @info: associated profiling data * @record: record type * @function: function number * @type: counter type * @count: index into values array * @num_types: number of counter types * @type_info: helper array to get values-array offset for current function */ struct gcov_iterator { struct gcov_info *info; int record; unsigned int function; unsigned int type; unsigned int count; int num_types; struct type_info type_info[0]; }; static struct gcov_fn_info *get_func(struct gcov_iterator *iter) { return get_fn_info(iter->info, iter->function); } static struct type_info *get_type(struct gcov_iterator *iter) { return &iter->type_info[iter->type]; } /** * gcov_iter_new - allocate and initialize profiling data iterator * @info: profiling data set to be iterated * * Return file iterator on success, %NULL otherwise. */ struct gcov_iterator *gcov_iter_new(struct gcov_info *info) { struct gcov_iterator *iter; iter = kzalloc(sizeof(struct gcov_iterator) + num_counter_active(info) * sizeof(struct type_info), GFP_KERNEL); if (iter) iter->info = info; return iter; } /** * gcov_iter_free - release memory for iterator * @iter: file iterator to free */ void gcov_iter_free(struct gcov_iterator *iter) { kfree(iter); } /** * gcov_iter_get_info - return profiling data set for given file iterator * @iter: file iterator */ struct gcov_info *gcov_iter_get_info(struct gcov_iterator *iter) { return iter->info; } /** * gcov_iter_start - reset file iterator to starting position * @iter: file iterator */ void gcov_iter_start(struct gcov_iterator *iter) { int i; iter->record = 0; iter->function = 0; iter->type = 0; iter->count = 0; iter->num_types = 0; for (i = 0; i < GCOV_COUNTERS; i++) { if (counter_active(iter->info, i)) { iter->type_info[iter->num_types].ctr_type = i; iter->type_info[iter->num_types++].offset = 0; } } } /* Mapping of logical record number to actual file content. */ #define RECORD_FILE_MAGIC 0 #define RECORD_GCOV_VERSION 1 #define RECORD_TIME_STAMP 2 #define RECORD_FUNCTION_TAG 3 #define RECORD_FUNCTON_TAG_LEN 4 #define RECORD_FUNCTION_IDENT 5 #define RECORD_FUNCTION_CHECK 6 #define RECORD_COUNT_TAG 7 #define RECORD_COUNT_LEN 8 #define RECORD_COUNT 9 /** * gcov_iter_next - advance file iterator to next logical record * @iter: file iterator * * Return zero if new position is valid, non-zero if iterator has reached end. */ int gcov_iter_next(struct gcov_iterator *iter) { switch (iter->record) { case RECORD_FILE_MAGIC: case RECORD_GCOV_VERSION: case RECORD_FUNCTION_TAG: case RECORD_FUNCTON_TAG_LEN: case RECORD_FUNCTION_IDENT: case RECORD_COUNT_TAG: /* Advance to next record */ iter->record++; break; case RECORD_COUNT: /* Advance to next count */ iter->count++; /* fall through */ case RECORD_COUNT_LEN: if (iter->count < get_func(iter)->n_ctrs[iter->type]) { iter->record = 9; break; } /* Advance to next counter type */ get_type(iter)->offset += iter->count; iter->count = 0; iter->type++; /* fall through */ case RECORD_FUNCTION_CHECK: if (iter->type < iter->num_types) { iter->record = 7; break; } /* Advance to next function */ iter->type = 0; iter->function++; /* fall through */ case RECORD_TIME_STAMP: if (iter->function < iter->info->n_functions) iter->record = 3; else iter->record = -1; break; } /* Check for EOF. */ if (iter->record == -1) return -EINVAL; else return 0; } /** * seq_write_gcov_u32 - write 32 bit number in gcov format to seq_file * @seq: seq_file handle * @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. */ static int seq_write_gcov_u32(struct seq_file *seq, u32 v) { return seq_write(seq, &v, sizeof(v)); } /** * seq_write_gcov_u64 - write 64 bit number in gcov format to seq_file * @seq: seq_file handle * @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. */ static int seq_write_gcov_u64(struct seq_file *seq, u64 v) { u32 data[2]; data[0] = (v & 0xffffffffUL); data[1] = (v >> 32); return seq_write(seq, data, sizeof(data)); } /** * gcov_iter_write - write data for current pos to seq_file * @iter: file iterator * @seq: seq_file handle * * Return zero on success, non-zero otherwise. */ int gcov_iter_write(struct gcov_iterator *iter, struct seq_file *seq) { int rc = -EINVAL; switch (iter->record) { case RECORD_FILE_MAGIC: rc = seq_write_gcov_u32(seq, GCOV_DATA_MAGIC); break; case RECORD_GCOV_VERSION: rc = seq_write_gcov_u32(seq, iter->info->version); break; case RECORD_TIME_STAMP: rc = seq_write_gcov_u32(seq, iter->info->stamp); break; case RECORD_FUNCTION_TAG: rc = seq_write_gcov_u32(seq, GCOV_TAG_FUNCTION); break; case RECORD_FUNCTON_TAG_LEN: rc = seq_write_gcov_u32(seq, 2); break; case RECORD_FUNCTION_IDENT: rc = seq_write_gcov_u32(seq, get_func(iter)->ident); break; case RECORD_FUNCTION_CHECK: rc = seq_write_gcov_u32(seq, get_func(iter)->checksum); break; case RECORD_COUNT_TAG: rc = seq_write_gcov_u32(seq, GCOV_TAG_FOR_COUNTER(get_type(iter)->ctr_type)); break; case RECORD_COUNT_LEN: rc = seq_write_gcov_u32(seq, get_func(iter)->n_ctrs[iter->type] * 2); break; case RECORD_COUNT: rc = seq_write_gcov_u64(seq, iter->info->counts[iter->type]. values[iter->count + get_type(iter)->offset]); break; } return rc; }