// SPDX-License-Identifier: GPL-2.0 /* * This code exports profiling data as debugfs files to userspace. * * Copyright IBM Corp. 2009 * Author(s): Peter Oberparleiter <[email protected]> * * Uses gcc-internal data definitions. * Based on the gcov-kernel patch by: * Hubertus Franke <[email protected]> * Nigel Hinds <[email protected]> * Rajan Ravindran <[email protected]> * Peter Oberparleiter <[email protected]> * Paul Larson * Yi CDL Yang */ #define pr_fmt(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 { … }; static const char objtree[] = …; static const char 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 = …; static int __init gcov_persist_setup(char *str) { … } __setup(…); #define ITER_STRIDE … /** * 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 { … }; /** * 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) { … } /** * gcov_iter_free - free iterator data * @iter: file iterator */ static void gcov_iter_free(struct gcov_iterator *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) { … } /** * gcov_iter_start - reset file iterator to starting position * @iter: file iterator */ static void gcov_iter_start(struct gcov_iterator *iter) { … } /** * 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) { … } /** * 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) { … } /* * 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) { … } /* seq_file.next() implementation for gcov data files. */ static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos) { … } /* seq_file.show() implementation for gcov data files. */ static int gcov_seq_show(struct seq_file *seq, void *data) { … } static void gcov_seq_stop(struct seq_file *seq, void *data) { … } static const struct seq_operations gcov_seq_ops = …; /* * 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) { … } /* * 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) { … } /* * 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) { … } /* * release() implementation for gcov data files. Release resources allocated * by open(). */ static int gcov_seq_release(struct inode *inode, struct file *file) { … } /* * 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) { … } /* * Reset all profiling data associated with the specified node. */ static void reset_node(struct gcov_node *node) { … } 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) { … } /* * 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) { … } /* * 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) { … } #define SKEW_PREFIX … /* * 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) { … } /* * 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) { … } static const struct file_operations gcov_data_fops = …; /* Basic initialization of a new node. */ static void init_node(struct gcov_node *node, struct gcov_info *info, const char *name, struct gcov_node *parent) { … } /* * Create a new node and associated debugfs entry. Needs to be called with * node_lock held. */ static struct gcov_node *new_node(struct gcov_node *parent, struct gcov_info *info, const char *name) { … } /* Remove symbolic links associated with node. */ static void remove_links(struct gcov_node *node) { … } /* * Remove node from all lists and debugfs and release associated resources. * Needs to be called with node_lock held. */ static void release_node(struct gcov_node *node) { … } /* Release node and empty parents. Needs to be called with node_lock held. */ static void remove_node(struct gcov_node *node) { … } /* * Find child node with given basename. Needs to be called with node_lock * held. */ static struct gcov_node *get_child_by_name(struct gcov_node *parent, const char *name) { … } /* * write() implementation for reset file. Reset all profiling data to zero * and remove nodes for which all associated object files are unloaded. */ static ssize_t reset_write(struct file *file, const char __user *addr, size_t len, loff_t *pos) { … } /* read() implementation for reset file. Unused. */ static ssize_t reset_read(struct file *file, char __user *addr, size_t len, loff_t *pos) { … } static const struct file_operations gcov_reset_fops = …; /* * Create a node for a given profiling data set and add it to all lists and * debugfs. Needs to be called with node_lock held. */ static void add_node(struct gcov_info *info) { … } /* * Associate a profiling data set with an existing node. Needs to be called * with node_lock held. */ static void add_info(struct gcov_node *node, struct gcov_info *info) { … } /* * Return the index of a profiling data set associated with a node. */ static int get_info_index(struct gcov_node *node, struct gcov_info *info) { … } /* * Save the data of a profiling data set which is being unloaded. */ static void save_info(struct gcov_node *node, struct gcov_info *info) { … } /* * Disassociate a profiling data set from a node. Needs to be called with * node_lock held. */ static void remove_info(struct gcov_node *node, struct gcov_info *info) { … } /* * Callback to create/remove profiling files when code compiled with * -fprofile-arcs is loaded/unloaded. */ void gcov_event(enum gcov_action action, struct gcov_info *info) { … } /* Create debugfs entries. */ static __init int gcov_fs_init(void) { … } device_initcall(gcov_fs_init);