1// SPDX-License-Identifier: GPL-2.0
2// This program is free software; you can redistribute it and/or
3// modify it under the terms of the GNU General Public License
4// as published by the Free Software Foundation; version 2.
5//
6// This program is distributed in the hope that it will be useful,
7// but WITHOUT ANY WARRANTY; without even the implied warranty of
8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9// GNU General Public License for more details.
10//
11// You should have received a copy of the GNU General Public License
12// along with this program; if not, write to the Free Software
13// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
14// 02110-1301, USA.
15/*
16 *  This code exports profiling data as debugfs files to userspace.
17 *
18 *    Copyright IBM Corp. 2009
19 *    Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
20 *
21 *    Uses gcc-internal data definitions.
22 *    Based on the gcov-kernel patch by:
23 *		 Hubertus Franke <frankeh@us.ibm.com>
24 *		 Nigel Hinds <nhinds@us.ibm.com>
25 *		 Rajan Ravindran <rajancr@us.ibm.com>
26 *		 Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
27 *		 Paul Larson
28 *		 Yi CDL Yang
29 */
30
31#include <sys/types.h>
32#include <sys/systm.h>
33#include <sys/param.h>
34#include <sys/sbuf.h>
35
36#include <sys/queue.h>
37#include <sys/linker.h>
38#include <sys/module.h>
39#include <sys/eventhandler.h>
40#include <sys/kernel.h>
41#include <sys/malloc.h>
42#include <sys/syslog.h>
43#include <sys/proc.h>
44#include <sys/sched.h>
45#include <sys/syslog.h>
46#include <sys/sysctl.h>
47#include <linux/debugfs.h>
48
49#include <gnu/gcov/gcov.h>
50#include <sys/queue.h>
51
52extern int gcov_events_enabled;
53static int gcov_persist;
54static struct mtx gcov_mtx;
55MTX_SYSINIT(gcov_init, &gcov_mtx, "gcov_mtx", MTX_DEF);
56MALLOC_DEFINE(M_GCOV, "gcov", "gcov");
57
58void __gcov_init(struct gcov_info *info);
59void __gcov_flush(void);
60void __gcov_merge_add(gcov_type *counters, unsigned int n_counters);
61void __gcov_merge_single(gcov_type *counters, unsigned int n_counters);
62void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters);
63void __gcov_merge_ior(gcov_type *counters, unsigned int n_counters);
64void __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters);
65void __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters);
66void __gcov_exit(void);
67
68static void gcov_event(enum gcov_action action, struct gcov_info *info);
69
70
71/*
72 * Private copy taken from libc
73 */
74static char *
75(basename)(char *path)
76{
77	char *ptr;
78
79	/*
80	 * If path is a null pointer or points to an empty string,
81	 * basename() shall return a pointer to the string ".".
82	 */
83	if (path == NULL || *path == '\0')
84		return (__DECONST(char *, "."));
85
86	/* Find end of last pathname component and null terminate it. */
87	ptr = path + strlen(path);
88	while (ptr > path + 1 && *(ptr - 1) == '/')
89		--ptr;
90	*ptr-- = '\0';
91
92	/* Find beginning of last pathname component. */
93	while (ptr > path && *(ptr - 1) != '/')
94		--ptr;
95	return (ptr);
96}
97
98/*
99 * __gcov_init is called by gcc-generated constructor code for each object
100 * file compiled with -fprofile-arcs.
101 */
102void
103__gcov_init(struct gcov_info *info)
104{
105	static unsigned int gcov_version;
106
107	mtx_lock(&gcov_mtx);
108	if (gcov_version == 0) {
109		gcov_version = gcov_info_version(info);
110		/*
111		 * Printing gcc's version magic may prove useful for debugging
112		 * incompatibility reports.
113		 */
114		log(LOG_INFO, "version magic: 0x%x\n", gcov_version);
115	}
116	/*
117	 * Add new profiling data structure to list and inform event
118	 * listener.
119	 */
120	gcov_info_link(info);
121	if (gcov_events_enabled)
122		gcov_event(GCOV_ADD, info);
123	mtx_unlock(&gcov_mtx);
124}
125
126/*
127 * These functions may be referenced by gcc-generated profiling code but serve
128 * no function for kernel profiling.
129 */
130void
131__gcov_flush(void)
132{
133	/* Unused. */
134}
135
136void
137__gcov_merge_add(gcov_type *counters, unsigned int n_counters)
138{
139	/* Unused. */
140}
141
142void
143__gcov_merge_single(gcov_type *counters, unsigned int n_counters)
144{
145	/* Unused. */
146}
147
148void
149__gcov_merge_delta(gcov_type *counters, unsigned int n_counters)
150{
151	/* Unused. */
152}
153
154void
155__gcov_merge_ior(gcov_type *counters, unsigned int n_counters)
156{
157	/* Unused. */
158}
159
160void
161__gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters)
162{
163	/* Unused. */
164}
165
166void
167__gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters)
168{
169	/* Unused. */
170}
171
172void
173__gcov_exit(void)
174{
175	/* Unused. */
176}
177
178
179/**
180 * struct gcov_node - represents a debugfs entry
181 * @entry: list entry for parent's child node list
182 * @children: child nodes
183 * @all_entry: list entry for list of all nodes
184 * @parent: parent node
185 * @loaded_info: array of pointers to profiling data sets for loaded object
186 *   files.
187 * @num_loaded: number of profiling data sets for loaded object files.
188 * @unloaded_info: accumulated copy of profiling data sets for unloaded
189 *   object files. Used only when gcov_persist=1.
190 * @dentry: main debugfs entry, either a directory or data file
191 * @links: associated symbolic links
192 * @name: data file basename
193 *
194 * struct gcov_node represents an entity within the gcov/ subdirectory
195 * of debugfs. There are directory and data file nodes. The latter represent
196 * the actual synthesized data file plus any associated symbolic links which
197 * are needed by the gcov tool to work correctly.
198 */
199struct gcov_node {
200	LIST_ENTRY(gcov_node) children_entry;
201	LIST_ENTRY(gcov_node) all_entry;
202	struct {
203		struct gcov_node *lh_first;
204	} children;
205	struct gcov_node *parent;
206	struct gcov_info **loaded_info;
207	struct gcov_info *unloaded_info;
208	struct dentry *dentry;
209	struct dentry **links;
210	int num_loaded;
211	char name[0];
212};
213
214#ifdef notyet
215static const char objtree[] = OBJTREE;
216static const char srctree[] = SRCTREE;
217#else
218static const char objtree[] = "";
219static const char srctree[] = "";
220#endif
221static struct gcov_node root_node;
222static struct {
223	struct gcov_node *lh_first;
224} all_head;
225static struct mtx node_lock;
226MTX_SYSINIT(node_init, &node_lock, "node_lock", MTX_DEF);
227static void remove_node(struct gcov_node *node);
228
229/*
230 * seq_file.start() implementation for gcov data files. Note that the
231 * gcov_iterator interface is designed to be more restrictive than seq_file
232 * (no start from arbitrary position, etc.), to simplify the iterator
233 * implementation.
234 */
235static void *
236gcov_seq_start(struct seq_file *seq, off_t *pos)
237{
238	off_t i;
239
240	gcov_iter_start(seq->private);
241	for (i = 0; i < *pos; i++) {
242		if (gcov_iter_next(seq->private))
243			return NULL;
244	}
245	return seq->private;
246}
247
248/* seq_file.next() implementation for gcov data files. */
249static void *
250gcov_seq_next(struct seq_file *seq, void *data, off_t *pos)
251{
252	struct gcov_iterator *iter = data;
253
254	if (gcov_iter_next(iter))
255		return NULL;
256	(*pos)++;
257
258	return iter;
259}
260
261/* seq_file.show() implementation for gcov data files. */
262static int
263gcov_seq_show(struct seq_file *seq, void *data)
264{
265	struct gcov_iterator *iter = data;
266
267	if (gcov_iter_write(iter, seq->buf))
268		return (-EINVAL);
269	return (0);
270}
271
272static void
273gcov_seq_stop(struct seq_file *seq, void *data)
274{
275	/* Unused. */
276}
277
278static const struct seq_operations gcov_seq_ops = {
279	.start	= gcov_seq_start,
280	.next	= gcov_seq_next,
281	.show	= gcov_seq_show,
282	.stop	= gcov_seq_stop,
283};
284
285/*
286 * Return a profiling data set associated with the given node. This is
287 * either a data set for a loaded object file or a data set copy in case
288 * all associated object files have been unloaded.
289 */
290static struct gcov_info *
291get_node_info(struct gcov_node *node)
292{
293	if (node->num_loaded > 0)
294		return (node->loaded_info[0]);
295
296	return (node->unloaded_info);
297}
298
299/*
300 * Return a newly allocated profiling data set which contains the sum of
301 * all profiling data associated with the given node.
302 */
303static struct gcov_info *
304get_accumulated_info(struct gcov_node *node)
305{
306	struct gcov_info *info;
307	int i = 0;
308
309	if (node->unloaded_info)
310		info = gcov_info_dup(node->unloaded_info);
311	else
312		info = gcov_info_dup(node->loaded_info[i++]);
313	if (info == NULL)
314		return (NULL);
315	for (; i < node->num_loaded; i++)
316		gcov_info_add(info, node->loaded_info[i]);
317
318	return (info);
319}
320
321/*
322 * open() implementation for gcov data files. Create a copy of the profiling
323 * data set and initialize the iterator and seq_file interface.
324 */
325static int
326gcov_seq_open(struct inode *inode, struct file *file)
327{
328	struct gcov_node *node = inode->i_private;
329	struct gcov_iterator *iter;
330	struct seq_file *seq;
331	struct gcov_info *info;
332	int rc = -ENOMEM;
333
334	mtx_lock(&node_lock);
335	/*
336	 * Read from a profiling data copy to minimize reference tracking
337	 * complexity and concurrent access and to keep accumulating multiple
338	 * profiling data sets associated with one node simple.
339	 */
340	info = get_accumulated_info(node);
341	if (info == NULL)
342		goto out_unlock;
343	iter = gcov_iter_new(info);
344	if (iter == NULL)
345		goto err_free_info;
346	rc = seq_open(file, &gcov_seq_ops);
347	if (rc)
348		goto err_free_iter_info;
349	seq = file->private_data;
350	seq->private = iter;
351out_unlock:
352	mtx_unlock(&node_lock);
353	return (rc);
354
355err_free_iter_info:
356	gcov_iter_free(iter);
357err_free_info:
358	gcov_info_free(info);
359	goto out_unlock;
360}
361
362/*
363 * release() implementation for gcov data files. Release resources allocated
364 * by open().
365 */
366static int
367gcov_seq_release(struct inode *inode, struct file *file)
368{
369	struct gcov_iterator *iter;
370	struct gcov_info *info;
371	struct seq_file *seq;
372
373	seq = file->private_data;
374	iter = seq->private;
375	info = gcov_iter_get_info(iter);
376	gcov_iter_free(iter);
377	gcov_info_free(info);
378	seq_release(inode, file);
379
380	return (0);
381}
382
383/*
384 * Find a node by the associated data file name. Needs to be called with
385 * node_lock held.
386 */
387static struct gcov_node *
388get_node_by_name(const char *name)
389{
390	struct gcov_node *node;
391	struct gcov_info *info;
392
393	LIST_FOREACH(node, &all_head, all_entry) {
394		info = get_node_info(node);
395		if (info && (strcmp(gcov_info_filename(info), name) == 0))
396			return (node);
397	}
398
399	return (NULL);
400}
401
402/*
403 * Reset all profiling data associated with the specified node.
404 */
405static void
406reset_node(struct gcov_node *node)
407{
408	int i;
409
410	if (node->unloaded_info)
411		gcov_info_reset(node->unloaded_info);
412	for (i = 0; i < node->num_loaded; i++)
413		gcov_info_reset(node->loaded_info[i]);
414}
415
416void
417gcov_stats_reset(void)
418{
419	struct gcov_node *node;
420
421	mtx_lock(&node_lock);
422 restart:
423	LIST_FOREACH(node, &all_head, all_entry) {
424		if (node->num_loaded > 0)
425			reset_node(node);
426		else if (LIST_EMPTY(&node->children)) {
427			remove_node(node);
428			goto restart;
429		}
430	}
431	mtx_unlock(&node_lock);
432}
433
434/*
435 * write() implementation for gcov data files. Reset profiling data for the
436 * corresponding file. If all associated object files have been unloaded,
437 * remove the debug fs node as well.
438 */
439static ssize_t
440gcov_seq_write(struct file *file, const char *addr, size_t len, off_t *pos)
441{
442	struct seq_file *seq;
443	struct gcov_info *info;
444	struct gcov_node *node;
445
446	seq = file->private_data;
447	info = gcov_iter_get_info(seq->private);
448	mtx_lock(&node_lock);
449	node = get_node_by_name(gcov_info_filename(info));
450	if (node) {
451		/* Reset counts or remove node for unloaded modules. */
452		if (node->num_loaded == 0)
453			remove_node(node);
454		else
455			reset_node(node);
456	}
457	/* Reset counts for open file. */
458	gcov_info_reset(info);
459	mtx_unlock(&node_lock);
460
461	return (len);
462}
463
464/*
465 * Given a string <path> representing a file path of format:
466 *   path/to/file.gcda
467 * construct and return a new string:
468 *   <dir/>path/to/file.<ext>
469 */
470static char *
471link_target(const char *dir, const char *path, const char *ext)
472{
473	char *target;
474	char *old_ext;
475	char *copy;
476
477	copy = strdup_flags(path, M_GCOV, M_NOWAIT);
478	if (!copy)
479		return (NULL);
480	old_ext = strrchr(copy, '.');
481	if (old_ext)
482		*old_ext = '\0';
483	target = NULL;
484	if (dir)
485		asprintf(&target, M_GCOV, "%s/%s.%s", dir, copy, ext);
486	else
487		asprintf(&target, M_GCOV, "%s.%s", copy, ext);
488	free(copy, M_GCOV);
489
490	return (target);
491}
492
493/*
494 * Construct a string representing the symbolic link target for the given
495 * gcov data file name and link type. Depending on the link type and the
496 * location of the data file, the link target can either point to a
497 * subdirectory of srctree, objtree or in an external location.
498 */
499static char *
500get_link_target(const char *filename, const struct gcov_link *ext)
501{
502	const char *rel;
503	char *result;
504
505	if (strncmp(filename, objtree, strlen(objtree)) == 0) {
506		rel = filename + strlen(objtree) + 1;
507		if (ext->dir == SRC_TREE)
508			result = link_target(srctree, rel, ext->ext);
509		else
510			result = link_target(objtree, rel, ext->ext);
511	} else {
512		/* External compilation. */
513		result = link_target(NULL, filename, ext->ext);
514	}
515
516	return (result);
517}
518
519#define SKEW_PREFIX	".tmp_"
520
521/*
522 * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
523 * for filename skewing caused by the mod-versioning mechanism.
524 */
525static const char *
526deskew(const char *basename)
527{
528	if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
529		return (basename + sizeof(SKEW_PREFIX) - 1);
530	return (basename);
531}
532
533/*
534 * Create links to additional files (usually .c and .gcno files) which the
535 * gcov tool expects to find in the same directory as the gcov data file.
536 */
537static void
538add_links(struct gcov_node *node, struct dentry *parent)
539{
540	const char *path_basename;
541	char *target;
542	int num;
543	int i;
544
545	for (num = 0; gcov_link[num].ext; num++)
546		/* Nothing. */;
547	node->links = malloc((num*sizeof(struct dentry *)), M_GCOV, M_NOWAIT|M_ZERO);
548	if (node->links == NULL)
549		return;
550	for (i = 0; i < num; i++) {
551		target = get_link_target(
552				gcov_info_filename(get_node_info(node)),
553				&gcov_link[i]);
554		if (target == NULL)
555			goto out_err;
556		path_basename = basename(target);
557		if (path_basename == target)
558			goto out_err;
559		node->links[i] = debugfs_create_symlink(deskew(path_basename),
560							parent,	target);
561		if (!node->links[i])
562			goto out_err;
563		free(target, M_GCOV);
564	}
565
566	return;
567out_err:
568	free(target, M_GCOV);
569	while (i-- > 0)
570		debugfs_remove(node->links[i]);
571	free(node->links, M_GCOV);
572	node->links = NULL;
573}
574
575static const struct file_operations gcov_data_fops = {
576	.open		= gcov_seq_open,
577	.release	= gcov_seq_release,
578	.read		= seq_read,
579	.llseek		= seq_lseek,
580	.write		= gcov_seq_write,
581};
582
583/* Basic initialization of a new node. */
584static void
585init_node(struct gcov_node *node, struct gcov_info *info,
586   const char *name, struct gcov_node *parent)
587{
588	LIST_INIT(&node->children);
589	if (node->loaded_info) {
590		node->loaded_info[0] = info;
591		node->num_loaded = 1;
592	}
593	node->parent = parent;
594	if (name)
595		strcpy(node->name, name);
596}
597
598/*
599 * Create a new node and associated debugfs entry. Needs to be called with
600 * node_lock held.
601 */
602static struct gcov_node *
603new_node(struct gcov_node *parent, struct gcov_info *info, const char *name)
604{
605	struct gcov_node *node;
606
607	node = malloc(sizeof(struct gcov_node) + strlen(name) + 1, M_GCOV, M_NOWAIT|M_ZERO);
608	if (!node)
609		goto err_nomem;
610	if (info) {
611		node->loaded_info = malloc(sizeof(struct gcov_info *), M_GCOV, M_NOWAIT|M_ZERO);
612		if (!node->loaded_info)
613			goto err_nomem;
614	}
615	init_node(node, info, name, parent);
616	/* Differentiate between gcov data file nodes and directory nodes. */
617	if (info) {
618		node->dentry = debugfs_create_file(deskew(node->name), 0600,
619					parent->dentry, node, &gcov_data_fops);
620	} else
621		node->dentry = debugfs_create_dir(node->name, parent->dentry);
622	if (!node->dentry) {
623		log(LOG_WARNING, "could not create file\n");
624		free(node, M_GCOV);
625		return NULL;
626	}
627	if (info)
628		add_links(node, parent->dentry);
629	LIST_INSERT_HEAD(&parent->children, node, children_entry);
630	LIST_INSERT_HEAD(&all_head, node, all_entry);
631
632	return (node);
633
634err_nomem:
635	free(node, M_GCOV);
636	log(LOG_WARNING, "out of memory\n");
637	return NULL;
638}
639
640/* Remove symbolic links associated with node. */
641static void
642remove_links(struct gcov_node *node)
643{
644
645	if (node->links == NULL)
646		return;
647	for (int i = 0; gcov_link[i].ext; i++)
648		debugfs_remove(node->links[i]);
649	free(node->links, M_GCOV);
650	node->links = NULL;
651}
652
653/*
654 * Remove node from all lists and debugfs and release associated resources.
655 * Needs to be called with node_lock held.
656 */
657static void
658release_node(struct gcov_node *node)
659{
660	LIST_REMOVE(node, children_entry);
661	LIST_REMOVE(node, all_entry);
662	debugfs_remove(node->dentry);
663	remove_links(node);
664	free(node->loaded_info, M_GCOV);
665	if (node->unloaded_info)
666		gcov_info_free(node->unloaded_info);
667	free(node, M_GCOV);
668}
669
670/* Release node and empty parents. Needs to be called with node_lock held. */
671static void
672remove_node(struct gcov_node *node)
673{
674	struct gcov_node *parent;
675
676	while ((node != &root_node) && LIST_EMPTY(&node->children)) {
677		parent = node->parent;
678		release_node(node);
679		node = parent;
680	}
681}
682
683/*
684 * Find child node with given basename. Needs to be called with node_lock
685 * held.
686 */
687static struct gcov_node *
688get_child_by_name(struct gcov_node *parent, const char *name)
689{
690	struct gcov_node *node;
691
692	LIST_FOREACH(node, &parent->children, children_entry) {
693		if (strcmp(node->name, name) == 0)
694			return (node);
695	}
696
697	return (NULL);
698}
699
700/*
701 * Create a node for a given profiling data set and add it to all lists and
702 * debugfs. Needs to be called with node_lock held.
703 */
704static void
705add_node(struct gcov_info *info)
706{
707	char *filename;
708	char *curr;
709	char *next;
710	struct gcov_node *parent;
711	struct gcov_node *node;
712
713	filename = strdup_flags(gcov_info_filename(info), M_GCOV, M_NOWAIT);
714	if (filename == NULL)
715		return;
716	parent = &root_node;
717	/* Create directory nodes along the path. */
718	for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
719		if (curr == next)
720			continue;
721		*next = 0;
722		if (strcmp(curr, ".") == 0)
723			continue;
724		if (strcmp(curr, "..") == 0) {
725			if (!parent->parent)
726				goto err_remove;
727			parent = parent->parent;
728			continue;
729		}
730		node = get_child_by_name(parent, curr);
731		if (!node) {
732			node = new_node(parent, NULL, curr);
733			if (!node)
734				goto err_remove;
735		}
736		parent = node;
737	}
738	/* Create file node. */
739	node = new_node(parent, info, curr);
740	if (!node)
741		goto err_remove;
742out:
743	free(filename, M_GCOV);
744	return;
745
746err_remove:
747	remove_node(parent);
748	goto out;
749}
750
751/*
752 * Associate a profiling data set with an existing node. Needs to be called
753 * with node_lock held.
754 */
755static void
756add_info(struct gcov_node *node, struct gcov_info *info)
757{
758	struct gcov_info **loaded_info;
759	int num = node->num_loaded;
760
761	/*
762	 * Prepare new array. This is done first to simplify cleanup in
763	 * case the new data set is incompatible, the node only contains
764	 * unloaded data sets and there's not enough memory for the array.
765	 */
766	loaded_info = malloc((num + 1)* sizeof(struct gcov_info *), M_GCOV, M_NOWAIT|M_ZERO);
767	if (!loaded_info) {
768		log(LOG_WARNING, "could not add '%s' (out of memory)\n",
769			gcov_info_filename(info));
770		return;
771	}
772	memcpy(loaded_info, node->loaded_info,
773	       num * sizeof(struct gcov_info *));
774	loaded_info[num] = info;
775	/* Check if the new data set is compatible. */
776	if (num == 0) {
777		/*
778		 * A module was unloaded, modified and reloaded. The new
779		 * data set replaces the copy of the last one.
780		 */
781		if (!gcov_info_is_compatible(node->unloaded_info, info)) {
782			log(LOG_WARNING, "discarding saved data for %s "
783				"(incompatible version)\n",
784				gcov_info_filename(info));
785			gcov_info_free(node->unloaded_info);
786			node->unloaded_info = NULL;
787		}
788	} else {
789		/*
790		 * Two different versions of the same object file are loaded.
791		 * The initial one takes precedence.
792		 */
793		if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
794			log(LOG_WARNING, "could not add '%s' (incompatible "
795				"version)\n", gcov_info_filename(info));
796			free(loaded_info, M_GCOV);
797			return;
798		}
799	}
800	/* Overwrite previous array. */
801	free(node->loaded_info, M_GCOV);
802	node->loaded_info = loaded_info;
803	node->num_loaded = num + 1;
804}
805
806/*
807 * Return the index of a profiling data set associated with a node.
808 */
809static int
810get_info_index(struct gcov_node *node, struct gcov_info *info)
811{
812	int i;
813
814	for (i = 0; i < node->num_loaded; i++) {
815		if (node->loaded_info[i] == info)
816			return (i);
817	}
818	return (ENOENT);
819}
820
821/*
822 * Save the data of a profiling data set which is being unloaded.
823 */
824static void
825save_info(struct gcov_node *node, struct gcov_info *info)
826{
827	if (node->unloaded_info)
828		gcov_info_add(node->unloaded_info, info);
829	else {
830		node->unloaded_info = gcov_info_dup(info);
831		if (!node->unloaded_info) {
832			log(LOG_WARNING, "could not save data for '%s' "
833				"(out of memory)\n",
834				gcov_info_filename(info));
835		}
836	}
837}
838
839/*
840 * Disassociate a profiling data set from a node. Needs to be called with
841 * node_lock held.
842 */
843static void
844remove_info(struct gcov_node *node, struct gcov_info *info)
845{
846	int i;
847
848	i = get_info_index(node, info);
849	if (i < 0) {
850		log(LOG_WARNING, "could not remove '%s' (not found)\n",
851			gcov_info_filename(info));
852		return;
853	}
854	if (gcov_persist)
855		save_info(node, info);
856	/* Shrink array. */
857	node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
858	node->num_loaded--;
859	if (node->num_loaded > 0)
860		return;
861	/* Last loaded data set was removed. */
862	free(node->loaded_info, M_GCOV);
863	node->loaded_info = NULL;
864	node->num_loaded = 0;
865	if (!node->unloaded_info)
866		remove_node(node);
867}
868
869/*
870 * Callback to create/remove profiling files when code compiled with
871 * -fprofile-arcs is loaded/unloaded.
872 */
873static void
874gcov_event(enum gcov_action action, struct gcov_info *info)
875{
876	struct gcov_node *node;
877
878	mtx_lock(&node_lock);
879	node = get_node_by_name(gcov_info_filename(info));
880	switch (action) {
881	case GCOV_ADD:
882		if (node)
883			add_info(node, info);
884		else
885			add_node(info);
886		break;
887	case GCOV_REMOVE:
888		if (node)
889			remove_info(node, info);
890		else {
891			log(LOG_WARNING, "could not remove '%s' (not found)\n",
892				gcov_info_filename(info));
893		}
894		break;
895	}
896	mtx_unlock(&node_lock);
897}
898
899/**
900 * gcov_enable_events - enable event reporting through gcov_event()
901 *
902 * Turn on reporting of profiling data load/unload-events through the
903 * gcov_event() callback. Also replay all previous events once. This function
904 * is needed because some events are potentially generated too early for the
905 * callback implementation to handle them initially.
906 */
907void
908gcov_enable_events(void)
909{
910	struct gcov_info *info = NULL;
911	int count;
912
913	mtx_lock(&gcov_mtx);
914	count = 0;
915
916	/* Perform event callback for previously registered entries. */
917	while ((info = gcov_info_next(info))) {
918		gcov_event(GCOV_ADD, info);
919		sched_relinquish(curthread);
920		count++;
921	}
922
923	mtx_unlock(&gcov_mtx);
924	printf("%s found %d events\n", __func__, count);
925}
926
927/* Update list and generate events when modules are unloaded. */
928void
929gcov_module_unload(void *arg __unused, module_t mod)
930{
931	struct gcov_info *info = NULL;
932	struct gcov_info *prev = NULL;
933
934	mtx_lock(&gcov_mtx );
935
936	/* Remove entries located in module from linked list. */
937	while ((info = gcov_info_next(info))) {
938		if (within_module((vm_offset_t)info, mod)) {
939			gcov_info_unlink(prev, info);
940			if (gcov_events_enabled)
941				gcov_event(GCOV_REMOVE, info);
942		} else
943			prev = info;
944	}
945
946	mtx_unlock(&gcov_mtx);
947}
948
949void
950gcov_fs_init(void)
951{
952	init_node(&root_node, NULL, NULL, NULL);
953	root_node.dentry = debugfs_create_dir("gcov", NULL);
954}
955