2009-06-18 03:28:08 +04:00
/*
* 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"
2013-11-13 03:11:24 +04:00
# 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 ;
}
2009-06-18 03:28:08 +04:00
/* 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 ;
}