Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 210447
b: refs/heads/master
c: 85a0fdf
h: refs/heads/master
i:
  210445: ba61e93
  210443: 0d1fec1
  210439: 80816f3
  210431: 45cf26c
v: v3
  • Loading branch information
Peter Oberparleiter authored and Linus Torvalds committed Sep 10, 2010
1 parent c1b1dd8 commit 0509e57
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 65 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: 2f327dad14aa8bc939a4f0a2d3fdcf64a2d8c09e
refs/heads/master: 85a0fdfd0f967507f3903e8419bc7e408f5a59de
244 changes: 180 additions & 64 deletions trunk/kernel/gcov/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
* @children: child nodes
* @all: list head for list of all nodes
* @parent: parent node
* @info: associated profiling data structure if not a directory
* @ghost: when an object file containing profiling data is unloaded we keep a
* copy of the profiling data here to allow collecting coverage data
* for cleanup code. Such a node is called a "ghost".
* @loaded_info: array of pointers to profiling data sets for loaded object
* files.
* @num_loaded: number of profiling data sets for loaded object files.
* @unloaded_info: accumulated copy of profiling data sets for unloaded
* object files. Used only when gcov_persist=1.
* @dentry: main debugfs entry, either a directory or data file
* @links: associated symbolic links
* @name: data file basename
Expand All @@ -51,10 +52,11 @@ struct gcov_node {
struct list_head children;
struct list_head all;
struct gcov_node *parent;
struct gcov_info *info;
struct gcov_info *ghost;
struct gcov_info **loaded_info;
struct gcov_info *unloaded_info;
struct dentry *dentry;
struct dentry **links;
int num_loaded;
char name[0];
};

Expand Down Expand Up @@ -136,16 +138,37 @@ static const struct seq_operations gcov_seq_ops = {
};

/*
* Return the profiling data set for a given node. This can either be the
* original profiling data structure or a duplicate (also called "ghost")
* in case the associated object file has been unloaded.
* Return a profiling data set associated with the given node. This is
* either a data set for a loaded object file or a data set copy in case
* all associated object files have been unloaded.
*/
static struct gcov_info *get_node_info(struct gcov_node *node)
{
if (node->info)
return node->info;
if (node->num_loaded > 0)
return node->loaded_info[0];

return node->ghost;
return node->unloaded_info;
}

/*
* Return a newly allocated profiling data set which contains the sum of
* all profiling data associated with the given node.
*/
static struct gcov_info *get_accumulated_info(struct gcov_node *node)
{
struct gcov_info *info;
int i = 0;

if (node->unloaded_info)
info = gcov_info_dup(node->unloaded_info);
else
info = gcov_info_dup(node->loaded_info[i++]);
if (!info)
return NULL;
for (; i < node->num_loaded; i++)
gcov_info_add(info, node->loaded_info[i]);

return info;
}

/*
Expand All @@ -163,9 +186,10 @@ static int gcov_seq_open(struct inode *inode, struct file *file)
mutex_lock(&node_lock);
/*
* Read from a profiling data copy to minimize reference tracking
* complexity and concurrent access.
* complexity and concurrent access and to keep accumulating multiple
* profiling data sets associated with one node simple.
*/
info = gcov_info_dup(get_node_info(node));
info = get_accumulated_info(node);
if (!info)
goto out_unlock;
iter = gcov_iter_new(info);
Expand Down Expand Up @@ -225,12 +249,25 @@ static struct gcov_node *get_node_by_name(const char *name)
return NULL;
}

/*
* Reset all profiling data associated with the specified node.
*/
static void reset_node(struct gcov_node *node)
{
int i;

if (node->unloaded_info)
gcov_info_reset(node->unloaded_info);
for (i = 0; i < node->num_loaded; i++)
gcov_info_reset(node->loaded_info[i]);
}

static void remove_node(struct gcov_node *node);

/*
* write() implementation for gcov data files. Reset profiling data for the
* associated file. If the object file has been unloaded (i.e. this is
* a "ghost" node), remove the debug fs node as well.
* corresponding file. If all associated object files have been unloaded,
* remove the debug fs node as well.
*/
static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
size_t len, loff_t *pos)
Expand All @@ -245,10 +282,10 @@ static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
node = get_node_by_name(info->filename);
if (node) {
/* Reset counts or remove node for unloaded modules. */
if (node->ghost)
if (node->num_loaded == 0)
remove_node(node);
else
gcov_info_reset(node->info);
reset_node(node);
}
/* Reset counts for open file. */
gcov_info_reset(info);
Expand Down Expand Up @@ -378,7 +415,10 @@ static void init_node(struct gcov_node *node, struct gcov_info *info,
INIT_LIST_HEAD(&node->list);
INIT_LIST_HEAD(&node->children);
INIT_LIST_HEAD(&node->all);
node->info = info;
if (node->loaded_info) {
node->loaded_info[0] = info;
node->num_loaded = 1;
}
node->parent = parent;
if (name)
strcpy(node->name, name);
Expand All @@ -394,9 +434,13 @@ static struct gcov_node *new_node(struct gcov_node *parent,
struct gcov_node *node;

node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
if (!node) {
pr_warning("out of memory\n");
return NULL;
if (!node)
goto err_nomem;
if (info) {
node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
GFP_KERNEL);
if (!node->loaded_info)
goto err_nomem;
}
init_node(node, info, name, parent);
/* Differentiate between gcov data file nodes and directory nodes. */
Expand All @@ -416,6 +460,11 @@ static struct gcov_node *new_node(struct gcov_node *parent,
list_add(&node->all, &all_head);

return node;

err_nomem:
kfree(node);
pr_warning("out of memory\n");
return NULL;
}

/* Remove symbolic links associated with node. */
Expand All @@ -441,8 +490,9 @@ static void release_node(struct gcov_node *node)
list_del(&node->all);
debugfs_remove(node->dentry);
remove_links(node);
if (node->ghost)
gcov_info_free(node->ghost);
kfree(node->loaded_info);
if (node->unloaded_info)
gcov_info_free(node->unloaded_info);
kfree(node);
}

Expand Down Expand Up @@ -477,7 +527,7 @@ static struct gcov_node *get_child_by_name(struct gcov_node *parent,

/*
* write() implementation for reset file. Reset all profiling data to zero
* and remove ghost nodes.
* and remove nodes for which all associated object files are unloaded.
*/
static ssize_t reset_write(struct file *file, const char __user *addr,
size_t len, loff_t *pos)
Expand All @@ -487,8 +537,8 @@ static ssize_t reset_write(struct file *file, const char __user *addr,
mutex_lock(&node_lock);
restart:
list_for_each_entry(node, &all_head, all) {
if (node->info)
gcov_info_reset(node->info);
if (node->num_loaded > 0)
reset_node(node);
else if (list_empty(&node->children)) {
remove_node(node);
/* Several nodes may have gone - restart loop. */
Expand Down Expand Up @@ -564,37 +614,115 @@ static void add_node(struct gcov_info *info)
}

/*
* The profiling data set associated with this node is being unloaded. Store a
* copy of the profiling data and turn this node into a "ghost".
* Associate a profiling data set with an existing node. Needs to be called
* with node_lock held.
*/
static int ghost_node(struct gcov_node *node)
static void add_info(struct gcov_node *node, struct gcov_info *info)
{
node->ghost = gcov_info_dup(node->info);
if (!node->ghost) {
pr_warning("could not save data for '%s' (out of memory)\n",
node->info->filename);
return -ENOMEM;
struct gcov_info **loaded_info;
int num = node->num_loaded;

/*
* Prepare new array. This is done first to simplify cleanup in
* case the new data set is incompatible, the node only contains
* unloaded data sets and there's not enough memory for the array.
*/
loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
if (!loaded_info) {
pr_warning("could not add '%s' (out of memory)\n",
info->filename);
return;
}
memcpy(loaded_info, node->loaded_info,
num * sizeof(struct gcov_info *));
loaded_info[num] = info;
/* Check if the new data set is compatible. */
if (num == 0) {
/*
* A module was unloaded, modified and reloaded. The new
* data set replaces the copy of the last one.
*/
if (!gcov_info_is_compatible(node->unloaded_info, info)) {
pr_warning("discarding saved data for %s "
"(incompatible version)\n", info->filename);
gcov_info_free(node->unloaded_info);
node->unloaded_info = NULL;
}
} else {
/*
* Two different versions of the same object file are loaded.
* The initial one takes precedence.
*/
if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
pr_warning("could not add '%s' (incompatible "
"version)\n", info->filename);
kfree(loaded_info);
return;
}
}
node->info = NULL;
/* Overwrite previous array. */
kfree(node->loaded_info);
node->loaded_info = loaded_info;
node->num_loaded = num + 1;
}

return 0;
/*
* Return the index of a profiling data set associated with a node.
*/
static int get_info_index(struct gcov_node *node, struct gcov_info *info)
{
int i;

for (i = 0; i < node->num_loaded; i++) {
if (node->loaded_info[i] == info)
return i;
}
return -ENOENT;
}

/*
* Profiling data for this node has been loaded again. Add profiling data
* from previous instantiation and turn this node into a regular node.
* Save the data of a profiling data set which is being unloaded.
*/
static void revive_node(struct gcov_node *node, struct gcov_info *info)
static void save_info(struct gcov_node *node, struct gcov_info *info)
{
if (gcov_info_is_compatible(node->ghost, info))
gcov_info_add(info, node->ghost);
if (node->unloaded_info)
gcov_info_add(node->unloaded_info, info);
else {
pr_warning("discarding saved data for '%s' (version changed)\n",
node->unloaded_info = gcov_info_dup(info);
if (!node->unloaded_info) {
pr_warning("could not save data for '%s' "
"(out of memory)\n", info->filename);
}
}
}

/*
* Disassociate a profiling data set from a node. Needs to be called with
* node_lock held.
*/
static void remove_info(struct gcov_node *node, struct gcov_info *info)
{
int i;

i = get_info_index(node, info);
if (i < 0) {
pr_warning("could not remove '%s' (not found)\n",
info->filename);
return;
}
gcov_info_free(node->ghost);
node->ghost = NULL;
node->info = info;
if (gcov_persist)
save_info(node, info);
/* Shrink array. */
node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
node->num_loaded--;
if (node->num_loaded > 0)
return;
/* Last loaded data set was removed. */
kfree(node->loaded_info);
node->loaded_info = NULL;
node->num_loaded = 0;
if (!node->unloaded_info)
remove_node(node);
}

/*
Expand All @@ -609,30 +737,18 @@ void gcov_event(enum gcov_action action, struct gcov_info *info)
node = get_node_by_name(info->filename);
switch (action) {
case GCOV_ADD:
/* Add new node or revive ghost. */
if (!node) {
if (node)
add_info(node, info);
else
add_node(info);
break;
}
if (gcov_persist)
revive_node(node, info);
else {
pr_warning("could not add '%s' (already exists)\n",
info->filename);
}
break;
case GCOV_REMOVE:
/* Remove node or turn into ghost. */
if (!node) {
if (node)
remove_info(node, info);
else {
pr_warning("could not remove '%s' (not found)\n",
info->filename);
break;
}
if (gcov_persist) {
if (!ghost_node(node))
break;
}
remove_node(node);
break;
}
mutex_unlock(&node_lock);
Expand Down

0 comments on commit 0509e57

Please sign in to comment.