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