Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755217Ab3HWIky (ORCPT ); Fri, 23 Aug 2013 04:40:54 -0400 Received: from mx1.redhat.com ([209.132.183.28]:27316 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755126Ab3HWIj5 (ORCPT ); Fri, 23 Aug 2013 04:39:57 -0400 From: Frantisek Hrbata To: linux-kernel@vger.kernel.org Cc: jstancek@redhat.com, keescook@chromium.org, peter.oberparleiter@de.ibm.com, rusty@rustcorp.com.au, linux-arch@vger.kernel.org, arnd@arndb.de, mgahagan@redhat.com, agospoda@redhat.com Subject: [RFC PATCH 2/4] gcov: add support for gcc 4.7 gcov format Date: Fri, 23 Aug 2013 10:39:34 +0200 Message-Id: <1377247176-13537-3-git-send-email-fhrbata@redhat.com> In-Reply-To: <1377247176-13537-1-git-send-email-fhrbata@redhat.com> References: <1377247176-13537-1-git-send-email-fhrbata@redhat.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 16561 Lines: 658 The gcov in-memory format changed in gcc 4.7. The biggest change, which requires this special implementation, is that gcov_info no longer contains array of counters for each counter type for all functions and gcov_fn_info is not used for mapping of function's counters to these arrays(offset). Now each gcov_fn_info contans it's counters, which makes things a little bit easier. This is heavily based on the previous gcc_3_4.c implementation. Signed-off-by: Frantisek Hrbata --- kernel/gcov/base.c | 6 + kernel/gcov/gcc_4_7.c | 612 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 618 insertions(+) create mode 100644 kernel/gcov/gcc_4_7.c diff --git a/kernel/gcov/base.c b/kernel/gcov/base.c index 912576a..f45b75b 100644 --- a/kernel/gcov/base.c +++ b/kernel/gcov/base.c @@ -79,6 +79,12 @@ void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters) } EXPORT_SYMBOL(__gcov_merge_delta); +void __gcov_merge_ior(gcov_type *counters, unsigned int n_counters) +{ + /* Unused. */ +} +EXPORT_SYMBOL(__gcov_merge_ior); + /** * gcov_enable_events - enable event reporting through gcov_event() * diff --git a/kernel/gcov/gcc_4_7.c b/kernel/gcov/gcc_4_7.c new file mode 100644 index 0000000..31bee00 --- /dev/null +++ b/kernel/gcov/gcc_4_7.c @@ -0,0 +1,612 @@ +/* + * This code provides functions to handle gcc's profiling data 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. + */ + +#include +#include +#include +#include +#include "gcov.h" + +#define GCOV_COUNTERS 8 +#define GCOV_TAG_FUNCTION_LENGTH 3 + +static struct gcov_info *gcov_info_head; + +/** + * 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[0]; +}; + +/** + * 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: uniquifying time stamp + * @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; + const char *filename; + void (*merge[GCOV_COUNTERS])(gcov_type *, unsigned int); + unsigned int n_functions; + const struct gcov_fn_info *const *functions; +}; + +/** + * 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. Doesn't change at run-time. + */ +static int counter_active(struct gcov_info *info, unsigned int type) +{ + return info->merge[type] ? 1 : 0; +} + +/* 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) +{ + const struct gcov_ctr_info *ci_ptr; + unsigned int fi_idx; + unsigned int t_idx; + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) + { + ci_ptr = info->functions[fi_idx]->ctrs; + + for (t_idx = 0; t_idx < GCOV_COUNTERS; t_idx++) { + if (!counter_active(info, t_idx)) + continue; + + memset(ci_ptr->values, 0, + sizeof(gcov_type) * ci_ptr->num); + ci_ptr++; + } + } +} + +/** + * 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 *dst, struct gcov_info *src) +{ + const struct gcov_ctr_info *dci_ptr; + const struct gcov_ctr_info *sci_ptr; + unsigned int fi_idx; + unsigned int t_idx; + unsigned int val_idx; + + for (fi_idx = 0; fi_idx < src->n_functions; fi_idx++) + { + dci_ptr = dst->functions[fi_idx]->ctrs; + sci_ptr = src->functions[fi_idx]->ctrs; + + for (t_idx = 0; t_idx < GCOV_COUNTERS; t_idx++) { + if (!counter_active(src, t_idx)) + continue; + + for (val_idx = 0; val_idx < sci_ptr->num; val_idx++) + dci_ptr->values[val_idx] += + sci_ptr->values[val_idx]; + + dci_ptr++; + sci_ptr++; + } + } +} + +/** + * 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; + struct gcov_ctr_info *dci_ptr; + struct gcov_ctr_info *sci_ptr; + size_t size; + unsigned int active; + unsigned int fi_idx; + unsigned int t_idx; + + dup = kmalloc(sizeof(struct gcov_info), GFP_KERNEL); + if (!dup) + return NULL; + + *dup = *info; + dup->next = NULL; + dup->filename = NULL; + dup->functions = NULL; + + dup->filename = kstrdup(info->filename, GFP_KERNEL); + if (!dup->filename) + goto err_free; + + size = sizeof(void *) * info->n_functions; + dup->functions = kzalloc(size, GFP_KERNEL); + if (!dup->functions) + goto err_free; + + active = num_counter_active(info); + size = sizeof(struct gcov_fn_info); + size += sizeof(struct gcov_ctr_info) * active; + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { + *(struct gcov_fn_info **)&dup->functions[fi_idx] = + kzalloc(size, GFP_KERNEL); + if (!dup->functions[fi_idx]) + goto err_free; + + *(struct gcov_fn_info *)&(*(dup->functions[fi_idx])) = + *(info->functions[fi_idx]); + + sci_ptr = (struct gcov_ctr_info *)info->functions[fi_idx]->ctrs; + dci_ptr = (struct gcov_ctr_info *)dup->functions[fi_idx]->ctrs; + + for (t_idx = 0; t_idx < GCOV_COUNTERS; t_idx++) { + if (!counter_active(info, t_idx)) + continue; + + dci_ptr->values = kmemdup(sci_ptr->values, + sizeof(gcov_type) * sci_ptr->num, GFP_KERNEL); + + if (!dci_ptr->values) + goto err_free; + + dci_ptr->num = sci_ptr->num; + + sci_ptr++; + dci_ptr++; + } + } + + 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; + unsigned int fi_idx; + unsigned int t_idx; + const struct gcov_ctr_info *ci_ptr; + + if (!info->functions) + goto free_info; + + active = num_counter_active(info); + + for (fi_idx = 0; fi_idx < info->n_functions; fi_idx++) { + if (!info->functions[fi_idx]) + continue; + + ci_ptr = info->functions[fi_idx]->ctrs; + + for (t_idx = 0; t_idx < GCOV_COUNTERS; t_idx++) { + if (!counter_active(info, t_idx)) + continue; + + if (!ci_ptr->values) + continue; + + kfree(ci_ptr->values); + + ci_ptr++; + } + + kfree(info->functions[fi_idx]); + } + +free_info: + kfree(info->functions); + kfree(info->filename); + kfree(info); +} + +/** + * 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 current counter type + */ +struct gcov_iterator { + struct gcov_info *info; + int record; + unsigned int function; + unsigned int type; + unsigned int count; + int num_types; + int type_info[0]; +}; + +static const struct gcov_fn_info *get_func(struct gcov_iterator *iter) +{ + return iter->info->functions[iter->function]; +} + +static const struct gcov_ctr_info *get_ctr(struct gcov_iterator *iter) +{ + return &get_func(iter)->ctrs[iter->type]; +} + +static gcov_type get_cnt_type(struct gcov_iterator *iter) +{ + return iter->type_info[iter->type]; +} + +static gcov_type get_cnt_val(struct gcov_iterator *iter) +{ + return get_ctr(iter)->values[iter->count]; +} + +/** + * 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(int), GFP_KERNEL); + if (iter) + iter->info = info; + + return iter; +} + +/** + * gcov_iter_get_info - return profiling data set for given file iterator + * @iter: file iterator + */ +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++] = i; + } +} + +/* 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_FUNCTION_TAG_LEN 4 +#define RECORD_FUNCTION_IDENT 5 +#define RECORD_FUNCTION_LINENO_CHECK 6 +#define RECORD_FUNCTION_CFG_CHECK 7 +#define RECORD_COUNT_TAG 8 +#define RECORD_COUNT_LEN 9 +#define RECORD_COUNT 10 + +/** + * 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_FUNCTION_TAG_LEN: + case RECORD_FUNCTION_IDENT: + case RECORD_FUNCTION_LINENO_CHECK: + case RECORD_COUNT_TAG: + /* Advance to next record */ + iter->record++; + break; + + case RECORD_COUNT: + iter->count++; + /* fall through */ + case RECORD_COUNT_LEN: + if (iter->count < get_ctr(iter)->num) { + iter->record = RECORD_COUNT; + break; + } + iter->count = 0; + iter->type++; + /* fall through */ + case RECORD_FUNCTION_CFG_CHECK: + if (iter->type < iter->num_types) { + iter->record = RECORD_COUNT_TAG; + break; + } + iter->type = 0; + iter->function++; + /* fall through */ + case RECORD_TIME_STAMP: + if (iter->function < iter->info->n_functions) + iter->record = RECORD_FUNCTION_TAG; + 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) +{ + switch (iter->record) { + case RECORD_FILE_MAGIC: + seq_write_gcov_u32(seq, GCOV_DATA_MAGIC); + break; + + case RECORD_GCOV_VERSION: + seq_write_gcov_u32(seq, iter->info->version); + break; + + case RECORD_TIME_STAMP: + seq_write_gcov_u32(seq, iter->info->stamp); + break; + + case RECORD_FUNCTION_TAG: + seq_write_gcov_u32(seq, GCOV_TAG_FUNCTION); + break; + + case RECORD_FUNCTION_TAG_LEN: + seq_write_gcov_u32(seq, GCOV_TAG_FUNCTION_LENGTH); + break; + + case RECORD_FUNCTION_IDENT: + seq_write_gcov_u32(seq, get_func(iter)->ident); + break; + + case RECORD_FUNCTION_LINENO_CHECK: + seq_write_gcov_u32(seq, get_func(iter)->lineno_checksum); + break; + + case RECORD_FUNCTION_CFG_CHECK: + seq_write_gcov_u32(seq, get_func(iter)->cfg_checksum); + break; + + case RECORD_COUNT_TAG: + seq_write_gcov_u32(seq, + GCOV_TAG_FOR_COUNTER(get_cnt_type(iter))); + break; + + case RECORD_COUNT_LEN: + seq_write_gcov_u32(seq, get_ctr(iter)->num * 2); + break; + + case RECORD_COUNT: + seq_write_gcov_u64(seq, get_cnt_val(iter)); + break; + + default: + return -EINVAL; + } + + return 0; +} -- 1.8.3.1 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/