xref: /freebsd-src/sys/gnu/gcov/gcov_fs.c (revision fdafd315ad0d0f28a11b9fb4476a9ab059c62b92)
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