linux/kernel/gcov/fs.c
<<
>>
Prefs
   1/*
   2 *  This code exports profiling data as debugfs files to userspace.
   3 *
   4 *    Copyright IBM Corp. 2009
   5 *    Author(s): Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
   6 *
   7 *    Uses gcc-internal data definitions.
   8 *    Based on the gcov-kernel patch by:
   9 *               Hubertus Franke <frankeh@us.ibm.com>
  10 *               Nigel Hinds <nhinds@us.ibm.com>
  11 *               Rajan Ravindran <rajancr@us.ibm.com>
  12 *               Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
  13 *               Paul Larson
  14 *               Yi CDL Yang
  15 */
  16
  17#define pr_fmt(fmt)     "gcov: " fmt
  18
  19#include <linux/init.h>
  20#include <linux/module.h>
  21#include <linux/debugfs.h>
  22#include <linux/fs.h>
  23#include <linux/list.h>
  24#include <linux/string.h>
  25#include <linux/slab.h>
  26#include <linux/mutex.h>
  27#include <linux/seq_file.h>
  28#include "gcov.h"
  29
  30/**
  31 * struct gcov_node - represents a debugfs entry
  32 * @list: list head for child node list
  33 * @children: child nodes
  34 * @all: list head for list of all nodes
  35 * @parent: parent node
  36 * @info: associated profiling data structure if not a directory
  37 * @ghost: when an object file containing profiling data is unloaded we keep a
  38 *         copy of the profiling data here to allow collecting coverage data
  39 *         for cleanup code. Such a node is called a "ghost".
  40 * @dentry: main debugfs entry, either a directory or data file
  41 * @links: associated symbolic links
  42 * @name: data file basename
  43 *
  44 * struct gcov_node represents an entity within the gcov/ subdirectory
  45 * of debugfs. There are directory and data file nodes. The latter represent
  46 * the actual synthesized data file plus any associated symbolic links which
  47 * are needed by the gcov tool to work correctly.
  48 */
  49struct gcov_node {
  50        struct list_head list;
  51        struct list_head children;
  52        struct list_head all;
  53        struct gcov_node *parent;
  54        struct gcov_info *info;
  55        struct gcov_info *ghost;
  56        struct dentry *dentry;
  57        struct dentry **links;
  58        char name[0];
  59};
  60
  61static const char objtree[] = OBJTREE;
  62static const char srctree[] = SRCTREE;
  63static struct gcov_node root_node;
  64static struct dentry *reset_dentry;
  65static LIST_HEAD(all_head);
  66static DEFINE_MUTEX(node_lock);
  67
  68/* If non-zero, keep copies of profiling data for unloaded modules. */
  69static int gcov_persist = 1;
  70
  71static int __init gcov_persist_setup(char *str)
  72{
  73        unsigned long val;
  74
  75        if (strict_strtoul(str, 0, &val)) {
  76                pr_warning("invalid gcov_persist parameter '%s'\n", str);
  77                return 0;
  78        }
  79        gcov_persist = val;
  80        pr_info("setting gcov_persist to %d\n", gcov_persist);
  81
  82        return 1;
  83}
  84__setup("gcov_persist=", gcov_persist_setup);
  85
  86/*
  87 * seq_file.start() implementation for gcov data files. Note that the
  88 * gcov_iterator interface is designed to be more restrictive than seq_file
  89 * (no start from arbitrary position, etc.), to simplify the iterator
  90 * implementation.
  91 */
  92static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
  93{
  94        loff_t i;
  95
  96        gcov_iter_start(seq->private);
  97        for (i = 0; i < *pos; i++) {
  98                if (gcov_iter_next(seq->private))
  99                        return NULL;
 100        }
 101        return seq->private;
 102}
 103
 104/* seq_file.next() implementation for gcov data files. */
 105static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
 106{
 107        struct gcov_iterator *iter = data;
 108
 109        if (gcov_iter_next(iter))
 110                return NULL;
 111        (*pos)++;
 112
 113        return iter;
 114}
 115
 116/* seq_file.show() implementation for gcov data files. */
 117static int gcov_seq_show(struct seq_file *seq, void *data)
 118{
 119        struct gcov_iterator *iter = data;
 120
 121        if (gcov_iter_write(iter, seq))
 122                return -EINVAL;
 123        return 0;
 124}
 125
 126static void gcov_seq_stop(struct seq_file *seq, void *data)
 127{
 128        /* Unused. */
 129}
 130
 131static const struct seq_operations gcov_seq_ops = {
 132        .start  = gcov_seq_start,
 133        .next   = gcov_seq_next,
 134        .show   = gcov_seq_show,
 135        .stop   = gcov_seq_stop,
 136};
 137
 138/*
 139 * Return the profiling data set for a given node. This can either be the
 140 * original profiling data structure or a duplicate (also called "ghost")
 141 * in case the associated object file has been unloaded.
 142 */
 143static struct gcov_info *get_node_info(struct gcov_node *node)
 144{
 145        if (node->info)
 146                return node->info;
 147
 148        return node->ghost;
 149}
 150
 151/*
 152 * open() implementation for gcov data files. Create a copy of the profiling
 153 * data set and initialize the iterator and seq_file interface.
 154 */
 155static int gcov_seq_open(struct inode *inode, struct file *file)
 156{
 157        struct gcov_node *node = inode->i_private;
 158        struct gcov_iterator *iter;
 159        struct seq_file *seq;
 160        struct gcov_info *info;
 161        int rc = -ENOMEM;
 162
 163        mutex_lock(&node_lock);
 164        /*
 165         * Read from a profiling data copy to minimize reference tracking
 166         * complexity and concurrent access.
 167         */
 168        info = gcov_info_dup(get_node_info(node));
 169        if (!info)
 170                goto out_unlock;
 171        iter = gcov_iter_new(info);
 172        if (!iter)
 173                goto err_free_info;
 174        rc = seq_open(file, &gcov_seq_ops);
 175        if (rc)
 176                goto err_free_iter_info;
 177        seq = file->private_data;
 178        seq->private = iter;
 179out_unlock:
 180        mutex_unlock(&node_lock);
 181        return rc;
 182
 183err_free_iter_info:
 184        gcov_iter_free(iter);
 185err_free_info:
 186        gcov_info_free(info);
 187        goto out_unlock;
 188}
 189
 190/*
 191 * release() implementation for gcov data files. Release resources allocated
 192 * by open().
 193 */
 194static int gcov_seq_release(struct inode *inode, struct file *file)
 195{
 196        struct gcov_iterator *iter;
 197        struct gcov_info *info;
 198        struct seq_file *seq;
 199
 200        seq = file->private_data;
 201        iter = seq->private;
 202        info = gcov_iter_get_info(iter);
 203        gcov_iter_free(iter);
 204        gcov_info_free(info);
 205        seq_release(inode, file);
 206
 207        return 0;
 208}
 209
 210/*
 211 * Find a node by the associated data file name. Needs to be called with
 212 * node_lock held.
 213 */
 214static struct gcov_node *get_node_by_name(const char *name)
 215{
 216        struct gcov_node *node;
 217        struct gcov_info *info;
 218
 219        list_for_each_entry(node, &all_head, all) {
 220                info = get_node_info(node);
 221                if (info && (strcmp(info->filename, name) == 0))
 222                        return node;
 223        }
 224
 225        return NULL;
 226}
 227
 228static void remove_node(struct gcov_node *node);
 229
 230/*
 231 * 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
 233 * a "ghost" node), remove the debug fs node as well.
 234 */
 235static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
 236                              size_t len, loff_t *pos)
 237{
 238        struct seq_file *seq;
 239        struct gcov_info *info;
 240        struct gcov_node *node;
 241
 242        seq = file->private_data;
 243        info = gcov_iter_get_info(seq->private);
 244        mutex_lock(&node_lock);
 245        node = get_node_by_name(info->filename);
 246        if (node) {
 247                /* Reset counts or remove node for unloaded modules. */
 248                if (node->ghost)
 249                        remove_node(node);
 250                else
 251                        gcov_info_reset(node->info);
 252        }
 253        /* Reset counts for open file. */
 254        gcov_info_reset(info);
 255        mutex_unlock(&node_lock);
 256
 257        return len;
 258}
 259
 260/*
 261 * Given a string <path> representing a file path of format:
 262 *   path/to/file.gcda
 263 * construct and return a new string:
 264 *   <dir/>path/to/file.<ext>
 265 */
 266static char *link_target(const char *dir, const char *path, const char *ext)
 267{
 268        char *target;
 269        char *old_ext;
 270        char *copy;
 271
 272        copy = kstrdup(path, GFP_KERNEL);
 273        if (!copy)
 274                return NULL;
 275        old_ext = strrchr(copy, '.');
 276        if (old_ext)
 277                *old_ext = '\0';
 278        if (dir)
 279                target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
 280        else
 281                target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
 282        kfree(copy);
 283
 284        return target;
 285}
 286
 287/*
 288 * Construct a string representing the symbolic link target for the given
 289 * gcov data file name and link type. Depending on the link type and the
 290 * location of the data file, the link target can either point to a
 291 * subdirectory of srctree, objtree or in an external location.
 292 */
 293static char *get_link_target(const char *filename, const struct gcov_link *ext)
 294{
 295        const char *rel;
 296        char *result;
 297
 298        if (strncmp(filename, objtree, strlen(objtree)) == 0) {
 299                rel = filename + strlen(objtree) + 1;
 300                if (ext->dir == SRC_TREE)
 301                        result = link_target(srctree, rel, ext->ext);
 302                else
 303                        result = link_target(objtree, rel, ext->ext);
 304        } else {
 305                /* External compilation. */
 306                result = link_target(NULL, filename, ext->ext);
 307        }
 308
 309        return result;
 310}
 311
 312#define SKEW_PREFIX     ".tmp_"
 313
 314/*
 315 * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
 316 * for filename skewing caused by the mod-versioning mechanism.
 317 */
 318static const char *deskew(const char *basename)
 319{
 320        if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
 321                return basename + sizeof(SKEW_PREFIX) - 1;
 322        return basename;
 323}
 324
 325/*
 326 * Create links to additional files (usually .c and .gcno files) which the
 327 * gcov tool expects to find in the same directory as the gcov data file.
 328 */
 329static void add_links(struct gcov_node *node, struct dentry *parent)
 330{
 331        char *basename;
 332        char *target;
 333        int num;
 334        int i;
 335
 336        for (num = 0; gcov_link[num].ext; num++)
 337                /* Nothing. */;
 338        node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
 339        if (!node->links)
 340                return;
 341        for (i = 0; i < num; i++) {
 342                target = get_link_target(get_node_info(node)->filename,
 343                                         &gcov_link[i]);
 344                if (!target)
 345                        goto out_err;
 346                basename = strrchr(target, '/');
 347                if (!basename)
 348                        goto out_err;
 349                basename++;
 350                node->links[i] = debugfs_create_symlink(deskew(basename),
 351                                                        parent, target);
 352                if (!node->links[i])
 353                        goto out_err;
 354                kfree(target);
 355        }
 356
 357        return;
 358out_err:
 359        kfree(target);
 360        while (i-- > 0)
 361                debugfs_remove(node->links[i]);
 362        kfree(node->links);
 363        node->links = NULL;
 364}
 365
 366static const struct file_operations gcov_data_fops = {
 367        .open           = gcov_seq_open,
 368        .release        = gcov_seq_release,
 369        .read           = seq_read,
 370        .llseek         = seq_lseek,
 371        .write          = gcov_seq_write,
 372};
 373
 374/* Basic initialization of a new node. */
 375static void init_node(struct gcov_node *node, struct gcov_info *info,
 376                      const char *name, struct gcov_node *parent)
 377{
 378        INIT_LIST_HEAD(&node->list);
 379        INIT_LIST_HEAD(&node->children);
 380        INIT_LIST_HEAD(&node->all);
 381        node->info = info;
 382        node->parent = parent;
 383        if (name)
 384                strcpy(node->name, name);
 385}
 386
 387/*
 388 * Create a new node and associated debugfs entry. Needs to be called with
 389 * node_lock held.
 390 */
 391static struct gcov_node *new_node(struct gcov_node *parent,
 392                                  struct gcov_info *info, const char *name)
 393{
 394        struct gcov_node *node;
 395
 396        node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
 397        if (!node) {
 398                pr_warning("out of memory\n");
 399                return NULL;
 400        }
 401        init_node(node, info, name, parent);
 402        /* Differentiate between gcov data file nodes and directory nodes. */
 403        if (info) {
 404                node->dentry = debugfs_create_file(deskew(node->name), 0600,
 405                                        parent->dentry, node, &gcov_data_fops);
 406        } else
 407                node->dentry = debugfs_create_dir(node->name, parent->dentry);
 408        if (!node->dentry) {
 409                pr_warning("could not create file\n");
 410                kfree(node);
 411                return NULL;
 412        }
 413        if (info)
 414                add_links(node, parent->dentry);
 415        list_add(&node->list, &parent->children);
 416        list_add(&node->all, &all_head);
 417
 418        return node;
 419}
 420
 421/* Remove symbolic links associated with node. */
 422static void remove_links(struct gcov_node *node)
 423{
 424        int i;
 425
 426        if (!node->links)
 427                return;
 428        for (i = 0; gcov_link[i].ext; i++)
 429                debugfs_remove(node->links[i]);
 430        kfree(node->links);
 431        node->links = NULL;
 432}
 433
 434/*
 435 * Remove node from all lists and debugfs and release associated resources.
 436 * Needs to be called with node_lock held.
 437 */
 438static void release_node(struct gcov_node *node)
 439{
 440        list_del(&node->list);
 441        list_del(&node->all);
 442        debugfs_remove(node->dentry);
 443        remove_links(node);
 444        if (node->ghost)
 445                gcov_info_free(node->ghost);
 446        kfree(node);
 447}
 448
 449/* Release node and empty parents. Needs to be called with node_lock held. */
 450static void remove_node(struct gcov_node *node)
 451{
 452        struct gcov_node *parent;
 453
 454        while ((node != &root_node) && list_empty(&node->children)) {
 455                parent = node->parent;
 456                release_node(node);
 457                node = parent;
 458        }
 459}
 460
 461/*
 462 * Find child node with given basename. Needs to be called with node_lock
 463 * held.
 464 */
 465static struct gcov_node *get_child_by_name(struct gcov_node *parent,
 466                                           const char *name)
 467{
 468        struct gcov_node *node;
 469
 470        list_for_each_entry(node, &parent->children, list) {
 471                if (strcmp(node->name, name) == 0)
 472                        return node;
 473        }
 474
 475        return NULL;
 476}
 477
 478/*
 479 * write() implementation for reset file. Reset all profiling data to zero
 480 * and remove ghost nodes.
 481 */
 482static ssize_t reset_write(struct file *file, const char __user *addr,
 483                           size_t len, loff_t *pos)
 484{
 485        struct gcov_node *node;
 486
 487        mutex_lock(&node_lock);
 488restart:
 489        list_for_each_entry(node, &all_head, all) {
 490                if (node->info)
 491                        gcov_info_reset(node->info);
 492                else if (list_empty(&node->children)) {
 493                        remove_node(node);
 494                        /* Several nodes may have gone - restart loop. */
 495                        goto restart;
 496                }
 497        }
 498        mutex_unlock(&node_lock);
 499
 500        return len;
 501}
 502
 503/* read() implementation for reset file. Unused. */
 504static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
 505                          loff_t *pos)
 506{
 507        /* Allow read operation so that a recursive copy won't fail. */
 508        return 0;
 509}
 510
 511static const struct file_operations gcov_reset_fops = {
 512        .write  = reset_write,
 513        .read   = reset_read,
 514};
 515
 516/*
 517 * Create a node for a given profiling data set and add it to all lists and
 518 * debugfs. Needs to be called with node_lock held.
 519 */
 520static void add_node(struct gcov_info *info)
 521{
 522        char *filename;
 523        char *curr;
 524        char *next;
 525        struct gcov_node *parent;
 526        struct gcov_node *node;
 527
 528        filename = kstrdup(info->filename, GFP_KERNEL);
 529        if (!filename)
 530                return;
 531        parent = &root_node;
 532        /* Create directory nodes along the path. */
 533        for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
 534                if (curr == next)
 535                        continue;
 536                *next = 0;
 537                if (strcmp(curr, ".") == 0)
 538                        continue;
 539                if (strcmp(curr, "..") == 0) {
 540                        if (!parent->parent)
 541                                goto err_remove;
 542                        parent = parent->parent;
 543                        continue;
 544                }
 545                node = get_child_by_name(parent, curr);
 546                if (!node) {
 547                        node = new_node(parent, NULL, curr);
 548                        if (!node)
 549                                goto err_remove;
 550                }
 551                parent = node;
 552        }
 553        /* Create file node. */
 554        node = new_node(parent, info, curr);
 555        if (!node)
 556                goto err_remove;
 557out:
 558        kfree(filename);
 559        return;
 560
 561err_remove:
 562        remove_node(parent);
 563        goto out;
 564}
 565
 566/*
 567 * The profiling data set associated with this node is being unloaded. Store a
 568 * copy of the profiling data and turn this node into a "ghost".
 569 */
 570static int ghost_node(struct gcov_node *node)
 571{
 572        node->ghost = gcov_info_dup(node->info);
 573        if (!node->ghost) {
 574                pr_warning("could not save data for '%s' (out of memory)\n",
 575                           node->info->filename);
 576                return -ENOMEM;
 577        }
 578        node->info = NULL;
 579
 580        return 0;
 581}
 582
 583/*
 584 * Profiling data for this node has been loaded again. Add profiling data
 585 * from previous instantiation and turn this node into a regular node.
 586 */
 587static void revive_node(struct gcov_node *node, struct gcov_info *info)
 588{
 589        if (gcov_info_is_compatible(node->ghost, info))
 590                gcov_info_add(info, node->ghost);
 591        else {
 592                pr_warning("discarding saved data for '%s' (version changed)\n",
 593                           info->filename);
 594        }
 595        gcov_info_free(node->ghost);
 596        node->ghost = NULL;
 597        node->info = info;
 598}
 599
 600/*
 601 * Callback to create/remove profiling files when code compiled with
 602 * -fprofile-arcs is loaded/unloaded.
 603 */
 604void gcov_event(enum gcov_action action, struct gcov_info *info)
 605{
 606        struct gcov_node *node;
 607
 608        mutex_lock(&node_lock);
 609        node = get_node_by_name(info->filename);
 610        switch (action) {
 611        case GCOV_ADD:
 612                /* Add new node or revive ghost. */
 613                if (!node) {
 614                        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;
 624        case GCOV_REMOVE:
 625                /* Remove node or turn into ghost. */
 626                if (!node) {
 627                        pr_warning("could not remove '%s' (not found)\n",
 628                                   info->filename);
 629                        break;
 630                }
 631                if (gcov_persist) {
 632                        if (!ghost_node(node))
 633                                break;
 634                }
 635                remove_node(node);
 636                break;
 637        }
 638        mutex_unlock(&node_lock);
 639}
 640
 641/* Create debugfs entries. */
 642static __init int gcov_fs_init(void)
 643{
 644        int rc = -EIO;
 645
 646        init_node(&root_node, NULL, NULL, NULL);
 647        /*
 648         * /sys/kernel/debug/gcov will be parent for the reset control file
 649         * and all profiling files.
 650         */
 651        root_node.dentry = debugfs_create_dir("gcov", NULL);
 652        if (!root_node.dentry)
 653                goto err_remove;
 654        /*
 655         * Create reset file which resets all profiling counts when written
 656         * to.
 657         */
 658        reset_dentry = debugfs_create_file("reset", 0600, root_node.dentry,
 659                                           NULL, &gcov_reset_fops);
 660        if (!reset_dentry)
 661                goto err_remove;
 662        /* Replay previous events to get our fs hierarchy up-to-date. */
 663        gcov_enable_events();
 664        return 0;
 665
 666err_remove:
 667        pr_err("init failed\n");
 668        if (root_node.dentry)
 669                debugfs_remove(root_node.dentry);
 670
 671        return rc;
 672}
 673device_initcall(gcov_fs_init);
 674