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
52 extern int gcov_events_enabled;
53 static int gcov_persist;
54 static struct mtx gcov_mtx;
55 MTX_SYSINIT(gcov_init, &gcov_mtx, "gcov_mtx", MTX_DEF);
56 MALLOC_DEFINE(M_GCOV, "gcov", "gcov");
57
58 void __gcov_init(struct gcov_info *info);
59 void __gcov_flush(void);
60 void __gcov_merge_add(gcov_type *counters, unsigned int n_counters);
61 void __gcov_merge_single(gcov_type *counters, unsigned int n_counters);
62 void __gcov_merge_delta(gcov_type *counters, unsigned int n_counters);
63 void __gcov_merge_ior(gcov_type *counters, unsigned int n_counters);
64 void __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters);
65 void __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters);
66 void __gcov_exit(void);
67
68 static void gcov_event(enum gcov_action action, struct gcov_info *info);
69
70
71 /*
72 * Private copy taken from libc
73 */
74 static 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 */
102 void
__gcov_init(struct gcov_info * info)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 */
130 void
__gcov_flush(void)131 __gcov_flush(void)
132 {
133 /* Unused. */
134 }
135
136 void
__gcov_merge_add(gcov_type * counters,unsigned int n_counters)137 __gcov_merge_add(gcov_type *counters, unsigned int n_counters)
138 {
139 /* Unused. */
140 }
141
142 void
__gcov_merge_single(gcov_type * counters,unsigned int n_counters)143 __gcov_merge_single(gcov_type *counters, unsigned int n_counters)
144 {
145 /* Unused. */
146 }
147
148 void
__gcov_merge_delta(gcov_type * counters,unsigned int n_counters)149 __gcov_merge_delta(gcov_type *counters, unsigned int n_counters)
150 {
151 /* Unused. */
152 }
153
154 void
__gcov_merge_ior(gcov_type * counters,unsigned int n_counters)155 __gcov_merge_ior(gcov_type *counters, unsigned int n_counters)
156 {
157 /* Unused. */
158 }
159
160 void
__gcov_merge_time_profile(gcov_type * counters,unsigned int n_counters)161 __gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters)
162 {
163 /* Unused. */
164 }
165
166 void
__gcov_merge_icall_topn(gcov_type * counters,unsigned int n_counters)167 __gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters)
168 {
169 /* Unused. */
170 }
171
172 void
__gcov_exit(void)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 */
199 struct 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
215 static const char objtree[] = OBJTREE;
216 static const char srctree[] = SRCTREE;
217 #else
218 static const char objtree[] = "";
219 static const char srctree[] = "";
220 #endif
221 static struct gcov_node root_node;
222 static struct {
223 struct gcov_node *lh_first;
224 } all_head;
225 static struct mtx node_lock;
226 MTX_SYSINIT(node_init, &node_lock, "node_lock", MTX_DEF);
227 static 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 */
235 static void *
gcov_seq_start(struct seq_file * seq,off_t * pos)236 gcov_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. */
249 static void *
gcov_seq_next(struct seq_file * seq,void * data,off_t * pos)250 gcov_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. */
262 static int
gcov_seq_show(struct seq_file * seq,void * data)263 gcov_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
272 static void
gcov_seq_stop(struct seq_file * seq,void * data)273 gcov_seq_stop(struct seq_file *seq, void *data)
274 {
275 /* Unused. */
276 }
277
278 static 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 */
290 static struct gcov_info *
get_node_info(struct gcov_node * node)291 get_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 */
303 static struct gcov_info *
get_accumulated_info(struct gcov_node * node)304 get_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 */
325 static int
gcov_seq_open(struct inode * inode,struct file * file)326 gcov_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;
351 out_unlock:
352 mtx_unlock(&node_lock);
353 return (rc);
354
355 err_free_iter_info:
356 gcov_iter_free(iter);
357 err_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 */
366 static int
gcov_seq_release(struct inode * inode,struct file * file)367 gcov_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 */
387 static struct gcov_node *
get_node_by_name(const char * name)388 get_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 */
405 static void
reset_node(struct gcov_node * node)406 reset_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
416 void
gcov_stats_reset(void)417 gcov_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 */
439 static ssize_t
gcov_seq_write(struct file * file,const char * addr,size_t len,off_t * pos)440 gcov_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 */
470 static char *
link_target(const char * dir,const char * path,const char * ext)471 link_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 */
499 static char *
get_link_target(const char * filename,const struct gcov_link * ext)500 get_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 */
525 static const char *
deskew(const char * basename)526 deskew(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 */
537 static void
add_links(struct gcov_node * node,struct dentry * parent)538 add_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;
567 out_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
575 static 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. */
584 static void
init_node(struct gcov_node * node,struct gcov_info * info,const char * name,struct gcov_node * parent)585 init_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 */
602 static struct gcov_node *
new_node(struct gcov_node * parent,struct gcov_info * info,const char * name)603 new_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
634 err_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. */
641 static void
remove_links(struct gcov_node * node)642 remove_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 */
657 static void
release_node(struct gcov_node * node)658 release_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. */
671 static void
remove_node(struct gcov_node * node)672 remove_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 */
687 static struct gcov_node *
get_child_by_name(struct gcov_node * parent,const char * name)688 get_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 */
704 static void
add_node(struct gcov_info * info)705 add_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;
742 out:
743 free(filename, M_GCOV);
744 return;
745
746 err_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 */
755 static void
add_info(struct gcov_node * node,struct gcov_info * info)756 add_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 */
809 static int
get_info_index(struct gcov_node * node,struct gcov_info * info)810 get_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 */
824 static void
save_info(struct gcov_node * node,struct gcov_info * info)825 save_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 */
843 static void
remove_info(struct gcov_node * node,struct gcov_info * info)844 remove_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 */
873 static void
gcov_event(enum gcov_action action,struct gcov_info * info)874 gcov_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 */
907 void
gcov_enable_events(void)908 gcov_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. */
928 void
gcov_module_unload(void * arg __unused,module_t mod)929 gcov_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
949 void
gcov_fs_init(void)950 gcov_fs_init(void)
951 {
952 init_node(&root_node, NULL, NULL, NULL);
953 root_node.dentry = debugfs_create_dir("gcov", NULL);
954 }
955