aboutsummaryrefslogtreecommitdiffstats
path: root/kernel/gcov/fs.c
diff options
context:
space:
mode:
authorGrant Likely <grant.likely@secretlab.ca>2010-12-30 00:20:30 -0500
committerGrant Likely <grant.likely@secretlab.ca>2010-12-30 00:21:47 -0500
commitd392da5207352f09030e95d9ea335a4225667ec0 (patch)
tree7d6cd1932afcad0a5619a5c504a6d93ca318187c /kernel/gcov/fs.c
parente39d5ef678045d61812c1401f04fe8edb14d6359 (diff)
parent387c31c7e5c9805b0aef8833d1731a5fe7bdea14 (diff)
Merge v2.6.37-rc8 into powerpc/next
Diffstat (limited to 'kernel/gcov/fs.c')
-rw-r--r--kernel/gcov/fs.c245
1 files changed, 181 insertions, 64 deletions
diff --git a/kernel/gcov/fs.c b/kernel/gcov/fs.c
index ef3c3f88a7a3..9bd0934f6c33 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 */
143static struct gcov_info *get_node_info(struct gcov_node *node) 145static 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 */
157static 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 */
255static 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
228static void remove_node(struct gcov_node *node); 265static 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 */
235static ssize_t gcov_seq_write(struct file *file, const char __user *addr, 272static 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
464err_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 */
482static ssize_t reset_write(struct file *file, const char __user *addr, 532static 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);
488restart: 538restart:
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. */
@@ -511,6 +561,7 @@ static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
511static const struct file_operations gcov_reset_fops = { 561static const struct file_operations gcov_reset_fops = {
512 .write = reset_write, 562 .write = reset_write,
513 .read = reset_read, 563 .read = reset_read,
564 .llseek = noop_llseek,
514}; 565};
515 566
516/* 567/*
@@ -564,37 +615,115 @@ err_remove:
564} 615}
565 616
566/* 617/*
567 * The profiling data set associated with this node is being unloaded. Store a 618 * 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". 619 * with node_lock held.
569 */ 620 */
570static int ghost_node(struct gcov_node *node) 621static void add_info(struct gcov_node *node, struct gcov_info *info)
571{ 622{
572 node->ghost = gcov_info_dup(node->info); 623 struct gcov_info **loaded_info;
573 if (!node->ghost) { 624 int num = node->num_loaded;
574 pr_warning("could not save data for '%s' (out of memory)\n", 625
575 node->info->filename); 626 /*
576 return -ENOMEM; 627 * Prepare new array. This is done first to simplify cleanup in
628 * case the new data set is incompatible, the node only contains
629 * unloaded data sets and there's not enough memory for the array.
630 */
631 loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
632 if (!loaded_info) {
633 pr_warning("could not add '%s' (out of memory)\n",
634 info->filename);
635 return;
636 }
637 memcpy(loaded_info, node->loaded_info,
638 num * sizeof(struct gcov_info *));
639 loaded_info[num] = info;
640 /* Check if the new data set is compatible. */
641 if (num == 0) {
642 /*
643 * A module was unloaded, modified and reloaded. The new
644 * data set replaces the copy of the last one.
645 */
646 if (!gcov_info_is_compatible(node->unloaded_info, info)) {
647 pr_warning("discarding saved data for %s "
648 "(incompatible version)\n", info->filename);
649 gcov_info_free(node->unloaded_info);
650 node->unloaded_info = NULL;
651 }
652 } else {
653 /*
654 * Two different versions of the same object file are loaded.
655 * The initial one takes precedence.
656 */
657 if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
658 pr_warning("could not add '%s' (incompatible "
659 "version)\n", info->filename);
660 kfree(loaded_info);
661 return;
662 }
577 } 663 }
578 node->info = NULL; 664 /* Overwrite previous array. */
665 kfree(node->loaded_info);
666 node->loaded_info = loaded_info;
667 node->num_loaded = num + 1;
668}
579 669
580 return 0; 670/*
671 * Return the index of a profiling data set associated with a node.
672 */
673static int get_info_index(struct gcov_node *node, struct gcov_info *info)
674{
675 int i;
676
677 for (i = 0; i < node->num_loaded; i++) {
678 if (node->loaded_info[i] == info)
679 return i;
680 }
681 return -ENOENT;
581} 682}
582 683
583/* 684/*
584 * Profiling data for this node has been loaded again. Add profiling data 685 * 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 */ 686 */
587static void revive_node(struct gcov_node *node, struct gcov_info *info) 687static void save_info(struct gcov_node *node, struct gcov_info *info)
588{ 688{
589 if (gcov_info_is_compatible(node->ghost, info)) 689 if (node->unloaded_info)
590 gcov_info_add(info, node->ghost); 690 gcov_info_add(node->unloaded_info, info);
591 else { 691 else {
592 pr_warning("discarding saved data for '%s' (version changed)\n", 692 node->unloaded_info = gcov_info_dup(info);
693 if (!node->unloaded_info) {
694 pr_warning("could not save data for '%s' "
695 "(out of memory)\n", info->filename);
696 }
697 }
698}
699
700/*
701 * Disassociate a profiling data set from a node. Needs to be called with
702 * node_lock held.
703 */
704static void remove_info(struct gcov_node *node, struct gcov_info *info)
705{
706 int i;
707
708 i = get_info_index(node, info);
709 if (i < 0) {
710 pr_warning("could not remove '%s' (not found)\n",
593 info->filename); 711 info->filename);
712 return;
594 } 713 }
595 gcov_info_free(node->ghost); 714 if (gcov_persist)
596 node->ghost = NULL; 715 save_info(node, info);
597 node->info = info; 716 /* Shrink array. */
717 node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
718 node->num_loaded--;
719 if (node->num_loaded > 0)
720 return;
721 /* Last loaded data set was removed. */
722 kfree(node->loaded_info);
723 node->loaded_info = NULL;
724 node->num_loaded = 0;
725 if (!node->unloaded_info)
726 remove_node(node);
598} 727}
599 728
600/* 729/*
@@ -609,30 +738,18 @@ void gcov_event(enum gcov_action action, struct gcov_info *info)
609 node = get_node_by_name(info->filename); 738 node = get_node_by_name(info->filename);
610 switch (action) { 739 switch (action) {
611 case GCOV_ADD: 740 case GCOV_ADD:
612 /* Add new node or revive ghost. */ 741 if (node)
613 if (!node) { 742 add_info(node, info);
743 else
614 add_node(info); 744 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; 745 break;
624 case GCOV_REMOVE: 746 case GCOV_REMOVE:
625 /* Remove node or turn into ghost. */ 747 if (node)
626 if (!node) { 748 remove_info(node, info);
749 else {
627 pr_warning("could not remove '%s' (not found)\n", 750 pr_warning("could not remove '%s' (not found)\n",
628 info->filename); 751 info->filename);
629 break;
630 } 752 }
631 if (gcov_persist) {
632 if (!ghost_node(node))
633 break;
634 }
635 remove_node(node);
636 break; 753 break;
637 } 754 }
638 mutex_unlock(&node_lock); 755 mutex_unlock(&node_lock);