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