// 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. Load more
// 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); }