/* * gcov.c * * Kernel module which produces basic block profile / coverage data * for the kernel files. * * (C) 2002 by Hubertus Franke, IBM T.J.Watson Research Center * This software is distributed under the terms of GNU GPL */ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include #endif #include #include #include #include #include #include #define GCOV_PROF_PROC "gcov" struct bb { long zero_word; const char *filename; long *counts; long ncounts; struct bb *next; const unsigned long *addresses; /* Older GCC's did not emit these fields. */ long nwords; const char **functions; const long *line_nums; const char **filenames; char *flags; }; extern struct bb *bb_head; static struct file_operations proc_gcov_operations; extern char *gcov_kernelpath; static int create_bb_links = 1; static int kernel_path_len; struct gcov_ftree_node { int isdir; /* directory or file */ char *fname; /* only the name within the hierachy */ struct gcov_ftree_node *sibling; /* sibling of tree */ struct gcov_ftree_node *files; /* children of tree */ struct gcov_ftree_node *parent; /* parent of current gcov_ftree_node */ struct proc_dir_entry *proc[4]; struct bb *bb; /* below only valid for leaf nodes == files */ unsigned long offset; /* offset in global file */ struct gcov_ftree_node *next; /* next leave node */ }; static struct proc_dir_entry *proc_vmlinux = NULL; static struct gcov_ftree_node *leave_nodes = NULL; static struct gcov_ftree_node *dumpall_cached_node = NULL; static struct gcov_ftree_node tree_root = { 1, GCOV_PROF_PROC, NULL, NULL, NULL, { NULL, NULL, NULL, NULL} , NULL, 0,NULL }; static char *endings[3] = { ".bb", ".bbg", ".c" }; static inline unsigned long hdr_ofs(struct gcov_ftree_node *tptr) { return ( (8 + strlen(tptr->bb->filename)+1 + 7) & ~7); } static inline unsigned long dump_size(struct gcov_ftree_node *tptr) { return (hdr_ofs(tptr) + (tptr->bb->ncounts+1)*8); } static void store_long (long value, void *buf) { const int bytes = 8; char dest[10]; int upper_bit = (value < 0 ? 128 : 0); size_t i; if (value < 0) { long oldvalue = value; value = -value; if (oldvalue != -value) return; } for(i = 0 ; i < (sizeof (value) < bytes ? sizeof (value) : bytes) ; i++) { dest[i] = value & (i == (bytes - 1) ? 127 : 255); value = value / 256; } if (value && value != -1) return; for(; i < bytes ; i++) dest[i] = 0; dest[bytes - 1] |= upper_bit; memcpy(buf,dest,bytes); } int create_dir_proc(struct gcov_ftree_node *bt, char *fname) { bt->proc[0] = proc_mkdir(fname, bt->parent->proc[0]); bt->proc[1] = bt->proc[2] = bt->proc[3] = NULL; return (bt->proc[0] == NULL); } static char* replace_ending(const char *fname,char *end, char *newend) { char *newfname; char *cptr = strstr(fname,end); int len; if (cptr == NULL) return NULL; len = cptr - fname; newfname = (char*)kmalloc(len+strlen(newend)+1,GFP_KERNEL); if (newfname == NULL) return NULL; memcpy(newfname,fname,len); strcpy(newfname+len,newend); return newfname; } int create_file_proc(struct gcov_ftree_node *bt, struct bb *bptr, char *fname, const char *fullname) { bt->proc[0] = create_proc_entry(fname, S_IWUSR | S_IRUGO, bt->parent->proc[0]); if (!bt->proc[0]) { printk("error creating file proc <%s>\n", fname); return 1; } bt->proc[0]->proc_fops = &proc_gcov_operations; bt->proc[0]->size = 8 + (8 * bptr->ncounts); if (create_bb_links) { int i; for (i=0;i<3;i++) { char *newfname; char *newfullname; newfname = replace_ending(fname,".da",endings[i]); newfullname = replace_ending(fullname,".da",endings[i]); if ((newfname) && (newfullname)) { bt->proc[i+1] = proc_symlink(newfname,bt->parent->proc[0],newfullname); } if (newfname) kfree(newfname); if (newfullname) kfree(newfullname); } } else { bt->proc[1] = bt->proc[2] = bt->proc[3] = NULL; } return 0; } void check_proc_fs(const char *fullname, struct gcov_ftree_node *parent, char *name, struct bb *bbptr) { char dirname[128]; char *localname = name; char *tname; int isdir; struct gcov_ftree_node *tptr; tname = strstr(name, "/"); if ((isdir = (tname != NULL))) { memcpy(dirname,name,tname-name); dirname[tname-name] = '\0'; localname = dirname; } /* search the list of files in gcov_ftree_node and * see whether file already exists in this directory level */ for ( tptr = parent->files ; tptr ; tptr = tptr->sibling) { if (!strcmp(tptr->fname,localname)) break; } if (!tptr) { /* no entry yet */ tptr = (struct gcov_ftree_node*) kmalloc(sizeof(struct gcov_ftree_node),GFP_KERNEL); tptr->parent = parent; if (!isdir) { if (create_file_proc(tptr, bbptr, localname,fullname)) { kfree(tptr); return; } tptr->bb = bbptr; tptr->proc[0]->data = tptr; tptr->next = leave_nodes; leave_nodes = tptr; } else { int len = strlen(dirname)+1; localname = (char*)kmalloc(len,GFP_KERNEL); strncpy(localname,dirname,len); if (create_dir_proc(tptr,localname)) { kfree(tptr); kfree(localname); return; } tptr->bb = NULL; tptr->proc[0]->data = NULL; tptr->next = NULL; } tptr->isdir = isdir; tptr->fname = localname; tptr->files = NULL; tptr->sibling = parent->files; parent->files = tptr; } if (isdir) check_proc_fs(fullname,tptr,tname+1,bbptr); } void walk_tree(struct gcov_ftree_node *root,char *buf,int depth) { struct gcov_ftree_node *bptr; char lbuf[128]; if (!root->isdir) { #if 0 int len = dump_size(root); printk("%d> %s/%s %d [%ld - %ld]\n", depth,buf,root->fname,len, root->offset,root->offset+len); #endif return; } sprintf(lbuf,"%s/%s",buf,root->fname); sprintf(lbuf,"%s/%s",buf,root->fname); for (bptr = root->files; bptr; bptr = bptr->sibling) { walk_tree(bptr,lbuf,depth+1); } } static ssize_t read_gcov(struct file *file, char *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; ssize_t read; long ncnt; struct bb *bbptr; long slen; long *wptr; struct gcov_ftree_node *treeptr; struct proc_dir_entry * de; struct inode *inode; int dumpall; int hdrofs; unsigned long poffs; MOD_INC_USE_COUNT; read = 0; hdrofs = 0; poffs = 0; inode = file->f_dentry->d_inode; de = (struct proc_dir_entry *) inode->u.generic_ip; dumpall = (de == proc_vmlinux); if (!dumpall) treeptr = (struct gcov_ftree_node*) (de ? de->data : NULL); else { if (dumpall_cached_node && (p >= dumpall_cached_node->offset)) { treeptr = dumpall_cached_node; } else treeptr = leave_nodes; while (treeptr) { struct gcov_ftree_node *next = treeptr->next; if ((next == NULL) || (p < next->offset)) { hdrofs = hdr_ofs(treeptr); poffs = treeptr->offset; break; } treeptr = next; } dumpall_cached_node = treeptr; } bbptr = treeptr ? treeptr->bb : NULL; if (bbptr == NULL) goto out; ncnt = bbptr->ncounts; p -= poffs; do { if (p < (hdrofs)) { slen = (strlen(treeptr->bb->filename)+8) & ~7; if (p >= 8) { if (slen > count) slen = count; memcpy(buf,&treeptr->bb->filename[p-8],slen); count-=slen;buf+= slen;read+=slen;p+= slen; continue; } wptr = &slen; } else if (p < (hdrofs + 8)) wptr = &ncnt; else if (p < (hdrofs) + (ncnt+1)*2*sizeof(long)) wptr = &bbptr->counts[(p/8)-1]; else break; /* do we have to write partial word */ if ((count < 8) || (p & 0x7)) { /* partial write */ printk("screw partial writes now\n"); break; } else { store_long(*wptr,buf); buf+=8;p+=8,count-=8;read+=8; } } while (count > 0); *ppos = p + poffs; out: MOD_DEC_USE_COUNT; return read; } static ssize_t write_gcov(struct file * file, const char * buf, size_t count, loff_t *ppos) { struct bb *ptr; struct proc_dir_entry * de; struct inode *inode; int resetall, i; struct gcov_ftree_node *tptr; MOD_INC_USE_COUNT; inode = file->f_dentry->d_inode; de = (struct proc_dir_entry *) inode->u.generic_ip; if (de == NULL) { MOD_DEC_USE_COUNT; return 0; } resetall = (de == proc_vmlinux); if (resetall) { for (ptr = bb_head; ptr != (struct bb *) 0; ptr = ptr->next) { int i; if (ptr->counts == NULL) continue; for (i = 0; i < ptr->ncounts; i++) ptr->counts[i]=0; } } else { tptr = (struct gcov_ftree_node*)(de->data); if (tptr == NULL) { MOD_DEC_USE_COUNT; return count; } ptr = tptr->bb; if (ptr->ncounts != 0) { for (i = 0; i < ptr->ncounts; i++) ptr->counts[i]=0; } } MOD_DEC_USE_COUNT; return count; } void do_create_bb (void) { extern long __CTOR_LIST__; typedef void (*func_ptr)(void) ; func_ptr *p = (func_ptr*) &__CTOR_LIST__; if ( p == NULL) { printk("No CTORS\n"); return; } for ( ; *p != (func_ptr) 0; p++) { printk("do_create_bb: %p\n", p); (*p) (); } } static struct file_operations proc_gcov_operations = { read: read_gcov, write: write_gcov }; int nctr = 1; int init_module() { const char *tmp; struct bb *bbptr; unsigned long offset = 0; struct gcov_ftree_node *tptr; printk("init module <%s>\n", GCOV_PROF_PROC); if (!bb_head) do_create_bb(); tree_root.proc[0] = proc_mkdir(GCOV_PROF_PROC, 0); kernel_path_len = strlen(gcov_kernelpath); for (bbptr = bb_head; bbptr ; bbptr = bbptr->next) { const char *filename = bbptr->filename; if (!strncmp (filename, gcov_kernelpath, kernel_path_len)) { // printk("%03d <%s>\n", nctr++, bbptr->filename); tmp =filename + kernel_path_len+1; if (*tmp == '0') continue; check_proc_fs(filename,&tree_root, (char*)tmp, bbptr); } } /* now inverse the pointers of the leave_nodes for caching purposes */ for (tptr = leave_nodes; tptr; tptr = tptr->next) { tptr->offset = offset; offset = dump_size(leave_nodes); } proc_vmlinux = create_proc_entry("vmlinux",S_IWUSR | S_IRUGO, tree_root.proc[0]); if (proc_vmlinux) proc_vmlinux->proc_fops = &proc_gcov_operations; walk_tree(&tree_root,"",0); return 0; } void cleanup_node(struct gcov_ftree_node *node, int delname) { struct gcov_ftree_node *next,*tptr; struct proc_dir_entry *par_proc; if (node->parent) par_proc = (struct proc_dir_entry*)(node->parent->proc[0]); else par_proc = &proc_root; if (node->isdir) { next = node->files; node->files = NULL; for (tptr = next ; tptr; ) { next = tptr->sibling; cleanup_node(tptr,1); kfree(tptr); tptr = next; } remove_proc_entry(node->fname, par_proc); if (delname) kfree(node->fname); } else { remove_proc_entry(node->fname, par_proc); if (create_bb_links) { int i; for (i=0;i<3;i++) { char *newfname; if (node->proc[i+1] == NULL) continue; newfname = replace_ending(node->fname,".da",endings[i]); if (newfname) { remove_proc_entry(newfname, par_proc); kfree(newfname); } } } } node->proc[0]->data = NULL; } void cleanup_module() { printk("remove module <%s>\n", GCOV_PROF_PROC); cleanup_node(&tree_root,0); }