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