diff options
Diffstat (limited to 'kernel/gcov/fs.c')
| -rw-r--r-- | kernel/gcov/fs.c | 244 |
1 files changed, 180 insertions, 64 deletions
diff --git a/kernel/gcov/fs.c b/kernel/gcov/fs.c index ef3c3f88a7a3..f83972b16564 100644 --- a/kernel/gcov/fs.c +++ b/kernel/gcov/fs.c | |||
| @@ -33,10 +33,11 @@ | |||
| 33 | * @children: child nodes | 33 | * @children: child nodes |
| 34 | * @all: list head for list of all nodes | 34 | * @all: list head for list of all nodes |
| 35 | * @parent: parent node | 35 | * @parent: parent node |
| 36 | * @info: associated profiling data structure if not a directory | 36 | * @loaded_info: array of pointers to profiling data sets for loaded object |
| 37 | * @ghost: when an object file containing profiling data is unloaded we keep a | 37 | * files. |
| 38 | * copy of the profiling data here to allow collecting coverage data | 38 | * @num_loaded: number of profiling data sets for loaded object files. |
| 39 | * for cleanup code. Such a node is called a "ghost". | 39 | * @unloaded_info: accumulated copy of profiling data sets for unloaded |
| 40 | * object files. Used only when gcov_persist=1. | ||
| 40 | * @dentry: main debugfs entry, either a directory or data file | 41 | * @dentry: main debugfs entry, either a directory or data file |
| 41 | * @links: associated symbolic links | 42 | * @links: associated symbolic links |
| 42 | * @name: data file basename | 43 | * @name: data file basename |
| @@ -51,10 +52,11 @@ struct gcov_node { | |||
| 51 | struct list_head children; | 52 | struct list_head children; |
| 52 | struct list_head all; | 53 | struct list_head all; |
| 53 | struct gcov_node *parent; | 54 | struct gcov_node *parent; |
| 54 | struct gcov_info *info; | 55 | struct gcov_info **loaded_info; |
| 55 | struct gcov_info *ghost; | 56 | struct gcov_info *unloaded_info; |
| 56 | struct dentry *dentry; | 57 | struct dentry *dentry; |
| 57 | struct dentry **links; | 58 | struct dentry **links; |
| 59 | int num_loaded; | ||
| 58 | char name[0]; | 60 | char name[0]; |
| 59 | }; | 61 | }; |
| 60 | 62 | ||
| @@ -136,16 +138,37 @@ static const struct seq_operations gcov_seq_ops = { | |||
| 136 | }; | 138 | }; |
| 137 | 139 | ||
| 138 | /* | 140 | /* |
| 139 | * Return the profiling data set for a given node. This can either be the | 141 | * Return a profiling data set associated with the given node. This is |
| 140 | * original profiling data structure or a duplicate (also called "ghost") | 142 | * either a data set for a loaded object file or a data set copy in case |
| 141 | * in case the associated object file has been unloaded. | 143 | * all associated object files have been unloaded. |
| 142 | */ | 144 | */ |
| 143 | static struct gcov_info *get_node_info(struct gcov_node *node) | 145 | static struct gcov_info *get_node_info(struct gcov_node *node) |
| 144 | { | 146 | { |
| 145 | if (node->info) | 147 | if (node->num_loaded > 0) |
| 146 | return node->info; | 148 | return node->loaded_info[0]; |
| 147 | 149 | ||
| 148 | return node->ghost; | 150 | return node->unloaded_info; |
| 151 | } | ||
| 152 | |||
| 153 | /* | ||
| 154 | * Return a newly allocated profiling data set which contains the sum of | ||
| 155 | * all profiling data associated with the given node. | ||
| 156 | */ | ||
| 157 | static struct gcov_info *get_accumulated_info(struct gcov_node *node) | ||
| 158 | { | ||
| 159 | struct gcov_info *info; | ||
| 160 | int i = 0; | ||
| 161 | |||
| 162 | if (node->unloaded_info) | ||
| 163 | info = gcov_info_dup(node->unloaded_info); | ||
| 164 | else | ||
| 165 | info = gcov_info_dup(node->loaded_info[i++]); | ||
| 166 | if (!info) | ||
| 167 | return NULL; | ||
| 168 | for (; i < node->num_loaded; i++) | ||
| 169 | gcov_info_add(info, node->loaded_info[i]); | ||
| 170 | |||
| 171 | return info; | ||
| 149 | } | 172 | } |
| 150 | 173 | ||
| 151 | /* | 174 | /* |
| @@ -163,9 +186,10 @@ static int gcov_seq_open(struct inode *inode, struct file *file) | |||
| 163 | mutex_lock(&node_lock); | 186 | mutex_lock(&node_lock); |
| 164 | /* | 187 | /* |
| 165 | * Read from a profiling data copy to minimize reference tracking | 188 | * Read from a profiling data copy to minimize reference tracking |
| 166 | * complexity and concurrent access. | 189 | * complexity and concurrent access and to keep accumulating multiple |
| 190 | * profiling data sets associated with one node simple. | ||
| 167 | */ | 191 | */ |
| 168 | info = gcov_info_dup(get_node_info(node)); | 192 | info = get_accumulated_info(node); |
| 169 | if (!info) | 193 | if (!info) |
| 170 | goto out_unlock; | 194 | goto out_unlock; |
| 171 | iter = gcov_iter_new(info); | 195 | iter = gcov_iter_new(info); |
| @@ -225,12 +249,25 @@ static struct gcov_node *get_node_by_name(const char *name) | |||
| 225 | return NULL; | 249 | return NULL; |
| 226 | } | 250 | } |
| 227 | 251 | ||
| 252 | /* | ||
| 253 | * Reset all profiling data associated with the specified node. | ||
| 254 | */ | ||
| 255 | static void reset_node(struct gcov_node *node) | ||
| 256 | { | ||
| 257 | int i; | ||
| 258 | |||
| 259 | if (node->unloaded_info) | ||
| 260 | gcov_info_reset(node->unloaded_info); | ||
| 261 | for (i = 0; i < node->num_loaded; i++) | ||
| 262 | gcov_info_reset(node->loaded_info[i]); | ||
| 263 | } | ||
| 264 | |||
| 228 | static void remove_node(struct gcov_node *node); | 265 | static void remove_node(struct gcov_node *node); |
| 229 | 266 | ||
| 230 | /* | 267 | /* |
| 231 | * write() implementation for gcov data files. Reset profiling data for the | 268 | * write() implementation for gcov data files. Reset profiling data for the |
| 232 | * associated file. If the object file has been unloaded (i.e. this is | 269 | * corresponding file. If all associated object files have been unloaded, |
| 233 | * a "ghost" node), remove the debug fs node as well. | 270 | * remove the debug fs node as well. |
| 234 | */ | 271 | */ |
| 235 | static ssize_t gcov_seq_write(struct file *file, const char __user *addr, | 272 | static ssize_t gcov_seq_write(struct file *file, const char __user *addr, |
| 236 | size_t len, loff_t *pos) | 273 | size_t len, loff_t *pos) |
| @@ -245,10 +282,10 @@ static ssize_t gcov_seq_write(struct file *file, const char __user *addr, | |||
| 245 | node = get_node_by_name(info->filename); | 282 | node = get_node_by_name(info->filename); |
| 246 | if (node) { | 283 | if (node) { |
| 247 | /* Reset counts or remove node for unloaded modules. */ | 284 | /* Reset counts or remove node for unloaded modules. */ |
| 248 | if (node->ghost) | 285 | if (node->num_loaded == 0) |
| 249 | remove_node(node); | 286 | remove_node(node); |
| 250 | else | 287 | else |
| 251 | gcov_info_reset(node->info); | 288 | reset_node(node); |
| 252 | } | 289 | } |
| 253 | /* Reset counts for open file. */ | 290 | /* Reset counts for open file. */ |
| 254 | gcov_info_reset(info); | 291 | gcov_info_reset(info); |
| @@ -378,7 +415,10 @@ static void init_node(struct gcov_node *node, struct gcov_info *info, | |||
| 378 | INIT_LIST_HEAD(&node->list); | 415 | INIT_LIST_HEAD(&node->list); |
| 379 | INIT_LIST_HEAD(&node->children); | 416 | INIT_LIST_HEAD(&node->children); |
| 380 | INIT_LIST_HEAD(&node->all); | 417 | INIT_LIST_HEAD(&node->all); |
| 381 | node->info = info; | 418 | if (node->loaded_info) { |
| 419 | node->loaded_info[0] = info; | ||
| 420 | node->num_loaded = 1; | ||
| 421 | } | ||
| 382 | node->parent = parent; | 422 | node->parent = parent; |
| 383 | if (name) | 423 | if (name) |
| 384 | strcpy(node->name, name); | 424 | strcpy(node->name, name); |
| @@ -394,9 +434,13 @@ static struct gcov_node *new_node(struct gcov_node *parent, | |||
| 394 | struct gcov_node *node; | 434 | struct gcov_node *node; |
| 395 | 435 | ||
| 396 | node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL); | 436 | node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL); |
| 397 | if (!node) { | 437 | if (!node) |
| 398 | pr_warning("out of memory\n"); | 438 | goto err_nomem; |
| 399 | return NULL; | 439 | if (info) { |
| 440 | node->loaded_info = kcalloc(1, sizeof(struct gcov_info *), | ||
| 441 | GFP_KERNEL); | ||
| 442 | if (!node->loaded_info) | ||
| 443 | goto err_nomem; | ||
| 400 | } | 444 | } |
| 401 | init_node(node, info, name, parent); | 445 | init_node(node, info, name, parent); |
| 402 | /* Differentiate between gcov data file nodes and directory nodes. */ | 446 | /* Differentiate between gcov data file nodes and directory nodes. */ |
| @@ -416,6 +460,11 @@ static struct gcov_node *new_node(struct gcov_node *parent, | |||
| 416 | list_add(&node->all, &all_head); | 460 | list_add(&node->all, &all_head); |
| 417 | 461 | ||
| 418 | return node; | 462 | return node; |
| 463 | |||
| 464 | err_nomem: | ||
| 465 | kfree(node); | ||
| 466 | pr_warning("out of memory\n"); | ||
| 467 | return NULL; | ||
| 419 | } | 468 | } |
| 420 | 469 | ||
| 421 | /* Remove symbolic links associated with node. */ | 470 | /* Remove symbolic links associated with node. */ |
| @@ -441,8 +490,9 @@ static void release_node(struct gcov_node *node) | |||
| 441 | list_del(&node->all); | 490 | list_del(&node->all); |
| 442 | debugfs_remove(node->dentry); | 491 | debugfs_remove(node->dentry); |
| 443 | remove_links(node); | 492 | remove_links(node); |
| 444 | if (node->ghost) | 493 | kfree(node->loaded_info); |
| 445 | gcov_info_free(node->ghost); | 494 | if (node->unloaded_info) |
| 495 | gcov_info_free(node->unloaded_info); | ||
| 446 | kfree(node); | 496 | kfree(node); |
| 447 | } | 497 | } |
| 448 | 498 | ||
| @@ -477,7 +527,7 @@ static struct gcov_node *get_child_by_name(struct gcov_node *parent, | |||
| 477 | 527 | ||
| 478 | /* | 528 | /* |
| 479 | * write() implementation for reset file. Reset all profiling data to zero | 529 | * write() implementation for reset file. Reset all profiling data to zero |
| 480 | * and remove ghost nodes. | 530 | * and remove nodes for which all associated object files are unloaded. |
| 481 | */ | 531 | */ |
| 482 | static ssize_t reset_write(struct file *file, const char __user *addr, | 532 | static ssize_t reset_write(struct file *file, const char __user *addr, |
| 483 | size_t len, loff_t *pos) | 533 | size_t len, loff_t *pos) |
| @@ -487,8 +537,8 @@ static ssize_t reset_write(struct file *file, const char __user *addr, | |||
| 487 | mutex_lock(&node_lock); | 537 | mutex_lock(&node_lock); |
| 488 | restart: | 538 | restart: |
| 489 | list_for_each_entry(node, &all_head, all) { | 539 | list_for_each_entry(node, &all_head, all) { |
| 490 | if (node->info) | 540 | if (node->num_loaded > 0) |
| 491 | gcov_info_reset(node->info); | 541 | reset_node(node); |
| 492 | else if (list_empty(&node->children)) { | 542 | else if (list_empty(&node->children)) { |
| 493 | remove_node(node); | 543 | remove_node(node); |
| 494 | /* Several nodes may have gone - restart loop. */ | 544 | /* Several nodes may have gone - restart loop. */ |
| @@ -564,37 +614,115 @@ err_remove: | |||
| 564 | } | 614 | } |
| 565 | 615 | ||
| 566 | /* | 616 | /* |
| 567 | * The profiling data set associated with this node is being unloaded. Store a | 617 | * Associate a profiling data set with an existing node. Needs to be called |
| 568 | * copy of the profiling data and turn this node into a "ghost". | 618 | * with node_lock held. |
| 569 | */ | 619 | */ |
| 570 | static int ghost_node(struct gcov_node *node) | 620 | static void add_info(struct gcov_node *node, struct gcov_info *info) |
| 571 | { | 621 | { |
| 572 | node->ghost = gcov_info_dup(node->info); | 622 | struct gcov_info **loaded_info; |
| 573 | if (!node->ghost) { | 623 | int num = node->num_loaded; |
| 574 | pr_warning("could not save data for '%s' (out of memory)\n", | 624 | |
| 575 | node->info->filename); | 625 | /* |
| 576 | return -ENOMEM; | 626 | * Prepare new array. This is done first to simplify cleanup in |
| 627 | * case the new data set is incompatible, the node only contains | ||
| 628 | * unloaded data sets and there's not enough memory for the array. | ||
| 629 | */ | ||
| 630 | loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL); | ||
| 631 | if (!loaded_info) { | ||
| 632 | pr_warning("could not add '%s' (out of memory)\n", | ||
| 633 | info->filename); | ||
| 634 | return; | ||
| 635 | } | ||
| 636 | memcpy(loaded_info, node->loaded_info, | ||
| 637 | num * sizeof(struct gcov_info *)); | ||
| 638 | loaded_info[num] = info; | ||
| 639 | /* Check if the new data set is compatible. */ | ||
| 640 | if (num == 0) { | ||
| 641 | /* | ||
| 642 | * A module was unloaded, modified and reloaded. The new | ||
| 643 | * data set replaces the copy of the last one. | ||
| 644 | */ | ||
| 645 | if (!gcov_info_is_compatible(node->unloaded_info, info)) { | ||
| 646 | pr_warning("discarding saved data for %s " | ||
| 647 | "(incompatible version)\n", info->filename); | ||
| 648 | gcov_info_free(node->unloaded_info); | ||
| 649 | node->unloaded_info = NULL; | ||
| 650 | } | ||
| 651 | } else { | ||
| 652 | /* | ||
| 653 | * Two different versions of the same object file are loaded. | ||
| 654 | * The initial one takes precedence. | ||
| 655 | */ | ||
| 656 | if (!gcov_info_is_compatible(node->loaded_info[0], info)) { | ||
| 657 | pr_warning("could not add '%s' (incompatible " | ||
| 658 | "version)\n", info->filename); | ||
| 659 | kfree(loaded_info); | ||
| 660 | return; | ||
| 661 | } | ||
| 577 | } | 662 | } |
| 578 | node->info = NULL; | 663 | /* Overwrite previous array. */ |
| 664 | kfree(node->loaded_info); | ||
| 665 | node->loaded_info = loaded_info; | ||
| 666 | node->num_loaded = num + 1; | ||
| 667 | } | ||
| 579 | 668 | ||
| 580 | return 0; | 669 | /* |
| 670 | * Return the index of a profiling data set associated with a node. | ||
| 671 | */ | ||
| 672 | static int get_info_index(struct gcov_node *node, struct gcov_info *info) | ||
| 673 | { | ||
| 674 | int i; | ||
| 675 | |||
| 676 | for (i = 0; i < node->num_loaded; i++) { | ||
| 677 | if (node->loaded_info[i] == info) | ||
| 678 | return i; | ||
| 679 | } | ||
| 680 | return -ENOENT; | ||
| 581 | } | 681 | } |
| 582 | 682 | ||
| 583 | /* | 683 | /* |
| 584 | * Profiling data for this node has been loaded again. Add profiling data | 684 | * Save the data of a profiling data set which is being unloaded. |
| 585 | * from previous instantiation and turn this node into a regular node. | ||
| 586 | */ | 685 | */ |
| 587 | static void revive_node(struct gcov_node *node, struct gcov_info *info) | 686 | static void save_info(struct gcov_node *node, struct gcov_info *info) |
| 588 | { | 687 | { |
| 589 | if (gcov_info_is_compatible(node->ghost, info)) | 688 | if (node->unloaded_info) |
| 590 | gcov_info_add(info, node->ghost); | 689 | gcov_info_add(node->unloaded_info, info); |
| 591 | else { | 690 | else { |
| 592 | pr_warning("discarding saved data for '%s' (version changed)\n", | 691 | node->unloaded_info = gcov_info_dup(info); |
| 692 | if (!node->unloaded_info) { | ||
| 693 | pr_warning("could not save data for '%s' " | ||
| 694 | "(out of memory)\n", info->filename); | ||
| 695 | } | ||
| 696 | } | ||
| 697 | } | ||
| 698 | |||
| 699 | /* | ||
| 700 | * Disassociate a profiling data set from a node. Needs to be called with | ||
| 701 | * node_lock held. | ||
| 702 | */ | ||
| 703 | static void remove_info(struct gcov_node *node, struct gcov_info *info) | ||
| 704 | { | ||
| 705 | int i; | ||
| 706 | |||
| 707 | i = get_info_index(node, info); | ||
| 708 | if (i < 0) { | ||
| 709 | pr_warning("could not remove '%s' (not found)\n", | ||
| 593 | info->filename); | 710 | info->filename); |
| 711 | return; | ||
| 594 | } | 712 | } |
| 595 | gcov_info_free(node->ghost); | 713 | if (gcov_persist) |
| 596 | node->ghost = NULL; | 714 | save_info(node, info); |
| 597 | node->info = info; | 715 | /* Shrink array. */ |
| 716 | node->loaded_info[i] = node->loaded_info[node->num_loaded - 1]; | ||
| 717 | node->num_loaded--; | ||
| 718 | if (node->num_loaded > 0) | ||
| 719 | return; | ||
| 720 | /* Last loaded data set was removed. */ | ||
| 721 | kfree(node->loaded_info); | ||
| 722 | node->loaded_info = NULL; | ||
| 723 | node->num_loaded = 0; | ||
| 724 | if (!node->unloaded_info) | ||
| 725 | remove_node(node); | ||
| 598 | } | 726 | } |
| 599 | 727 | ||
| 600 | /* | 728 | /* |
| @@ -609,30 +737,18 @@ void gcov_event(enum gcov_action action, struct gcov_info *info) | |||
| 609 | node = get_node_by_name(info->filename); | 737 | node = get_node_by_name(info->filename); |
| 610 | switch (action) { | 738 | switch (action) { |
| 611 | case GCOV_ADD: | 739 | case GCOV_ADD: |
| 612 | /* Add new node or revive ghost. */ | 740 | if (node) |
| 613 | if (!node) { | 741 | add_info(node, info); |
| 742 | else | ||
| 614 | add_node(info); | 743 | add_node(info); |
| 615 | break; | ||
| 616 | } | ||
| 617 | if (gcov_persist) | ||
| 618 | revive_node(node, info); | ||
| 619 | else { | ||
| 620 | pr_warning("could not add '%s' (already exists)\n", | ||
| 621 | info->filename); | ||
| 622 | } | ||
| 623 | break; | 744 | break; |
| 624 | case GCOV_REMOVE: | 745 | case GCOV_REMOVE: |
| 625 | /* Remove node or turn into ghost. */ | 746 | if (node) |
| 626 | if (!node) { | 747 | remove_info(node, info); |
| 748 | else { | ||
| 627 | pr_warning("could not remove '%s' (not found)\n", | 749 | pr_warning("could not remove '%s' (not found)\n", |
| 628 | info->filename); | 750 | info->filename); |
| 629 | break; | ||
| 630 | } | 751 | } |
| 631 | if (gcov_persist) { | ||
| 632 | if (!ghost_node(node)) | ||
| 633 | break; | ||
| 634 | } | ||
| 635 | remove_node(node); | ||
| 636 | break; | 752 | break; |
| 637 | } | 753 | } |
| 638 | mutex_unlock(&node_lock); | 754 | mutex_unlock(&node_lock); |
