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