15426539cSMatt Macy // SPDX-License-Identifier: GPL-2.0
28ea23c2bSMatt Macy // This program is free software; you can redistribute it and/or
38ea23c2bSMatt Macy // modify it under the terms of the GNU General Public License
4*9e23ca1cSBrooks Davis // as published by the Free Software Foundation; version 2.
58ea23c2bSMatt Macy //
68ea23c2bSMatt Macy // This program is distributed in the hope that it will be useful,
78ea23c2bSMatt Macy // but WITHOUT ANY WARRANTY; without even the implied warranty of
88ea23c2bSMatt Macy // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
98ea23c2bSMatt Macy // GNU General Public License for more details.
108ea23c2bSMatt Macy //
118ea23c2bSMatt Macy // You should have received a copy of the GNU General Public License
128ea23c2bSMatt Macy // along with this program; if not, write to the Free Software
138ea23c2bSMatt Macy // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
148ea23c2bSMatt Macy // 02110-1301, USA.
155426539cSMatt Macy /*
165426539cSMatt Macy * This code exports profiling data as debugfs files to userspace.
175426539cSMatt Macy *
185426539cSMatt Macy * Copyright IBM Corp. 2009
195426539cSMatt Macy * Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
205426539cSMatt Macy *
215426539cSMatt Macy * Uses gcc-internal data definitions.
225426539cSMatt Macy * Based on the gcov-kernel patch by:
235426539cSMatt Macy * Hubertus Franke <frankeh@us.ibm.com>
245426539cSMatt Macy * Nigel Hinds <nhinds@us.ibm.com>
255426539cSMatt Macy * Rajan Ravindran <rajancr@us.ibm.com>
265426539cSMatt Macy * Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
275426539cSMatt Macy * Paul Larson
285426539cSMatt Macy * Yi CDL Yang
295426539cSMatt Macy */
305426539cSMatt Macy
315426539cSMatt Macy #include <sys/types.h>
325426539cSMatt Macy #include <sys/systm.h>
335426539cSMatt Macy #include <sys/param.h>
345426539cSMatt Macy #include <sys/sbuf.h>
355426539cSMatt Macy
365426539cSMatt Macy #include <sys/queue.h>
375426539cSMatt Macy #include <sys/linker.h>
385426539cSMatt Macy #include <sys/module.h>
395426539cSMatt Macy #include <sys/eventhandler.h>
405426539cSMatt Macy #include <sys/kernel.h>
415426539cSMatt Macy #include <sys/malloc.h>
425426539cSMatt Macy #include <sys/syslog.h>
435426539cSMatt Macy #include <sys/proc.h>
445426539cSMatt Macy #include <sys/sched.h>
455426539cSMatt Macy #include <sys/syslog.h>
465426539cSMatt Macy #include <sys/sysctl.h>
475426539cSMatt Macy #include <linux/debugfs.h>
485426539cSMatt Macy
495426539cSMatt Macy #include <gnu/gcov/gcov.h>
505426539cSMatt Macy #include <sys/queue.h>
515426539cSMatt Macy
525426539cSMatt Macy extern int gcov_events_enabled;
535426539cSMatt Macy static int gcov_persist;
545426539cSMatt Macy static struct mtx gcov_mtx;
555426539cSMatt Macy MTX_SYSINIT(gcov_init, &gcov_mtx, "gcov_mtx", MTX_DEF);
565426539cSMatt Macy MALLOC_DEFINE(M_GCOV, "gcov", "gcov");
575426539cSMatt Macy
585426539cSMatt Macy void __gcov_init(struct gcov_info *info);
595426539cSMatt Macy void __gcov_flush(void);
605426539cSMatt Macy void __gcov_merge_add(gcov_type *counters, unsigned int n_counters);
615426539cSMatt Macy void __gcov_merge_single(gcov_type *counters, unsigned int n_counters);
625426539cSMatt Macy void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters);
635426539cSMatt Macy void __gcov_merge_ior(gcov_type *counters, unsigned int n_counters);
645426539cSMatt Macy void __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters);
655426539cSMatt Macy void __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters);
665426539cSMatt Macy void __gcov_exit(void);
675426539cSMatt Macy
685426539cSMatt Macy static void gcov_event(enum gcov_action action, struct gcov_info *info);
695426539cSMatt Macy
705426539cSMatt Macy
715426539cSMatt Macy /*
725426539cSMatt Macy * Private copy taken from libc
735426539cSMatt Macy */
745426539cSMatt Macy static char *
755426539cSMatt Macy (basename)(char *path)
765426539cSMatt Macy {
775426539cSMatt Macy char *ptr;
785426539cSMatt Macy
795426539cSMatt Macy /*
805426539cSMatt Macy * If path is a null pointer or points to an empty string,
815426539cSMatt Macy * basename() shall return a pointer to the string ".".
825426539cSMatt Macy */
835426539cSMatt Macy if (path == NULL || *path == '\0')
845426539cSMatt Macy return (__DECONST(char *, "."));
855426539cSMatt Macy
865426539cSMatt Macy /* Find end of last pathname component and null terminate it. */
875426539cSMatt Macy ptr = path + strlen(path);
885426539cSMatt Macy while (ptr > path + 1 && *(ptr - 1) == '/')
895426539cSMatt Macy --ptr;
905426539cSMatt Macy *ptr-- = '\0';
915426539cSMatt Macy
925426539cSMatt Macy /* Find beginning of last pathname component. */
935426539cSMatt Macy while (ptr > path && *(ptr - 1) != '/')
945426539cSMatt Macy --ptr;
955426539cSMatt Macy return (ptr);
965426539cSMatt Macy }
975426539cSMatt Macy
985426539cSMatt Macy /*
995426539cSMatt Macy * __gcov_init is called by gcc-generated constructor code for each object
1005426539cSMatt Macy * file compiled with -fprofile-arcs.
1015426539cSMatt Macy */
1025426539cSMatt Macy void
__gcov_init(struct gcov_info * info)1035426539cSMatt Macy __gcov_init(struct gcov_info *info)
1045426539cSMatt Macy {
1055426539cSMatt Macy static unsigned int gcov_version;
1065426539cSMatt Macy
1075426539cSMatt Macy mtx_lock(&gcov_mtx);
1085426539cSMatt Macy if (gcov_version == 0) {
1095426539cSMatt Macy gcov_version = gcov_info_version(info);
1105426539cSMatt Macy /*
1115426539cSMatt Macy * Printing gcc's version magic may prove useful for debugging
1125426539cSMatt Macy * incompatibility reports.
1135426539cSMatt Macy */
1145426539cSMatt Macy log(LOG_INFO, "version magic: 0x%x\n", gcov_version);
1155426539cSMatt Macy }
1165426539cSMatt Macy /*
1175426539cSMatt Macy * Add new profiling data structure to list and inform event
1185426539cSMatt Macy * listener.
1195426539cSMatt Macy */
1205426539cSMatt Macy gcov_info_link(info);
1215426539cSMatt Macy if (gcov_events_enabled)
1225426539cSMatt Macy gcov_event(GCOV_ADD, info);
1235426539cSMatt Macy mtx_unlock(&gcov_mtx);
1245426539cSMatt Macy }
1255426539cSMatt Macy
1265426539cSMatt Macy /*
1275426539cSMatt Macy * These functions may be referenced by gcc-generated profiling code but serve
1285426539cSMatt Macy * no function for kernel profiling.
1295426539cSMatt Macy */
1305426539cSMatt Macy void
__gcov_flush(void)1315426539cSMatt Macy __gcov_flush(void)
1325426539cSMatt Macy {
1335426539cSMatt Macy /* Unused. */
1345426539cSMatt Macy }
1355426539cSMatt Macy
1365426539cSMatt Macy void
__gcov_merge_add(gcov_type * counters,unsigned int n_counters)1375426539cSMatt Macy __gcov_merge_add(gcov_type *counters, unsigned int n_counters)
1385426539cSMatt Macy {
1395426539cSMatt Macy /* Unused. */
1405426539cSMatt Macy }
1415426539cSMatt Macy
1425426539cSMatt Macy void
__gcov_merge_single(gcov_type * counters,unsigned int n_counters)1435426539cSMatt Macy __gcov_merge_single(gcov_type *counters, unsigned int n_counters)
1445426539cSMatt Macy {
1455426539cSMatt Macy /* Unused. */
1465426539cSMatt Macy }
1475426539cSMatt Macy
1485426539cSMatt Macy void
__gcov_merge_delta(gcov_type * counters,unsigned int n_counters)1495426539cSMatt Macy __gcov_merge_delta(gcov_type *counters, unsigned int n_counters)
1505426539cSMatt Macy {
1515426539cSMatt Macy /* Unused. */
1525426539cSMatt Macy }
1535426539cSMatt Macy
1545426539cSMatt Macy void
__gcov_merge_ior(gcov_type * counters,unsigned int n_counters)1555426539cSMatt Macy __gcov_merge_ior(gcov_type *counters, unsigned int n_counters)
1565426539cSMatt Macy {
1575426539cSMatt Macy /* Unused. */
1585426539cSMatt Macy }
1595426539cSMatt Macy
1605426539cSMatt Macy void
__gcov_merge_time_profile(gcov_type * counters,unsigned int n_counters)1615426539cSMatt Macy __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters)
1625426539cSMatt Macy {
1635426539cSMatt Macy /* Unused. */
1645426539cSMatt Macy }
1655426539cSMatt Macy
1665426539cSMatt Macy void
__gcov_merge_icall_topn(gcov_type * counters,unsigned int n_counters)1675426539cSMatt Macy __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters)
1685426539cSMatt Macy {
1695426539cSMatt Macy /* Unused. */
1705426539cSMatt Macy }
1715426539cSMatt Macy
1725426539cSMatt Macy void
__gcov_exit(void)1735426539cSMatt Macy __gcov_exit(void)
1745426539cSMatt Macy {
1755426539cSMatt Macy /* Unused. */
1765426539cSMatt Macy }
1775426539cSMatt Macy
1785426539cSMatt Macy
1795426539cSMatt Macy /**
1805426539cSMatt Macy * struct gcov_node - represents a debugfs entry
1815426539cSMatt Macy * @entry: list entry for parent's child node list
1825426539cSMatt Macy * @children: child nodes
1835426539cSMatt Macy * @all_entry: list entry for list of all nodes
1845426539cSMatt Macy * @parent: parent node
1855426539cSMatt Macy * @loaded_info: array of pointers to profiling data sets for loaded object
1865426539cSMatt Macy * files.
1875426539cSMatt Macy * @num_loaded: number of profiling data sets for loaded object files.
1885426539cSMatt Macy * @unloaded_info: accumulated copy of profiling data sets for unloaded
1895426539cSMatt Macy * object files. Used only when gcov_persist=1.
1905426539cSMatt Macy * @dentry: main debugfs entry, either a directory or data file
1915426539cSMatt Macy * @links: associated symbolic links
1925426539cSMatt Macy * @name: data file basename
1935426539cSMatt Macy *
1945426539cSMatt Macy * struct gcov_node represents an entity within the gcov/ subdirectory
1955426539cSMatt Macy * of debugfs. There are directory and data file nodes. The latter represent
1965426539cSMatt Macy * the actual synthesized data file plus any associated symbolic links which
1975426539cSMatt Macy * are needed by the gcov tool to work correctly.
1985426539cSMatt Macy */
1995426539cSMatt Macy struct gcov_node {
2005426539cSMatt Macy LIST_ENTRY(gcov_node) children_entry;
2015426539cSMatt Macy LIST_ENTRY(gcov_node) all_entry;
2025426539cSMatt Macy struct {
2035426539cSMatt Macy struct gcov_node *lh_first;
2045426539cSMatt Macy } children;
2055426539cSMatt Macy struct gcov_node *parent;
2065426539cSMatt Macy struct gcov_info **loaded_info;
2075426539cSMatt Macy struct gcov_info *unloaded_info;
2085426539cSMatt Macy struct dentry *dentry;
2095426539cSMatt Macy struct dentry **links;
2105426539cSMatt Macy int num_loaded;
2115426539cSMatt Macy char name[0];
2125426539cSMatt Macy };
2135426539cSMatt Macy
2145426539cSMatt Macy #ifdef notyet
2155426539cSMatt Macy static const char objtree[] = OBJTREE;
2165426539cSMatt Macy static const char srctree[] = SRCTREE;
2175426539cSMatt Macy #else
2185426539cSMatt Macy static const char objtree[] = "";
2195426539cSMatt Macy static const char srctree[] = "";
2205426539cSMatt Macy #endif
2215426539cSMatt Macy static struct gcov_node root_node;
2225426539cSMatt Macy static struct {
2235426539cSMatt Macy struct gcov_node *lh_first;
2245426539cSMatt Macy } all_head;
2255426539cSMatt Macy static struct mtx node_lock;
2265426539cSMatt Macy MTX_SYSINIT(node_init, &node_lock, "node_lock", MTX_DEF);
2275426539cSMatt Macy static void remove_node(struct gcov_node *node);
2285426539cSMatt Macy
2295426539cSMatt Macy /*
2305426539cSMatt Macy * seq_file.start() implementation for gcov data files. Note that the
2315426539cSMatt Macy * gcov_iterator interface is designed to be more restrictive than seq_file
2325426539cSMatt Macy * (no start from arbitrary position, etc.), to simplify the iterator
2335426539cSMatt Macy * implementation.
2345426539cSMatt Macy */
2355426539cSMatt Macy static void *
gcov_seq_start(struct seq_file * seq,off_t * pos)2365426539cSMatt Macy gcov_seq_start(struct seq_file *seq, off_t *pos)
2375426539cSMatt Macy {
2385426539cSMatt Macy off_t i;
2395426539cSMatt Macy
2405426539cSMatt Macy gcov_iter_start(seq->private);
2415426539cSMatt Macy for (i = 0; i < *pos; i++) {
2425426539cSMatt Macy if (gcov_iter_next(seq->private))
2435426539cSMatt Macy return NULL;
2445426539cSMatt Macy }
2455426539cSMatt Macy return seq->private;
2465426539cSMatt Macy }
2475426539cSMatt Macy
2485426539cSMatt Macy /* seq_file.next() implementation for gcov data files. */
2495426539cSMatt Macy static void *
gcov_seq_next(struct seq_file * seq,void * data,off_t * pos)2505426539cSMatt Macy gcov_seq_next(struct seq_file *seq, void *data, off_t *pos)
2515426539cSMatt Macy {
2525426539cSMatt Macy struct gcov_iterator *iter = data;
2535426539cSMatt Macy
2545426539cSMatt Macy if (gcov_iter_next(iter))
2555426539cSMatt Macy return NULL;
2565426539cSMatt Macy (*pos)++;
2575426539cSMatt Macy
2585426539cSMatt Macy return iter;
2595426539cSMatt Macy }
2605426539cSMatt Macy
2615426539cSMatt Macy /* seq_file.show() implementation for gcov data files. */
2625426539cSMatt Macy static int
gcov_seq_show(struct seq_file * seq,void * data)2635426539cSMatt Macy gcov_seq_show(struct seq_file *seq, void *data)
2645426539cSMatt Macy {
2655426539cSMatt Macy struct gcov_iterator *iter = data;
2665426539cSMatt Macy
2675426539cSMatt Macy if (gcov_iter_write(iter, seq->buf))
2685426539cSMatt Macy return (-EINVAL);
2695426539cSMatt Macy return (0);
2705426539cSMatt Macy }
2715426539cSMatt Macy
2725426539cSMatt Macy static void
gcov_seq_stop(struct seq_file * seq,void * data)2735426539cSMatt Macy gcov_seq_stop(struct seq_file *seq, void *data)
2745426539cSMatt Macy {
2755426539cSMatt Macy /* Unused. */
2765426539cSMatt Macy }
2775426539cSMatt Macy
2785426539cSMatt Macy static const struct seq_operations gcov_seq_ops = {
2795426539cSMatt Macy .start = gcov_seq_start,
2805426539cSMatt Macy .next = gcov_seq_next,
2815426539cSMatt Macy .show = gcov_seq_show,
2825426539cSMatt Macy .stop = gcov_seq_stop,
2835426539cSMatt Macy };
2845426539cSMatt Macy
2855426539cSMatt Macy /*
2865426539cSMatt Macy * Return a profiling data set associated with the given node. This is
2875426539cSMatt Macy * either a data set for a loaded object file or a data set copy in case
2885426539cSMatt Macy * all associated object files have been unloaded.
2895426539cSMatt Macy */
2905426539cSMatt Macy static struct gcov_info *
get_node_info(struct gcov_node * node)2915426539cSMatt Macy get_node_info(struct gcov_node *node)
2925426539cSMatt Macy {
2935426539cSMatt Macy if (node->num_loaded > 0)
2945426539cSMatt Macy return (node->loaded_info[0]);
2955426539cSMatt Macy
2965426539cSMatt Macy return (node->unloaded_info);
2975426539cSMatt Macy }
2985426539cSMatt Macy
2995426539cSMatt Macy /*
3005426539cSMatt Macy * Return a newly allocated profiling data set which contains the sum of
3015426539cSMatt Macy * all profiling data associated with the given node.
3025426539cSMatt Macy */
3035426539cSMatt Macy static struct gcov_info *
get_accumulated_info(struct gcov_node * node)3045426539cSMatt Macy get_accumulated_info(struct gcov_node *node)
3055426539cSMatt Macy {
3065426539cSMatt Macy struct gcov_info *info;
3075426539cSMatt Macy int i = 0;
3085426539cSMatt Macy
3095426539cSMatt Macy if (node->unloaded_info)
3105426539cSMatt Macy info = gcov_info_dup(node->unloaded_info);
3115426539cSMatt Macy else
3125426539cSMatt Macy info = gcov_info_dup(node->loaded_info[i++]);
3135426539cSMatt Macy if (info == NULL)
3145426539cSMatt Macy return (NULL);
3155426539cSMatt Macy for (; i < node->num_loaded; i++)
3165426539cSMatt Macy gcov_info_add(info, node->loaded_info[i]);
3175426539cSMatt Macy
3185426539cSMatt Macy return (info);
3195426539cSMatt Macy }
3205426539cSMatt Macy
3215426539cSMatt Macy /*
3225426539cSMatt Macy * open() implementation for gcov data files. Create a copy of the profiling
3235426539cSMatt Macy * data set and initialize the iterator and seq_file interface.
3245426539cSMatt Macy */
3255426539cSMatt Macy static int
gcov_seq_open(struct inode * inode,struct file * file)3265426539cSMatt Macy gcov_seq_open(struct inode *inode, struct file *file)
3275426539cSMatt Macy {
3285426539cSMatt Macy struct gcov_node *node = inode->i_private;
3295426539cSMatt Macy struct gcov_iterator *iter;
3305426539cSMatt Macy struct seq_file *seq;
3315426539cSMatt Macy struct gcov_info *info;
3325426539cSMatt Macy int rc = -ENOMEM;
3335426539cSMatt Macy
3345426539cSMatt Macy mtx_lock(&node_lock);
3355426539cSMatt Macy /*
3365426539cSMatt Macy * Read from a profiling data copy to minimize reference tracking
3375426539cSMatt Macy * complexity and concurrent access and to keep accumulating multiple
3385426539cSMatt Macy * profiling data sets associated with one node simple.
3395426539cSMatt Macy */
3405426539cSMatt Macy info = get_accumulated_info(node);
3415426539cSMatt Macy if (info == NULL)
3425426539cSMatt Macy goto out_unlock;
3435426539cSMatt Macy iter = gcov_iter_new(info);
3445426539cSMatt Macy if (iter == NULL)
3455426539cSMatt Macy goto err_free_info;
3465426539cSMatt Macy rc = seq_open(file, &gcov_seq_ops);
3475426539cSMatt Macy if (rc)
3485426539cSMatt Macy goto err_free_iter_info;
3495426539cSMatt Macy seq = file->private_data;
3505426539cSMatt Macy seq->private = iter;
3515426539cSMatt Macy out_unlock:
3525426539cSMatt Macy mtx_unlock(&node_lock);
3535426539cSMatt Macy return (rc);
3545426539cSMatt Macy
3555426539cSMatt Macy err_free_iter_info:
3565426539cSMatt Macy gcov_iter_free(iter);
3575426539cSMatt Macy err_free_info:
3585426539cSMatt Macy gcov_info_free(info);
3595426539cSMatt Macy goto out_unlock;
3605426539cSMatt Macy }
3615426539cSMatt Macy
3625426539cSMatt Macy /*
3635426539cSMatt Macy * release() implementation for gcov data files. Release resources allocated
3645426539cSMatt Macy * by open().
3655426539cSMatt Macy */
3665426539cSMatt Macy static int
gcov_seq_release(struct inode * inode,struct file * file)3675426539cSMatt Macy gcov_seq_release(struct inode *inode, struct file *file)
3685426539cSMatt Macy {
3695426539cSMatt Macy struct gcov_iterator *iter;
3705426539cSMatt Macy struct gcov_info *info;
3715426539cSMatt Macy struct seq_file *seq;
3725426539cSMatt Macy
3735426539cSMatt Macy seq = file->private_data;
3745426539cSMatt Macy iter = seq->private;
3755426539cSMatt Macy info = gcov_iter_get_info(iter);
3765426539cSMatt Macy gcov_iter_free(iter);
3775426539cSMatt Macy gcov_info_free(info);
3785426539cSMatt Macy seq_release(inode, file);
3795426539cSMatt Macy
3805426539cSMatt Macy return (0);
3815426539cSMatt Macy }
3825426539cSMatt Macy
3835426539cSMatt Macy /*
3845426539cSMatt Macy * Find a node by the associated data file name. Needs to be called with
3855426539cSMatt Macy * node_lock held.
3865426539cSMatt Macy */
3875426539cSMatt Macy static struct gcov_node *
get_node_by_name(const char * name)3885426539cSMatt Macy get_node_by_name(const char *name)
3895426539cSMatt Macy {
3905426539cSMatt Macy struct gcov_node *node;
3915426539cSMatt Macy struct gcov_info *info;
3925426539cSMatt Macy
3935426539cSMatt Macy LIST_FOREACH(node, &all_head, all_entry) {
3945426539cSMatt Macy info = get_node_info(node);
3955426539cSMatt Macy if (info && (strcmp(gcov_info_filename(info), name) == 0))
3965426539cSMatt Macy return (node);
3975426539cSMatt Macy }
3985426539cSMatt Macy
3995426539cSMatt Macy return (NULL);
4005426539cSMatt Macy }
4015426539cSMatt Macy
4025426539cSMatt Macy /*
4035426539cSMatt Macy * Reset all profiling data associated with the specified node.
4045426539cSMatt Macy */
4055426539cSMatt Macy static void
reset_node(struct gcov_node * node)4065426539cSMatt Macy reset_node(struct gcov_node *node)
4075426539cSMatt Macy {
4085426539cSMatt Macy int i;
4095426539cSMatt Macy
4105426539cSMatt Macy if (node->unloaded_info)
4115426539cSMatt Macy gcov_info_reset(node->unloaded_info);
4125426539cSMatt Macy for (i = 0; i < node->num_loaded; i++)
4135426539cSMatt Macy gcov_info_reset(node->loaded_info[i]);
4145426539cSMatt Macy }
4155426539cSMatt Macy
4165426539cSMatt Macy void
gcov_stats_reset(void)4175426539cSMatt Macy gcov_stats_reset(void)
4185426539cSMatt Macy {
4195426539cSMatt Macy struct gcov_node *node;
4205426539cSMatt Macy
4215426539cSMatt Macy mtx_lock(&node_lock);
4225426539cSMatt Macy restart:
4235426539cSMatt Macy LIST_FOREACH(node, &all_head, all_entry) {
4245426539cSMatt Macy if (node->num_loaded > 0)
4255426539cSMatt Macy reset_node(node);
4265426539cSMatt Macy else if (LIST_EMPTY(&node->children)) {
4275426539cSMatt Macy remove_node(node);
4285426539cSMatt Macy goto restart;
4295426539cSMatt Macy }
4305426539cSMatt Macy }
4315426539cSMatt Macy mtx_unlock(&node_lock);
4325426539cSMatt Macy }
4335426539cSMatt Macy
4345426539cSMatt Macy /*
4355426539cSMatt Macy * write() implementation for gcov data files. Reset profiling data for the
4365426539cSMatt Macy * corresponding file. If all associated object files have been unloaded,
4375426539cSMatt Macy * remove the debug fs node as well.
4385426539cSMatt Macy */
4395426539cSMatt Macy static ssize_t
gcov_seq_write(struct file * file,const char * addr,size_t len,off_t * pos)4405426539cSMatt Macy gcov_seq_write(struct file *file, const char *addr, size_t len, off_t *pos)
4415426539cSMatt Macy {
4425426539cSMatt Macy struct seq_file *seq;
4435426539cSMatt Macy struct gcov_info *info;
4445426539cSMatt Macy struct gcov_node *node;
4455426539cSMatt Macy
4465426539cSMatt Macy seq = file->private_data;
4475426539cSMatt Macy info = gcov_iter_get_info(seq->private);
4485426539cSMatt Macy mtx_lock(&node_lock);
4495426539cSMatt Macy node = get_node_by_name(gcov_info_filename(info));
4505426539cSMatt Macy if (node) {
4515426539cSMatt Macy /* Reset counts or remove node for unloaded modules. */
4525426539cSMatt Macy if (node->num_loaded == 0)
4535426539cSMatt Macy remove_node(node);
4545426539cSMatt Macy else
4555426539cSMatt Macy reset_node(node);
4565426539cSMatt Macy }
4575426539cSMatt Macy /* Reset counts for open file. */
4585426539cSMatt Macy gcov_info_reset(info);
4595426539cSMatt Macy mtx_unlock(&node_lock);
4605426539cSMatt Macy
4615426539cSMatt Macy return (len);
4625426539cSMatt Macy }
4635426539cSMatt Macy
4645426539cSMatt Macy /*
4655426539cSMatt Macy * Given a string <path> representing a file path of format:
4665426539cSMatt Macy * path/to/file.gcda
4675426539cSMatt Macy * construct and return a new string:
4685426539cSMatt Macy * <dir/>path/to/file.<ext>
4695426539cSMatt Macy */
4705426539cSMatt Macy static char *
link_target(const char * dir,const char * path,const char * ext)4715426539cSMatt Macy link_target(const char *dir, const char *path, const char *ext)
4725426539cSMatt Macy {
4735426539cSMatt Macy char *target;
4745426539cSMatt Macy char *old_ext;
4755426539cSMatt Macy char *copy;
4765426539cSMatt Macy
4775426539cSMatt Macy copy = strdup_flags(path, M_GCOV, M_NOWAIT);
4785426539cSMatt Macy if (!copy)
4795426539cSMatt Macy return (NULL);
4805426539cSMatt Macy old_ext = strrchr(copy, '.');
4815426539cSMatt Macy if (old_ext)
4825426539cSMatt Macy *old_ext = '\0';
4835426539cSMatt Macy target = NULL;
4845426539cSMatt Macy if (dir)
4855426539cSMatt Macy asprintf(&target, M_GCOV, "%s/%s.%s", dir, copy, ext);
4865426539cSMatt Macy else
4875426539cSMatt Macy asprintf(&target, M_GCOV, "%s.%s", copy, ext);
4885426539cSMatt Macy free(copy, M_GCOV);
4895426539cSMatt Macy
4905426539cSMatt Macy return (target);
4915426539cSMatt Macy }
4925426539cSMatt Macy
4935426539cSMatt Macy /*
4945426539cSMatt Macy * Construct a string representing the symbolic link target for the given
4955426539cSMatt Macy * gcov data file name and link type. Depending on the link type and the
4965426539cSMatt Macy * location of the data file, the link target can either point to a
4975426539cSMatt Macy * subdirectory of srctree, objtree or in an external location.
4985426539cSMatt Macy */
4995426539cSMatt Macy static char *
get_link_target(const char * filename,const struct gcov_link * ext)5005426539cSMatt Macy get_link_target(const char *filename, const struct gcov_link *ext)
5015426539cSMatt Macy {
5025426539cSMatt Macy const char *rel;
5035426539cSMatt Macy char *result;
5045426539cSMatt Macy
5055426539cSMatt Macy if (strncmp(filename, objtree, strlen(objtree)) == 0) {
5065426539cSMatt Macy rel = filename + strlen(objtree) + 1;
5075426539cSMatt Macy if (ext->dir == SRC_TREE)
5085426539cSMatt Macy result = link_target(srctree, rel, ext->ext);
5095426539cSMatt Macy else
5105426539cSMatt Macy result = link_target(objtree, rel, ext->ext);
5115426539cSMatt Macy } else {
5125426539cSMatt Macy /* External compilation. */
5135426539cSMatt Macy result = link_target(NULL, filename, ext->ext);
5145426539cSMatt Macy }
5155426539cSMatt Macy
5165426539cSMatt Macy return (result);
5175426539cSMatt Macy }
5185426539cSMatt Macy
5195426539cSMatt Macy #define SKEW_PREFIX ".tmp_"
5205426539cSMatt Macy
5215426539cSMatt Macy /*
5225426539cSMatt Macy * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
5235426539cSMatt Macy * for filename skewing caused by the mod-versioning mechanism.
5245426539cSMatt Macy */
5255426539cSMatt Macy static const char *
deskew(const char * basename)5265426539cSMatt Macy deskew(const char *basename)
5275426539cSMatt Macy {
5285426539cSMatt Macy if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
5295426539cSMatt Macy return (basename + sizeof(SKEW_PREFIX) - 1);
5305426539cSMatt Macy return (basename);
5315426539cSMatt Macy }
5325426539cSMatt Macy
5335426539cSMatt Macy /*
5345426539cSMatt Macy * Create links to additional files (usually .c and .gcno files) which the
5355426539cSMatt Macy * gcov tool expects to find in the same directory as the gcov data file.
5365426539cSMatt Macy */
5375426539cSMatt Macy static void
add_links(struct gcov_node * node,struct dentry * parent)5385426539cSMatt Macy add_links(struct gcov_node *node, struct dentry *parent)
5395426539cSMatt Macy {
5405426539cSMatt Macy const char *path_basename;
5415426539cSMatt Macy char *target;
5425426539cSMatt Macy int num;
5435426539cSMatt Macy int i;
5445426539cSMatt Macy
5455426539cSMatt Macy for (num = 0; gcov_link[num].ext; num++)
5465426539cSMatt Macy /* Nothing. */;
5475426539cSMatt Macy node->links = malloc((num*sizeof(struct dentry *)), M_GCOV, M_NOWAIT|M_ZERO);
5485426539cSMatt Macy if (node->links == NULL)
5495426539cSMatt Macy return;
5505426539cSMatt Macy for (i = 0; i < num; i++) {
5515426539cSMatt Macy target = get_link_target(
5525426539cSMatt Macy gcov_info_filename(get_node_info(node)),
5535426539cSMatt Macy &gcov_link[i]);
5545426539cSMatt Macy if (target == NULL)
5555426539cSMatt Macy goto out_err;
5565426539cSMatt Macy path_basename = basename(target);
5575426539cSMatt Macy if (path_basename == target)
5585426539cSMatt Macy goto out_err;
5595426539cSMatt Macy node->links[i] = debugfs_create_symlink(deskew(path_basename),
5605426539cSMatt Macy parent, target);
5615426539cSMatt Macy if (!node->links[i])
5625426539cSMatt Macy goto out_err;
5635426539cSMatt Macy free(target, M_GCOV);
5645426539cSMatt Macy }
5655426539cSMatt Macy
5665426539cSMatt Macy return;
5675426539cSMatt Macy out_err:
5685426539cSMatt Macy free(target, M_GCOV);
5695426539cSMatt Macy while (i-- > 0)
5705426539cSMatt Macy debugfs_remove(node->links[i]);
5715426539cSMatt Macy free(node->links, M_GCOV);
5725426539cSMatt Macy node->links = NULL;
5735426539cSMatt Macy }
5745426539cSMatt Macy
5755426539cSMatt Macy static const struct file_operations gcov_data_fops = {
5765426539cSMatt Macy .open = gcov_seq_open,
5775426539cSMatt Macy .release = gcov_seq_release,
5785426539cSMatt Macy .read = seq_read,
5795426539cSMatt Macy .llseek = seq_lseek,
5805426539cSMatt Macy .write = gcov_seq_write,
5815426539cSMatt Macy };
5825426539cSMatt Macy
5835426539cSMatt Macy /* Basic initialization of a new node. */
5845426539cSMatt Macy static void
init_node(struct gcov_node * node,struct gcov_info * info,const char * name,struct gcov_node * parent)5855426539cSMatt Macy init_node(struct gcov_node *node, struct gcov_info *info,
5865426539cSMatt Macy const char *name, struct gcov_node *parent)
5875426539cSMatt Macy {
5885426539cSMatt Macy LIST_INIT(&node->children);
5895426539cSMatt Macy if (node->loaded_info) {
5905426539cSMatt Macy node->loaded_info[0] = info;
5915426539cSMatt Macy node->num_loaded = 1;
5925426539cSMatt Macy }
5935426539cSMatt Macy node->parent = parent;
5945426539cSMatt Macy if (name)
5955426539cSMatt Macy strcpy(node->name, name);
5965426539cSMatt Macy }
5975426539cSMatt Macy
5985426539cSMatt Macy /*
5995426539cSMatt Macy * Create a new node and associated debugfs entry. Needs to be called with
6005426539cSMatt Macy * node_lock held.
6015426539cSMatt Macy */
6025426539cSMatt Macy static struct gcov_node *
new_node(struct gcov_node * parent,struct gcov_info * info,const char * name)6035426539cSMatt Macy new_node(struct gcov_node *parent, struct gcov_info *info, const char *name)
6045426539cSMatt Macy {
6055426539cSMatt Macy struct gcov_node *node;
6065426539cSMatt Macy
6075426539cSMatt Macy node = malloc(sizeof(struct gcov_node) + strlen(name) + 1, M_GCOV, M_NOWAIT|M_ZERO);
6085426539cSMatt Macy if (!node)
6095426539cSMatt Macy goto err_nomem;
6105426539cSMatt Macy if (info) {
6115426539cSMatt Macy node->loaded_info = malloc(sizeof(struct gcov_info *), M_GCOV, M_NOWAIT|M_ZERO);
6125426539cSMatt Macy if (!node->loaded_info)
6135426539cSMatt Macy goto err_nomem;
6145426539cSMatt Macy }
6155426539cSMatt Macy init_node(node, info, name, parent);
6165426539cSMatt Macy /* Differentiate between gcov data file nodes and directory nodes. */
6175426539cSMatt Macy if (info) {
6185426539cSMatt Macy node->dentry = debugfs_create_file(deskew(node->name), 0600,
6195426539cSMatt Macy parent->dentry, node, &gcov_data_fops);
6205426539cSMatt Macy } else
6215426539cSMatt Macy node->dentry = debugfs_create_dir(node->name, parent->dentry);
6225426539cSMatt Macy if (!node->dentry) {
6235426539cSMatt Macy log(LOG_WARNING, "could not create file\n");
6245426539cSMatt Macy free(node, M_GCOV);
6255426539cSMatt Macy return NULL;
6265426539cSMatt Macy }
6275426539cSMatt Macy if (info)
6285426539cSMatt Macy add_links(node, parent->dentry);
6295426539cSMatt Macy LIST_INSERT_HEAD(&parent->children, node, children_entry);
6305426539cSMatt Macy LIST_INSERT_HEAD(&all_head, node, all_entry);
6315426539cSMatt Macy
6325426539cSMatt Macy return (node);
6335426539cSMatt Macy
6345426539cSMatt Macy err_nomem:
6355426539cSMatt Macy free(node, M_GCOV);
6365426539cSMatt Macy log(LOG_WARNING, "out of memory\n");
6375426539cSMatt Macy return NULL;
6385426539cSMatt Macy }
6395426539cSMatt Macy
6405426539cSMatt Macy /* Remove symbolic links associated with node. */
6415426539cSMatt Macy static void
remove_links(struct gcov_node * node)6425426539cSMatt Macy remove_links(struct gcov_node *node)
6435426539cSMatt Macy {
6445426539cSMatt Macy
6455426539cSMatt Macy if (node->links == NULL)
6465426539cSMatt Macy return;
6475426539cSMatt Macy for (int i = 0; gcov_link[i].ext; i++)
6485426539cSMatt Macy debugfs_remove(node->links[i]);
6495426539cSMatt Macy free(node->links, M_GCOV);
6505426539cSMatt Macy node->links = NULL;
6515426539cSMatt Macy }
6525426539cSMatt Macy
6535426539cSMatt Macy /*
6545426539cSMatt Macy * Remove node from all lists and debugfs and release associated resources.
6555426539cSMatt Macy * Needs to be called with node_lock held.
6565426539cSMatt Macy */
6575426539cSMatt Macy static void
release_node(struct gcov_node * node)6585426539cSMatt Macy release_node(struct gcov_node *node)
6595426539cSMatt Macy {
6605426539cSMatt Macy LIST_REMOVE(node, children_entry);
6615426539cSMatt Macy LIST_REMOVE(node, all_entry);
6625426539cSMatt Macy debugfs_remove(node->dentry);
6635426539cSMatt Macy remove_links(node);
6645426539cSMatt Macy free(node->loaded_info, M_GCOV);
6655426539cSMatt Macy if (node->unloaded_info)
6665426539cSMatt Macy gcov_info_free(node->unloaded_info);
6675426539cSMatt Macy free(node, M_GCOV);
6685426539cSMatt Macy }
6695426539cSMatt Macy
6705426539cSMatt Macy /* Release node and empty parents. Needs to be called with node_lock held. */
6715426539cSMatt Macy static void
remove_node(struct gcov_node * node)6725426539cSMatt Macy remove_node(struct gcov_node *node)
6735426539cSMatt Macy {
6745426539cSMatt Macy struct gcov_node *parent;
6755426539cSMatt Macy
6765426539cSMatt Macy while ((node != &root_node) && LIST_EMPTY(&node->children)) {
6775426539cSMatt Macy parent = node->parent;
6785426539cSMatt Macy release_node(node);
6795426539cSMatt Macy node = parent;
6805426539cSMatt Macy }
6815426539cSMatt Macy }
6825426539cSMatt Macy
6835426539cSMatt Macy /*
6845426539cSMatt Macy * Find child node with given basename. Needs to be called with node_lock
6855426539cSMatt Macy * held.
6865426539cSMatt Macy */
6875426539cSMatt Macy static struct gcov_node *
get_child_by_name(struct gcov_node * parent,const char * name)6885426539cSMatt Macy get_child_by_name(struct gcov_node *parent, const char *name)
6895426539cSMatt Macy {
6905426539cSMatt Macy struct gcov_node *node;
6915426539cSMatt Macy
6925426539cSMatt Macy LIST_FOREACH(node, &parent->children, children_entry) {
6935426539cSMatt Macy if (strcmp(node->name, name) == 0)
6945426539cSMatt Macy return (node);
6955426539cSMatt Macy }
6965426539cSMatt Macy
6975426539cSMatt Macy return (NULL);
6985426539cSMatt Macy }
6995426539cSMatt Macy
7005426539cSMatt Macy /*
7015426539cSMatt Macy * Create a node for a given profiling data set and add it to all lists and
7025426539cSMatt Macy * debugfs. Needs to be called with node_lock held.
7035426539cSMatt Macy */
7045426539cSMatt Macy static void
add_node(struct gcov_info * info)7055426539cSMatt Macy add_node(struct gcov_info *info)
7065426539cSMatt Macy {
7075426539cSMatt Macy char *filename;
7085426539cSMatt Macy char *curr;
7095426539cSMatt Macy char *next;
7105426539cSMatt Macy struct gcov_node *parent;
7115426539cSMatt Macy struct gcov_node *node;
7125426539cSMatt Macy
7135426539cSMatt Macy filename = strdup_flags(gcov_info_filename(info), M_GCOV, M_NOWAIT);
7145426539cSMatt Macy if (filename == NULL)
7155426539cSMatt Macy return;
7165426539cSMatt Macy parent = &root_node;
7175426539cSMatt Macy /* Create directory nodes along the path. */
7185426539cSMatt Macy for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
7195426539cSMatt Macy if (curr == next)
7205426539cSMatt Macy continue;
7215426539cSMatt Macy *next = 0;
7225426539cSMatt Macy if (strcmp(curr, ".") == 0)
7235426539cSMatt Macy continue;
7245426539cSMatt Macy if (strcmp(curr, "..") == 0) {
7255426539cSMatt Macy if (!parent->parent)
7265426539cSMatt Macy goto err_remove;
7275426539cSMatt Macy parent = parent->parent;
7285426539cSMatt Macy continue;
7295426539cSMatt Macy }
7305426539cSMatt Macy node = get_child_by_name(parent, curr);
7315426539cSMatt Macy if (!node) {
7325426539cSMatt Macy node = new_node(parent, NULL, curr);
7335426539cSMatt Macy if (!node)
7345426539cSMatt Macy goto err_remove;
7355426539cSMatt Macy }
7365426539cSMatt Macy parent = node;
7375426539cSMatt Macy }
7385426539cSMatt Macy /* Create file node. */
7395426539cSMatt Macy node = new_node(parent, info, curr);
7405426539cSMatt Macy if (!node)
7415426539cSMatt Macy goto err_remove;
7425426539cSMatt Macy out:
7435426539cSMatt Macy free(filename, M_GCOV);
7445426539cSMatt Macy return;
7455426539cSMatt Macy
7465426539cSMatt Macy err_remove:
7475426539cSMatt Macy remove_node(parent);
7485426539cSMatt Macy goto out;
7495426539cSMatt Macy }
7505426539cSMatt Macy
7515426539cSMatt Macy /*
7525426539cSMatt Macy * Associate a profiling data set with an existing node. Needs to be called
7535426539cSMatt Macy * with node_lock held.
7545426539cSMatt Macy */
7555426539cSMatt Macy static void
add_info(struct gcov_node * node,struct gcov_info * info)7565426539cSMatt Macy add_info(struct gcov_node *node, struct gcov_info *info)
7575426539cSMatt Macy {
7585426539cSMatt Macy struct gcov_info **loaded_info;
7595426539cSMatt Macy int num = node->num_loaded;
7605426539cSMatt Macy
7615426539cSMatt Macy /*
7625426539cSMatt Macy * Prepare new array. This is done first to simplify cleanup in
7635426539cSMatt Macy * case the new data set is incompatible, the node only contains
7645426539cSMatt Macy * unloaded data sets and there's not enough memory for the array.
7655426539cSMatt Macy */
7665426539cSMatt Macy loaded_info = malloc((num + 1)* sizeof(struct gcov_info *), M_GCOV, M_NOWAIT|M_ZERO);
7675426539cSMatt Macy if (!loaded_info) {
7685426539cSMatt Macy log(LOG_WARNING, "could not add '%s' (out of memory)\n",
7695426539cSMatt Macy gcov_info_filename(info));
7705426539cSMatt Macy return;
7715426539cSMatt Macy }
7725426539cSMatt Macy memcpy(loaded_info, node->loaded_info,
7735426539cSMatt Macy num * sizeof(struct gcov_info *));
7745426539cSMatt Macy loaded_info[num] = info;
7755426539cSMatt Macy /* Check if the new data set is compatible. */
7765426539cSMatt Macy if (num == 0) {
7775426539cSMatt Macy /*
7785426539cSMatt Macy * A module was unloaded, modified and reloaded. The new
7795426539cSMatt Macy * data set replaces the copy of the last one.
7805426539cSMatt Macy */
7815426539cSMatt Macy if (!gcov_info_is_compatible(node->unloaded_info, info)) {
7825426539cSMatt Macy log(LOG_WARNING, "discarding saved data for %s "
7835426539cSMatt Macy "(incompatible version)\n",
7845426539cSMatt Macy gcov_info_filename(info));
7855426539cSMatt Macy gcov_info_free(node->unloaded_info);
7865426539cSMatt Macy node->unloaded_info = NULL;
7875426539cSMatt Macy }
7885426539cSMatt Macy } else {
7895426539cSMatt Macy /*
7905426539cSMatt Macy * Two different versions of the same object file are loaded.
7915426539cSMatt Macy * The initial one takes precedence.
7925426539cSMatt Macy */
7935426539cSMatt Macy if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
7945426539cSMatt Macy log(LOG_WARNING, "could not add '%s' (incompatible "
7955426539cSMatt Macy "version)\n", gcov_info_filename(info));
7965426539cSMatt Macy free(loaded_info, M_GCOV);
7975426539cSMatt Macy return;
7985426539cSMatt Macy }
7995426539cSMatt Macy }
8005426539cSMatt Macy /* Overwrite previous array. */
8015426539cSMatt Macy free(node->loaded_info, M_GCOV);
8025426539cSMatt Macy node->loaded_info = loaded_info;
8035426539cSMatt Macy node->num_loaded = num + 1;
8045426539cSMatt Macy }
8055426539cSMatt Macy
8065426539cSMatt Macy /*
8075426539cSMatt Macy * Return the index of a profiling data set associated with a node.
8085426539cSMatt Macy */
8095426539cSMatt Macy static int
get_info_index(struct gcov_node * node,struct gcov_info * info)8105426539cSMatt Macy get_info_index(struct gcov_node *node, struct gcov_info *info)
8115426539cSMatt Macy {
8125426539cSMatt Macy int i;
8135426539cSMatt Macy
8145426539cSMatt Macy for (i = 0; i < node->num_loaded; i++) {
8155426539cSMatt Macy if (node->loaded_info[i] == info)
8165426539cSMatt Macy return (i);
8175426539cSMatt Macy }
8185426539cSMatt Macy return (ENOENT);
8195426539cSMatt Macy }
8205426539cSMatt Macy
8215426539cSMatt Macy /*
8225426539cSMatt Macy * Save the data of a profiling data set which is being unloaded.
8235426539cSMatt Macy */
8245426539cSMatt Macy static void
save_info(struct gcov_node * node,struct gcov_info * info)8255426539cSMatt Macy save_info(struct gcov_node *node, struct gcov_info *info)
8265426539cSMatt Macy {
8275426539cSMatt Macy if (node->unloaded_info)
8285426539cSMatt Macy gcov_info_add(node->unloaded_info, info);
8295426539cSMatt Macy else {
8305426539cSMatt Macy node->unloaded_info = gcov_info_dup(info);
8315426539cSMatt Macy if (!node->unloaded_info) {
8325426539cSMatt Macy log(LOG_WARNING, "could not save data for '%s' "
8335426539cSMatt Macy "(out of memory)\n",
8345426539cSMatt Macy gcov_info_filename(info));
8355426539cSMatt Macy }
8365426539cSMatt Macy }
8375426539cSMatt Macy }
8385426539cSMatt Macy
8395426539cSMatt Macy /*
8405426539cSMatt Macy * Disassociate a profiling data set from a node. Needs to be called with
8415426539cSMatt Macy * node_lock held.
8425426539cSMatt Macy */
8435426539cSMatt Macy static void
remove_info(struct gcov_node * node,struct gcov_info * info)8445426539cSMatt Macy remove_info(struct gcov_node *node, struct gcov_info *info)
8455426539cSMatt Macy {
8465426539cSMatt Macy int i;
8475426539cSMatt Macy
8485426539cSMatt Macy i = get_info_index(node, info);
8495426539cSMatt Macy if (i < 0) {
8505426539cSMatt Macy log(LOG_WARNING, "could not remove '%s' (not found)\n",
8515426539cSMatt Macy gcov_info_filename(info));
8525426539cSMatt Macy return;
8535426539cSMatt Macy }
8545426539cSMatt Macy if (gcov_persist)
8555426539cSMatt Macy save_info(node, info);
8565426539cSMatt Macy /* Shrink array. */
8575426539cSMatt Macy node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
8585426539cSMatt Macy node->num_loaded--;
8595426539cSMatt Macy if (node->num_loaded > 0)
8605426539cSMatt Macy return;
8615426539cSMatt Macy /* Last loaded data set was removed. */
8625426539cSMatt Macy free(node->loaded_info, M_GCOV);
8635426539cSMatt Macy node->loaded_info = NULL;
8645426539cSMatt Macy node->num_loaded = 0;
8655426539cSMatt Macy if (!node->unloaded_info)
8665426539cSMatt Macy remove_node(node);
8675426539cSMatt Macy }
8685426539cSMatt Macy
8695426539cSMatt Macy /*
8705426539cSMatt Macy * Callback to create/remove profiling files when code compiled with
8715426539cSMatt Macy * -fprofile-arcs is loaded/unloaded.
8725426539cSMatt Macy */
8735426539cSMatt Macy static void
gcov_event(enum gcov_action action,struct gcov_info * info)8745426539cSMatt Macy gcov_event(enum gcov_action action, struct gcov_info *info)
8755426539cSMatt Macy {
8765426539cSMatt Macy struct gcov_node *node;
8775426539cSMatt Macy
8785426539cSMatt Macy mtx_lock(&node_lock);
8795426539cSMatt Macy node = get_node_by_name(gcov_info_filename(info));
8805426539cSMatt Macy switch (action) {
8815426539cSMatt Macy case GCOV_ADD:
8825426539cSMatt Macy if (node)
8835426539cSMatt Macy add_info(node, info);
8845426539cSMatt Macy else
8855426539cSMatt Macy add_node(info);
8865426539cSMatt Macy break;
8875426539cSMatt Macy case GCOV_REMOVE:
8885426539cSMatt Macy if (node)
8895426539cSMatt Macy remove_info(node, info);
8905426539cSMatt Macy else {
8915426539cSMatt Macy log(LOG_WARNING, "could not remove '%s' (not found)\n",
8925426539cSMatt Macy gcov_info_filename(info));
8935426539cSMatt Macy }
8945426539cSMatt Macy break;
8955426539cSMatt Macy }
8965426539cSMatt Macy mtx_unlock(&node_lock);
8975426539cSMatt Macy }
8985426539cSMatt Macy
8995426539cSMatt Macy /**
9005426539cSMatt Macy * gcov_enable_events - enable event reporting through gcov_event()
9015426539cSMatt Macy *
9025426539cSMatt Macy * Turn on reporting of profiling data load/unload-events through the
9035426539cSMatt Macy * gcov_event() callback. Also replay all previous events once. This function
9045426539cSMatt Macy * is needed because some events are potentially generated too early for the
9055426539cSMatt Macy * callback implementation to handle them initially.
9065426539cSMatt Macy */
9075426539cSMatt Macy void
gcov_enable_events(void)9085426539cSMatt Macy gcov_enable_events(void)
9095426539cSMatt Macy {
9105426539cSMatt Macy struct gcov_info *info = NULL;
9115426539cSMatt Macy int count;
9125426539cSMatt Macy
9135426539cSMatt Macy mtx_lock(&gcov_mtx);
9145426539cSMatt Macy count = 0;
9155426539cSMatt Macy
9165426539cSMatt Macy /* Perform event callback for previously registered entries. */
9175426539cSMatt Macy while ((info = gcov_info_next(info))) {
9185426539cSMatt Macy gcov_event(GCOV_ADD, info);
9195426539cSMatt Macy sched_relinquish(curthread);
9205426539cSMatt Macy count++;
9215426539cSMatt Macy }
9225426539cSMatt Macy
9235426539cSMatt Macy mtx_unlock(&gcov_mtx);
9245426539cSMatt Macy printf("%s found %d events\n", __func__, count);
9255426539cSMatt Macy }
9265426539cSMatt Macy
9275426539cSMatt Macy /* Update list and generate events when modules are unloaded. */
9285426539cSMatt Macy void
gcov_module_unload(void * arg __unused,module_t mod)9295426539cSMatt Macy gcov_module_unload(void *arg __unused, module_t mod)
9305426539cSMatt Macy {
9315426539cSMatt Macy struct gcov_info *info = NULL;
9325426539cSMatt Macy struct gcov_info *prev = NULL;
9335426539cSMatt Macy
9345426539cSMatt Macy mtx_lock(&gcov_mtx );
9355426539cSMatt Macy
9365426539cSMatt Macy /* Remove entries located in module from linked list. */
9375426539cSMatt Macy while ((info = gcov_info_next(info))) {
9385426539cSMatt Macy if (within_module((vm_offset_t)info, mod)) {
9395426539cSMatt Macy gcov_info_unlink(prev, info);
9405426539cSMatt Macy if (gcov_events_enabled)
9415426539cSMatt Macy gcov_event(GCOV_REMOVE, info);
9425426539cSMatt Macy } else
9435426539cSMatt Macy prev = info;
9445426539cSMatt Macy }
9455426539cSMatt Macy
9465426539cSMatt Macy mtx_unlock(&gcov_mtx);
9475426539cSMatt Macy }
9485426539cSMatt Macy
9495426539cSMatt Macy void
gcov_fs_init(void)9505426539cSMatt Macy gcov_fs_init(void)
9515426539cSMatt Macy {
9525426539cSMatt Macy init_node(&root_node, NULL, NULL, NULL);
9535426539cSMatt Macy root_node.dentry = debugfs_create_dir("gcov", NULL);
9545426539cSMatt Macy }
955