Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/gnu/gcov/gcov_fs.c
39507 views
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 <[email protected]>
20
*
21
* Uses gcc-internal data definitions.
22
* Based on the gcov-kernel patch by:
23
* Hubertus Franke <[email protected]>
24
* Nigel Hinds <[email protected]>
25
* Rajan Ravindran <[email protected]>
26
* Peter Oberparleiter <[email protected]>
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
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
131
__gcov_flush(void)
132
{
133
/* Unused. */
134
}
135
136
void
137
__gcov_merge_add(gcov_type *counters, unsigned int n_counters)
138
{
139
/* Unused. */
140
}
141
142
void
143
__gcov_merge_single(gcov_type *counters, unsigned int n_counters)
144
{
145
/* Unused. */
146
}
147
148
void
149
__gcov_merge_delta(gcov_type *counters, unsigned int n_counters)
150
{
151
/* Unused. */
152
}
153
154
void
155
__gcov_merge_ior(gcov_type *counters, unsigned int n_counters)
156
{
157
/* Unused. */
158
}
159
160
void
161
__gcov_merge_time_profile(gcov_type *counters, unsigned int n_counters)
162
{
163
/* Unused. */
164
}
165
166
void
167
__gcov_merge_icall_topn(gcov_type *counters, unsigned int n_counters)
168
{
169
/* Unused. */
170
}
171
172
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 *
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 *
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
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
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 *
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 *
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
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
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 *
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
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
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
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 *
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 *
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 *
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
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
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 *
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
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
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
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 *
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
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
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
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
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
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
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
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
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
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
956