881 lines · 20.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* *  This code exports profiling data as debugfs files to userspace. * *    Copyright IBM Corp. 2009 *    Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com> * *    Uses gcc-internal data definitions. *    Based on the gcov-kernel patch by: *		 Hubertus Franke <frankeh@us.ibm.com> *		 Nigel Hinds <nhinds@us.ibm.com> *		 Rajan Ravindran <rajancr@us.ibm.com> *		 Peter Oberparleiter <oberpar@linux.vnet.ibm.com> *		 Paul Larson *		 Yi CDL Yang */ #define pr_fmt(fmt)	"gcov: " fmt #include <linux/init.h>#include <linux/module.h>#include <linux/debugfs.h>#include <linux/fs.h>#include <linux/list.h>#include <linux/string.h>#include <linux/slab.h>#include <linux/mutex.h>#include <linux/seq_file.h>#include <linux/mm.h>#include "gcov.h" /** * struct gcov_node - represents a debugfs entry * @list: list head for child node list * @children: child nodes * @all: list head for list of all nodes * @parent: parent node * @loaded_info: array of pointers to profiling data sets for loaded object *   files. * @num_loaded: number of profiling data sets for loaded object files. * @unloaded_info: accumulated copy of profiling data sets for unloaded *   object files. Used only when gcov_persist=1. * @dentry: main debugfs entry, either a directory or data file * @links: associated symbolic links * @name: data file basename * * struct gcov_node represents an entity within the gcov/ subdirectory * of debugfs. There are directory and data file nodes. The latter represent * the actual synthesized data file plus any associated symbolic links which * are needed by the gcov tool to work correctly. */struct gcov_node {	struct list_head list;	struct list_head children;	struct list_head all;	struct gcov_node *parent;	struct gcov_info **loaded_info;	struct gcov_info *unloaded_info;	struct dentry *dentry;	struct dentry **links;	int num_loaded;	char name[];}; static const char objtree[] = OBJTREE;static const char srctree[] = SRCTREE;static struct gcov_node root_node;static LIST_HEAD(all_head);static DEFINE_MUTEX(node_lock); /* If non-zero, keep copies of profiling data for unloaded modules. */static int gcov_persist = 1; static int __init gcov_persist_setup(char *str){	unsigned long val; 	if (kstrtoul(str, 0, &val)) {		pr_warn("invalid gcov_persist parameter '%s'\n", str);		return 0;	}	gcov_persist = val;	pr_info("setting gcov_persist to %d\n", gcov_persist); 	return 1;}__setup("gcov_persist=", gcov_persist_setup); #define ITER_STRIDE	PAGE_SIZE /** * struct gcov_iterator - specifies current file position in logical records * @info: associated profiling data * @buffer: buffer containing file data * @size: size of buffer * @pos: current position in file */struct gcov_iterator {	struct gcov_info *info;	size_t size;	loff_t pos;	char buffer[] __counted_by(size);}; /** * gcov_iter_new - allocate and initialize profiling data iterator * @info: profiling data set to be iterated * * Return file iterator on success, %NULL otherwise. */static struct gcov_iterator *gcov_iter_new(struct gcov_info *info){	struct gcov_iterator *iter;	size_t size; 	/* Dry-run to get the actual buffer size. */	size = convert_to_gcda(NULL, info); 	iter = kvmalloc_flex(*iter, buffer, size);	if (!iter)		return NULL; 	iter->info = info;	iter->size = size;	convert_to_gcda(iter->buffer, info); 	return iter;}  /** * gcov_iter_free - free iterator data * @iter: file iterator */static void gcov_iter_free(struct gcov_iterator *iter){	kvfree(iter);} /** * gcov_iter_get_info - return profiling data set for given file iterator * @iter: file iterator */static 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 */static void gcov_iter_start(struct gcov_iterator *iter){	iter->pos = 0;} /** * 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. */static int gcov_iter_next(struct gcov_iterator *iter){	if (iter->pos < iter->size)		iter->pos += ITER_STRIDE; 	if (iter->pos >= iter->size)		return -EINVAL; 	return 0;} /** * 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. */static int gcov_iter_write(struct gcov_iterator *iter, struct seq_file *seq){	size_t len; 	if (iter->pos >= iter->size)		return -EINVAL; 	len = ITER_STRIDE;	if (iter->pos + len > iter->size)		len = iter->size - iter->pos; 	seq_write(seq, iter->buffer + iter->pos, len); 	return 0;} /* * seq_file.start() implementation for gcov data files. Note that the * gcov_iterator interface is designed to be more restrictive than seq_file * (no start from arbitrary position, etc.), to simplify the iterator * implementation. */static void *gcov_seq_start(struct seq_file *seq, loff_t *pos){	loff_t i; 	gcov_iter_start(seq->private);	for (i = 0; i < *pos; i++) {		if (gcov_iter_next(seq->private))			return NULL;	}	return seq->private;} /* seq_file.next() implementation for gcov data files. */static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos){	struct gcov_iterator *iter = data; 	(*pos)++;	if (gcov_iter_next(iter))		return NULL; 	return iter;} /* seq_file.show() implementation for gcov data files. */static int gcov_seq_show(struct seq_file *seq, void *data){	struct gcov_iterator *iter = data; 	if (gcov_iter_write(iter, seq))		return -EINVAL;	return 0;} static void gcov_seq_stop(struct seq_file *seq, void *data){	/* Unused. */} static const struct seq_operations gcov_seq_ops = {	.start	= gcov_seq_start,	.next	= gcov_seq_next,	.show	= gcov_seq_show,	.stop	= gcov_seq_stop,}; /* * Return a profiling data set associated with the given node. This is * either a data set for a loaded object file or a data set copy in case * all associated object files have been unloaded. */static struct gcov_info *get_node_info(struct gcov_node *node){	if (node->num_loaded > 0)		return node->loaded_info[0]; 	return node->unloaded_info;} /* * Return a newly allocated profiling data set which contains the sum of * all profiling data associated with the given node. */static struct gcov_info *get_accumulated_info(struct gcov_node *node){	struct gcov_info *info;	int i = 0; 	if (node->unloaded_info)		info = gcov_info_dup(node->unloaded_info);	else		info = gcov_info_dup(node->loaded_info[i++]);	if (!info)		return NULL;	for (; i < node->num_loaded; i++)		gcov_info_add(info, node->loaded_info[i]); 	return info;} /* * open() implementation for gcov data files. Create a copy of the profiling * data set and initialize the iterator and seq_file interface. */static int gcov_seq_open(struct inode *inode, struct file *file){	struct gcov_node *node = inode->i_private;	struct gcov_iterator *iter;	struct seq_file *seq;	struct gcov_info *info;	int rc = -ENOMEM; 	mutex_lock(&node_lock);	/*	 * Read from a profiling data copy to minimize reference tracking	 * complexity and concurrent access and to keep accumulating multiple	 * profiling data sets associated with one node simple.	 */	info = get_accumulated_info(node);	if (!info)		goto out_unlock;	iter = gcov_iter_new(info);	if (!iter)		goto err_free_info;	rc = seq_open(file, &gcov_seq_ops);	if (rc)		goto err_free_iter_info;	seq = file->private_data;	seq->private = iter;out_unlock:	mutex_unlock(&node_lock);	return rc; err_free_iter_info:	gcov_iter_free(iter);err_free_info:	gcov_info_free(info);	goto out_unlock;} /* * release() implementation for gcov data files. Release resources allocated * by open(). */static int gcov_seq_release(struct inode *inode, struct file *file){	struct gcov_iterator *iter;	struct gcov_info *info;	struct seq_file *seq; 	seq = file->private_data;	iter = seq->private;	info = gcov_iter_get_info(iter);	gcov_iter_free(iter);	gcov_info_free(info);	seq_release(inode, file); 	return 0;} /* * Find a node by the associated data file name. Needs to be called with * node_lock held. */static struct gcov_node *get_node_by_name(const char *name){	struct gcov_node *node;	struct gcov_info *info; 	list_for_each_entry(node, &all_head, all) {		info = get_node_info(node);		if (info && (strcmp(gcov_info_filename(info), name) == 0))			return node;	} 	return NULL;} /* * Reset all profiling data associated with the specified node. */static void reset_node(struct gcov_node *node){	int i; 	if (node->unloaded_info)		gcov_info_reset(node->unloaded_info);	for (i = 0; i < node->num_loaded; i++)		gcov_info_reset(node->loaded_info[i]);} static void remove_node(struct gcov_node *node); /* * write() implementation for gcov data files. Reset profiling data for the * corresponding file. If all associated object files have been unloaded, * remove the debug fs node as well. */static ssize_t gcov_seq_write(struct file *file, const char __user *addr,			      size_t len, loff_t *pos){	struct seq_file *seq;	struct gcov_info *info;	struct gcov_node *node; 	seq = file->private_data;	info = gcov_iter_get_info(seq->private);	mutex_lock(&node_lock);	node = get_node_by_name(gcov_info_filename(info));	if (node) {		/* Reset counts or remove node for unloaded modules. */		if (node->num_loaded == 0)			remove_node(node);		else			reset_node(node);	}	/* Reset counts for open file. */	gcov_info_reset(info);	mutex_unlock(&node_lock); 	return len;} /* * Given a string <path> representing a file path of format: *   path/to/file.gcda * construct and return a new string: *   <dir/>path/to/file.<ext> */static char *link_target(const char *dir, const char *path, const char *ext){	char *target;	char *old_ext;	char *copy; 	copy = kstrdup(path, GFP_KERNEL);	if (!copy)		return NULL;	old_ext = strrchr(copy, '.');	if (old_ext)		*old_ext = '\0';	if (dir)		target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);	else		target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);	kfree(copy); 	return target;} /* * Construct a string representing the symbolic link target for the given * gcov data file name and link type. Depending on the link type and the * location of the data file, the link target can either point to a * subdirectory of srctree, objtree or in an external location. */static char *get_link_target(const char *filename, const struct gcov_link *ext){	const char *rel;	char *result; 	if (strncmp(filename, objtree, strlen(objtree)) == 0) {		rel = filename + strlen(objtree) + 1;		if (ext->dir == SRC_TREE)			result = link_target(srctree, rel, ext->ext);		else			result = link_target(objtree, rel, ext->ext);	} else {		/* External compilation. */		result = link_target(NULL, filename, ext->ext);	} 	return result;} #define SKEW_PREFIX	".tmp_" /* * For a filename .tmp_filename.ext return filename.ext. Needed to compensate * for filename skewing caused by the mod-versioning mechanism. */static const char *deskew(const char *basename){	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)		return basename + sizeof(SKEW_PREFIX) - 1;	return basename;} /* * Create links to additional files (usually .c and .gcno files) which the * gcov tool expects to find in the same directory as the gcov data file. */static void add_links(struct gcov_node *node, struct dentry *parent){	const char *basename;	char *target;	int num;	int i; 	for (num = 0; gcov_link[num].ext; num++)		/* Nothing. */;	node->links = kzalloc_objs(struct dentry *, num);	if (!node->links)		return;	for (i = 0; i < num; i++) {		target = get_link_target(				gcov_info_filename(get_node_info(node)),				&gcov_link[i]);		if (!target)			goto out_err;		basename = kbasename(target);		if (basename == target)			goto out_err;		node->links[i] = debugfs_create_symlink(deskew(basename),							parent,	target);		kfree(target);	}

Lines 1–500 of 881.