| 1 | |
| 2 | |
| 3 | * This code exports profiling data as debugfs files to userspace.
|
| 4 | *
|
| 5 | * Copyright IBM Corp. 2009
|
| 6 | * Author(s): Peter Oberparleiter <[email protected]>
|
| 7 | *
|
| 8 | * Uses gcc-internal data definitions.
|
| 9 | * Based on the gcov-kernel patch by:
|
| 10 | * Hubertus Franke <[email protected]>
|
| 11 | * Nigel Hinds <[email protected]>
|
| 12 | * Rajan Ravindran <[email protected]>
|
| 13 | * Peter Oberparleiter <[email protected]>
|
| 14 | * Paul Larson
|
| 15 | * Yi CDL Yang
|
| 16 |
|
| 17 |
|
| 18 | #define pr_fmt(fmt) "gcov: " fmt
|
| 19 |
|
| 20 | #include <linux/init.h>
|
| 21 | #include <linux/module.h>
|
| 22 | #include <linux/debugfs.h>
|
| 23 | #include <linux/fs.h>
|
| 24 | #include <linux/list.h>
|
| 25 | #include <linux/string.h>
|
| 26 | #include <linux/slab.h>
|
| 27 | #include <linux/mutex.h>
|
| 28 | #include <linux/seq_file.h>
|
| 29 | #include <linux/mm.h>
|
| 30 | #include "gcov.h"
|
| 31 |
|
| 32 | |
| 33 | * struct gcov_node - represents a debugfs entry
|
| 34 | * @list: list head for child node list
|
| 35 | * @children: child nodes
|
| 36 | * @all: list head for list of all nodes
|
| 37 | * @parent: parent node
|
| 38 | * @loaded_info: array of pointers to profiling data sets for loaded object
|
| 39 | * files.
|
| 40 | * @num_loaded: number of profiling data sets for loaded object files.
|
| 41 | * @unloaded_info: accumulated copy of profiling data sets for unloaded
|
| 42 | * object files. Used only when gcov_persist=1.
|
| 43 | * @dentry: main debugfs entry, either a directory or data file
|
| 44 | * @links: associated symbolic links
|
| 45 | * @name: data file basename
|
| 46 | *
|
| 47 | * struct gcov_node represents an entity within the gcov/ subdirectory
|
| 48 | * of debugfs. There are directory and data file nodes. The latter represent
|
| 49 | * the actual synthesized data file plus any associated symbolic links which
|
| 50 | * are needed by the gcov tool to work correctly.
|
| 51 |
|
| 52 | struct gcov_node {
|
| 53 | struct list_head list;
|
| 54 | struct list_head children;
|
| 55 | struct list_head all;
|
| 56 | struct gcov_node *parent;
|
| 57 | struct gcov_info **loaded_info;
|
| 58 | struct gcov_info *unloaded_info;
|
| 59 | struct dentry *dentry;
|
| 60 | struct dentry **links;
|
| 61 | int num_loaded;
|
| 62 | char name[];
|
| 63 | };
|
| 64 |
|
| 65 | static const char objtree[] = OBJTREE;
|
| 66 | static const char srctree[] = SRCTREE;
|
| 67 | static struct gcov_node root_node;
|
| 68 | static LIST_HEAD(all_head);
|
| 69 | static DEFINE_MUTEX(node_lock);
|
| 70 |
|
| 71 |
|
| 72 | static int gcov_persist = 1;
|
| 73 |
|
| 74 | static int __init gcov_persist_setup(char *str)
|
| 75 | {
|
| 76 | unsigned long val;
|
| 77 |
|
| 78 | if (kstrtoul(str, 0, &val)) {
|
| 79 | pr_warn("invalid gcov_persist parameter '%s'\n", str);
|
| 80 | return 0;
|
| 81 | }
|
| 82 | gcov_persist = val;
|
| 83 | pr_info("setting gcov_persist to %d\n", gcov_persist);
|
| 84 |
|
| 85 | return 1;
|
| 86 | }
|
| 87 | __setup("gcov_persist=", gcov_persist_setup);
|
| 88 |
|
| 89 | #define ITER_STRIDE PAGE_SIZE
|
| 90 |
|
| 91 | |
| 92 | * struct gcov_iterator - specifies current file position in logical records
|
| 93 | * @info: associated profiling data
|
| 94 | * @buffer: buffer containing file data
|
| 95 | * @size: size of buffer
|
| 96 | * @pos: current position in file
|
| 97 |
|
| 98 | struct gcov_iterator {
|
| 99 | struct gcov_info *info;
|
| 100 | size_t size;
|
| 101 | loff_t pos;
|
| 102 | char buffer[] __counted_by(size);
|
| 103 | };
|
| 104 |
|
| 105 | |
| 106 | * gcov_iter_new - allocate and initialize profiling data iterator
|
| 107 | * @info: profiling data set to be iterated
|
| 108 | *
|
| 109 | * Return file iterator on success, %NULL otherwise.
|
| 110 |
|
| 111 | static struct gcov_iterator *gcov_iter_new(struct gcov_info *info)
|
| 112 | {
|
| 113 | struct gcov_iterator *iter;
|
| 114 | size_t size;
|
| 115 |
|
| 116 |
|
| 117 | size = convert_to_gcda(NULL, info);
|
| 118 |
|
| 119 | iter = kvmalloc_flex(*iter, buffer, size);
|
| 120 | if (!iter)
|
| 121 | return NULL;
|
| 122 |
|
| 123 | iter->info = info;
|
| 124 | iter->size = size;
|
| 125 | convert_to_gcda(iter->buffer, info);
|
| 126 |
|
| 127 | return iter;
|
| 128 | }
|
| 129 |
|
| 130 |
|
| 131 | |
| 132 | * gcov_iter_free - free iterator data
|
| 133 | * @iter: file iterator
|
| 134 |
|
| 135 | static void gcov_iter_free(struct gcov_iterator *iter)
|
| 136 | {
|
| 137 | kvfree(iter);
|
| 138 | }
|
| 139 |
|
| 140 | |
| 141 | * gcov_iter_get_info - return profiling data set for given file iterator
|
| 142 | * @iter: file iterator
|
| 143 |
|
| 144 | static struct gcov_info *gcov_iter_get_info(struct gcov_iterator *iter)
|
| 145 | {
|
| 146 | return iter->info;
|
| 147 | }
|
| 148 |
|
| 149 | |
| 150 | * gcov_iter_start - reset file iterator to starting position
|
| 151 | * @iter: file iterator
|
| 152 |
|
| 153 | static void gcov_iter_start(struct gcov_iterator *iter)
|
| 154 | {
|
| 155 | iter->pos = 0;
|
| 156 | }
|
| 157 |
|
| 158 | |
| 159 | * gcov_iter_next - advance file iterator to next logical record
|
| 160 | * @iter: file iterator
|
| 161 | *
|
| 162 | * Return zero if new position is valid, non-zero if iterator has reached end.
|
| 163 |
|
| 164 | static int gcov_iter_next(struct gcov_iterator *iter)
|
| 165 | {
|
| 166 | if (iter->pos < iter->size)
|
| 167 | iter->pos += ITER_STRIDE;
|
| 168 |
|
| 169 | if (iter->pos >= iter->size)
|
| 170 | return -EINVAL;
|
| 171 |
|
| 172 | return 0;
|
| 173 | }
|
| 174 |
|
| 175 | |
| 176 | * gcov_iter_write - write data for current pos to seq_file
|
| 177 | * @iter: file iterator
|
| 178 | * @seq: seq_file handle
|
| 179 | *
|
| 180 | * Return zero on success, non-zero otherwise.
|
| 181 |
|
| 182 | static int gcov_iter_write(struct gcov_iterator *iter, struct seq_file *seq)
|
| 183 | {
|
| 184 | size_t len;
|
| 185 |
|
| 186 | if (iter->pos >= iter->size)
|
| 187 | return -EINVAL;
|
| 188 |
|
| 189 | len = ITER_STRIDE;
|
| 190 | if (iter->pos + len > iter->size)
|
| 191 | len = iter->size - iter->pos;
|
| 192 |
|
| 193 | seq_write(seq, iter->buffer + iter->pos, len);
|
| 194 |
|
| 195 | return 0;
|
| 196 | }
|
| 197 |
|
| 198 | |
| 199 | * seq_file.start() implementation for gcov data files. Note that the
|
| 200 | * gcov_iterator interface is designed to be more restrictive than seq_file
|
| 201 | * (no start from arbitrary position, etc.), to simplify the iterator
|
| 202 | * implementation.
|
| 203 |
|
| 204 | static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
|
| 205 | {
|
| 206 | loff_t i;
|
| 207 |
|
| 208 | gcov_iter_start(seq->private);
|
| 209 | for (i = 0; i < *pos; i++) {
|
| 210 | if (gcov_iter_next(seq->private))
|
| 211 | return NULL;
|
| 212 | }
|
| 213 | return seq->private;
|
| 214 | }
|
| 215 |
|
| 216 |
|
| 217 | static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
|
| 218 | {
|
| 219 | struct gcov_iterator *iter = data;
|
| 220 |
|
| 221 | (*pos)++;
|
| 222 | if (gcov_iter_next(iter))
|
| 223 | return NULL;
|
| 224 |
|
| 225 | return iter;
|
| 226 | }
|
| 227 |
|
| 228 |
|
| 229 | static int gcov_seq_show(struct seq_file *seq, void *data)
|
| 230 | {
|
| 231 | struct gcov_iterator *iter = data;
|
| 232 |
|
| 233 | if (gcov_iter_write(iter, seq))
|
| 234 | return -EINVAL;
|
| 235 | return 0;
|
| 236 | }
|
| 237 |
|
| 238 | static void gcov_seq_stop(struct seq_file *seq, void *data)
|
| 239 | {
|
| 240 |
|
| 241 | }
|
| 242 |
|
| 243 | static const struct seq_operations gcov_seq_ops = {
|
| 244 | .start = gcov_seq_start,
|
| 245 | .next = gcov_seq_next,
|
| 246 | .show = gcov_seq_show,
|
| 247 | .stop = gcov_seq_stop,
|
| 248 | };
|
| 249 |
|
| 250 | |
| 251 | * Return a profiling data set associated with the given node. This is
|
| 252 | * either a data set for a loaded object file or a data set copy in case
|
| 253 | * all associated object files have been unloaded.
|
| 254 |
|
| 255 | static struct gcov_info *get_node_info(struct gcov_node *node)
|
| 256 | {
|
| 257 | if (node->num_loaded > 0)
|
| 258 | return node->loaded_info[0];
|
| 259 |
|
| 260 | return node->unloaded_info;
|
| 261 | }
|
| 262 |
|
| 263 | |
| 264 | * Return a newly allocated profiling data set which contains the sum of
|
| 265 | * all profiling data associated with the given node.
|
| 266 |
|
| 267 | static struct gcov_info *get_accumulated_info(struct gcov_node *node)
|
| 268 | {
|
| 269 | struct gcov_info *info;
|
| 270 | int i = 0;
|
| 271 |
|
| 272 | if (node->unloaded_info)
|
| 273 | info = gcov_info_dup(node->unloaded_info);
|
| 274 | else
|
| 275 | info = gcov_info_dup(node->loaded_info[i++]);
|
| 276 | if (!info)
|
| 277 | return NULL;
|
| 278 | for (; i < node->num_loaded; i++)
|
| 279 | gcov_info_add(info, node->loaded_info[i]);
|
| 280 |
|
| 281 | return info;
|
| 282 | }
|
| 283 |
|
| 284 | |
| 285 | * open() implementation for gcov data files. Create a copy of the profiling
|
| 286 | * data set and initialize the iterator and seq_file interface.
|
| 287 |
|
| 288 | static int gcov_seq_open(struct inode *inode, struct file *file)
|
| 289 | {
|
| 290 | struct gcov_node *node = inode->i_private;
|
| 291 | struct gcov_iterator *iter;
|
| 292 | struct seq_file *seq;
|
| 293 | struct gcov_info *info;
|
| 294 | int rc = -ENOMEM;
|
| 295 |
|
| 296 | mutex_lock(&node_lock);
|
| 297 | |
| 298 | * Read from a profiling data copy to minimize reference tracking
|
| 299 | * complexity and concurrent access and to keep accumulating multiple
|
| 300 | * profiling data sets associated with one node simple.
|
| 301 |
|
| 302 | info = get_accumulated_info(node);
|
| 303 | if (!info)
|
| 304 | goto out_unlock;
|
| 305 | iter = gcov_iter_new(info);
|
| 306 | if (!iter)
|
| 307 | goto err_free_info;
|
| 308 | rc = seq_open(file, &gcov_seq_ops);
|
| 309 | if (rc)
|
| 310 | goto err_free_iter_info;
|
| 311 | seq = file->private_data;
|
| 312 | seq->private = iter;
|
| 313 | out_unlock:
|
| 314 | mutex_unlock(&node_lock);
|
| 315 | return rc;
|
| 316 |
|
| 317 | err_free_iter_info:
|
| 318 | gcov_iter_free(iter);
|
| 319 | err_free_info:
|
| 320 | gcov_info_free(info);
|
| 321 | goto out_unlock;
|
| 322 | }
|
| 323 |
|
| 324 | |
| 325 | * release() implementation for gcov data files. Release resources allocated
|
| 326 | * by open().
|
| 327 |
|
| 328 | static int gcov_seq_release(struct inode *inode, struct file *file)
|
| 329 | {
|
| 330 | struct gcov_iterator *iter;
|
| 331 | struct gcov_info *info;
|
| 332 | struct seq_file *seq;
|
| 333 |
|
| 334 | seq = file->private_data;
|
| 335 | iter = seq->private;
|
| 336 | info = gcov_iter_get_info(iter);
|
| 337 | gcov_iter_free(iter);
|
| 338 | gcov_info_free(info);
|
| 339 | seq_release(inode, file);
|
| 340 |
|
| 341 | return 0;
|
| 342 | }
|
| 343 |
|
| 344 | |
| 345 | * Find a node by the associated data file name. Needs to be called with
|
| 346 | * node_lock held.
|
| 347 |
|
| 348 | static struct gcov_node *get_node_by_name(const char *name)
|
| 349 | {
|
| 350 | struct gcov_node *node;
|
| 351 | struct gcov_info *info;
|
| 352 |
|
| 353 | list_for_each_entry(node, &all_head, all) {
|
| 354 | info = get_node_info(node);
|
| 355 | if (info && (strcmp(gcov_info_filename(info), name) == 0))
|
| 356 | return node;
|
| 357 | }
|
| 358 |
|
| 359 | return NULL;
|
| 360 | }
|
| 361 |
|
| 362 | |
| 363 | * Reset all profiling data associated with the specified node.
|
| 364 |
|
| 365 | static void reset_node(struct gcov_node *node)
|
| 366 | {
|
| 367 | int i;
|
| 368 |
|
| 369 | if (node->unloaded_info)
|
| 370 | gcov_info_reset(node->unloaded_info);
|
| 371 | for (i = 0; i < node->num_loaded; i++)
|
| 372 | gcov_info_reset(node->loaded_info[i]);
|
| 373 | }
|
| 374 |
|
| 375 | static void remove_node(struct gcov_node *node);
|
| 376 |
|
| 377 | |
| 378 | * write() implementation for gcov data files. Reset profiling data for the
|
| 379 | * corresponding file. If all associated object files have been unloaded,
|
| 380 | * remove the debug fs node as well.
|
| 381 |
|
| 382 | static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
|
| 383 | size_t len, loff_t *pos)
|
| 384 | {
|
| 385 | struct seq_file *seq;
|
| 386 | struct gcov_info *info;
|
| 387 | struct gcov_node *node;
|
| 388 |
|
| 389 | seq = file->private_data;
|
| 390 | info = gcov_iter_get_info(seq->private);
|
| 391 | mutex_lock(&node_lock);
|
| 392 | node = get_node_by_name(gcov_info_filename(info));
|
| 393 | if (node) {
|
| 394 |
|
| 395 | if (node->num_loaded == 0)
|
| 396 | remove_node(node);
|
| 397 | else
|
| 398 | reset_node(node);
|
| 399 | }
|
| 400 |
|
| 401 | gcov_info_reset(info);
|
| 402 | mutex_unlock(&node_lock);
|
| 403 |
|
| 404 | return len;
|
| 405 | }
|
| 406 |
|
| 407 | |
| 408 | * Given a string <path> representing a file path of format:
|
| 409 | * path/to/file.gcda
|
| 410 | * construct and return a new string:
|
| 411 | * <dir/>path/to/file.<ext>
|
| 412 |
|
| 413 | static char *link_target(const char *dir, const char *path, const char *ext)
|
| 414 | {
|
| 415 | char *target;
|
| 416 | char *old_ext;
|
| 417 | char *copy;
|
| 418 |
|
| 419 | copy = kstrdup(path, GFP_KERNEL);
|
| 420 | if (!copy)
|
| 421 | return NULL;
|
| 422 | old_ext = strrchr(copy, '.');
|
| 423 | if (old_ext)
|
| 424 | *old_ext = '\0';
|
| 425 | if (dir)
|
| 426 | target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
|
| 427 | else
|
| 428 | target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
|
| 429 | kfree(copy);
|
| 430 |
|
| 431 | return target;
|
| 432 | }
|
| 433 |
|
| 434 | |
| 435 | * Construct a string representing the symbolic link target for the given
|
| 436 | * gcov data file name and link type. Depending on the link type and the
|
| 437 | * location of the data file, the link target can either point to a
|
| 438 | * subdirectory of srctree, objtree or in an external location.
|
| 439 |
|
| 440 | static char *get_link_target(const char *filename, const struct gcov_link *ext)
|
| 441 | {
|
| 442 | const char *rel;
|
| 443 | char *result;
|
| 444 |
|
| 445 | if (strncmp(filename, objtree, strlen(objtree)) == 0) {
|
| 446 | rel = filename + strlen(objtree) + 1;
|
| 447 | if (ext->dir == SRC_TREE)
|
| 448 | result = link_target(srctree, rel, ext->ext);
|
| 449 | else
|
| 450 | result = link_target(objtree, rel, ext->ext);
|
| 451 | } else {
|
| 452 |
|
| 453 | result = link_target(NULL, filename, ext->ext);
|
| 454 | }
|
| 455 |
|
| 456 | return result;
|
| 457 | }
|
| 458 |
|
| 459 | #define SKEW_PREFIX ".tmp_"
|
| 460 |
|
| 461 | |
| 462 | * For a filename .tmp_filename.ext return filename.ext. Needed to compensate
|
| 463 | * for filename skewing caused by the mod-versioning mechanism.
|
| 464 |
|
| 465 | static const char *deskew(const char *basename)
|
| 466 | {
|
| 467 | if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
|
| 468 | return basename + sizeof(SKEW_PREFIX) - 1;
|
| 469 | return basename;
|
| 470 | }
|
| 471 |
|
| 472 | |
| 473 | * Create links to additional files (usually .c and .gcno files) which the
|
| 474 | * gcov tool expects to find in the same directory as the gcov data file.
|
| 475 |
|
| 476 | static void add_links(struct gcov_node *node, struct dentry *parent)
|
| 477 | {
|
| 478 | const char *basename;
|
| 479 | char *target;
|
| 480 | int num;
|
| 481 | int i;
|
| 482 |
|
| 483 | for (num = 0; gcov_link[num].ext; num++)
|
| 484 | ;
|
| 485 | node->links = kzalloc_objs(struct dentry *, num);
|
| 486 | if (!node->links)
|
| 487 | return;
|
| 488 | for (i = 0; i < num; i++) {
|
| 489 | target = get_link_target(
|
| 490 | gcov_info_filename(get_node_info(node)),
|
| 491 | &gcov_link[i]);
|
| 492 | if (!target)
|
| 493 | goto out_err;
|
| 494 | basename = kbasename(target);
|
| 495 | if (basename == target)
|
| 496 | goto out_err;
|
| 497 | node->links[i] = debugfs_create_symlink(deskew(basename),
|
| 498 | parent, target);
|
| 499 | kfree(target);
|
| 500 | }
|
| 501 |
|
| 502 | return;
|
| 503 | out_err:
|
| 504 | kfree(target);
|
| 505 | while (i-- > 0)
|
| 506 | debugfs_remove(node->links[i]);
|
| 507 | kfree(node->links);
|
| 508 | node->links = NULL;
|
| 509 | }
|
| 510 |
|
| 511 | static const struct file_operations gcov_data_fops = {
|
| 512 | .open = gcov_seq_open,
|
| 513 | .release = gcov_seq_release,
|
| 514 | .read = seq_read,
|
| 515 | .llseek = seq_lseek,
|
| 516 | .write = gcov_seq_write,
|
| 517 | };
|
| 518 |
|
| 519 |
|
| 520 | static void init_node(struct gcov_node *node, struct gcov_info *info,
|
| 521 | const char *name, struct gcov_node *parent)
|
| 522 | {
|
| 523 | INIT_LIST_HEAD(&node->list);
|
| 524 | INIT_LIST_HEAD(&node->children);
|
| 525 | INIT_LIST_HEAD(&node->all);
|
| 526 | if (node->loaded_info) {
|
| 527 | node->loaded_info[0] = info;
|
| 528 | node->num_loaded = 1;
|
| 529 | }
|
| 530 | node->parent = parent;
|
| 531 | if (name)
|
| 532 | strcpy(node->name, name);
|
| 533 | }
|
| 534 |
|
| 535 | |
| 536 | * Create a new node and associated debugfs entry. Needs to be called with
|
| 537 | * node_lock held.
|
| 538 |
|
| 539 | static struct gcov_node *new_node(struct gcov_node *parent,
|
| 540 | struct gcov_info *info, const char *name)
|
| 541 | {
|
| 542 | struct gcov_node *node;
|
| 543 |
|
| 544 | node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
|
| 545 | if (!node)
|
| 546 | goto err_nomem;
|
| 547 | if (info) {
|
| 548 | node->loaded_info = kzalloc_objs(struct gcov_info *, 1);
|
| 549 | if (!node->loaded_info)
|
| 550 | goto err_nomem;
|
| 551 | }
|
| 552 | init_node(node, info, name, parent);
|
| 553 |
|
| 554 | if (info) {
|
| 555 | node->dentry = debugfs_create_file(deskew(node->name), 0600,
|
| 556 | parent->dentry, node, &gcov_data_fops);
|
| 557 | } else
|
| 558 | node->dentry = debugfs_create_dir(node->name, parent->dentry);
|
| 559 | if (info)
|
| 560 | add_links(node, parent->dentry);
|
| 561 | list_add(&node->list, &parent->children);
|
| 562 | list_add(&node->all, &all_head);
|
| 563 |
|
| 564 | return node;
|
| 565 |
|
| 566 | err_nomem:
|
| 567 | kfree(node);
|
| 568 | pr_warn("out of memory\n");
|
| 569 | return NULL;
|
| 570 | }
|
| 571 |
|
| 572 |
|
| 573 | static void remove_links(struct gcov_node *node)
|
| 574 | {
|
| 575 | int i;
|
| 576 |
|
| 577 | if (!node->links)
|
| 578 | return;
|
| 579 | for (i = 0; gcov_link[i].ext; i++)
|
| 580 | debugfs_remove(node->links[i]);
|
| 581 | kfree(node->links);
|
| 582 | node->links = NULL;
|
| 583 | }
|
| 584 |
|
| 585 | |
| 586 | * Remove node from all lists and debugfs and release associated resources.
|
| 587 | * Needs to be called with node_lock held.
|
| 588 |
|
| 589 | static void release_node(struct gcov_node *node)
|
| 590 | {
|
| 591 | list_del(&node->list);
|
| 592 | list_del(&node->all);
|
| 593 | debugfs_remove(node->dentry);
|
| 594 | remove_links(node);
|
| 595 | kfree(node->loaded_info);
|
| 596 | if (node->unloaded_info)
|
| 597 | gcov_info_free(node->unloaded_info);
|
| 598 | kfree(node);
|
| 599 | }
|
| 600 |
|
| 601 |
|
| 602 | static void remove_node(struct gcov_node *node)
|
| 603 | {
|
| 604 | struct gcov_node *parent;
|
| 605 |
|
| 606 | while ((node != &root_node) && list_empty(&node->children)) {
|
| 607 | parent = node->parent;
|
| 608 | release_node(node);
|
| 609 | node = parent;
|
| 610 | }
|
| 611 | }
|
| 612 |
|
| 613 | |
| 614 | * Find child node with given basename. Needs to be called with node_lock
|
| 615 | * held.
|
| 616 |
|
| 617 | static struct gcov_node *get_child_by_name(struct gcov_node *parent,
|
| 618 | const char *name)
|
| 619 | {
|
| 620 | struct gcov_node *node;
|
| 621 |
|
| 622 | list_for_each_entry(node, &parent->children, list) {
|
| 623 | if (strcmp(node->name, name) == 0)
|
| 624 | return node;
|
| 625 | }
|
| 626 |
|
| 627 | return NULL;
|
| 628 | }
|
| 629 |
|
| 630 | |
| 631 | * write() implementation for reset file. Reset all profiling data to zero
|
| 632 | * and remove nodes for which all associated object files are unloaded.
|
| 633 |
|
| 634 | static ssize_t reset_write(struct file *file, const char __user *addr,
|
| 635 | size_t len, loff_t *pos)
|
| 636 | {
|
| 637 | struct gcov_node *node;
|
| 638 |
|
| 639 | mutex_lock(&node_lock);
|
| 640 | restart:
|
| 641 | list_for_each_entry(node, &all_head, all) {
|
| 642 | if (node->num_loaded > 0)
|
| 643 | reset_node(node);
|
| 644 | else if (list_empty(&node->children)) {
|
| 645 | remove_node(node);
|
| 646 |
|
| 647 | goto restart;
|
| 648 | }
|
| 649 | }
|
| 650 | mutex_unlock(&node_lock);
|
| 651 |
|
| 652 | return len;
|
| 653 | }
|
| 654 |
|
| 655 |
|
| 656 | static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
|
| 657 | loff_t *pos)
|
| 658 | {
|
| 659 |
|
| 660 | return 0;
|
| 661 | }
|
| 662 |
|
| 663 | static const struct file_operations gcov_reset_fops = {
|
| 664 | .write = reset_write,
|
| 665 | .read = reset_read,
|
| 666 | .llseek = noop_llseek,
|
| 667 | };
|
| 668 |
|
| 669 | |
| 670 | * Create a node for a given profiling data set and add it to all lists and
|
| 671 | * debugfs. Needs to be called with node_lock held.
|
| 672 |
|
| 673 | static void add_node(struct gcov_info *info)
|
| 674 | {
|
| 675 | char *filename;
|
| 676 | char *curr;
|
| 677 | char *next;
|
| 678 | struct gcov_node *parent;
|
| 679 | struct gcov_node *node;
|
| 680 |
|
| 681 | filename = kstrdup(gcov_info_filename(info), GFP_KERNEL);
|
| 682 | if (!filename)
|
| 683 | return;
|
| 684 | parent = &root_node;
|
| 685 |
|
| 686 | for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
|
| 687 | if (curr == next)
|
| 688 | continue;
|
| 689 | *next = 0;
|
| 690 | if (strcmp(curr, ".") == 0)
|
| 691 | continue;
|
| 692 | if (strcmp(curr, "..") == 0) {
|
| 693 | if (!parent->parent)
|
| 694 | goto err_remove;
|
| 695 | parent = parent->parent;
|
| 696 | continue;
|
| 697 | }
|
| 698 | node = get_child_by_name(parent, curr);
|
| 699 | if (!node) {
|
| 700 | node = new_node(parent, NULL, curr);
|
| 701 | if (!node)
|
| 702 | goto err_remove;
|
| 703 | }
|
| 704 | parent = node;
|
| 705 | }
|
| 706 |
|
| 707 | node = new_node(parent, info, curr);
|
| 708 | if (!node)
|
| 709 | goto err_remove;
|
| 710 | out:
|
| 711 | kfree(filename);
|
| 712 | return;
|
| 713 |
|
| 714 | err_remove:
|
| 715 | remove_node(parent);
|
| 716 | goto out;
|
| 717 | }
|
| 718 |
|
| 719 | |
| 720 | * Associate a profiling data set with an existing node. Needs to be called
|
| 721 | * with node_lock held.
|
| 722 |
|
| 723 | static void add_info(struct gcov_node *node, struct gcov_info *info)
|
| 724 | {
|
| 725 | struct gcov_info **loaded_info;
|
| 726 | int num = node->num_loaded;
|
| 727 |
|
| 728 | |
| 729 | * Prepare new array. This is done first to simplify cleanup in
|
| 730 | * case the new data set is incompatible, the node only contains
|
| 731 | * unloaded data sets and there's not enough memory for the array.
|
| 732 |
|
| 733 | loaded_info = kzalloc_objs(struct gcov_info *, num + 1);
|
| 734 | if (!loaded_info) {
|
| 735 | pr_warn("could not add '%s' (out of memory)\n",
|
| 736 | gcov_info_filename(info));
|
| 737 | return;
|
| 738 | }
|
| 739 | memcpy(loaded_info, node->loaded_info,
|
| 740 | num * sizeof(struct gcov_info *));
|
| 741 | loaded_info[num] = info;
|
| 742 |
|
| 743 | if (num == 0) {
|
| 744 | |
| 745 | * A module was unloaded, modified and reloaded. The new
|
| 746 | * data set replaces the copy of the last one.
|
| 747 |
|
| 748 | if (!gcov_info_is_compatible(node->unloaded_info, info)) {
|
| 749 | pr_warn("discarding saved data for %s "
|
| 750 | "(incompatible version)\n",
|
| 751 | gcov_info_filename(info));
|
| 752 | gcov_info_free(node->unloaded_info);
|
| 753 | node->unloaded_info = NULL;
|
| 754 | }
|
| 755 | } else {
|
| 756 | |
| 757 | * Two different versions of the same object file are loaded.
|
| 758 | * The initial one takes precedence.
|
| 759 |
|
| 760 | if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
|
| 761 | pr_warn("could not add '%s' (incompatible "
|
| 762 | "version)\n", gcov_info_filename(info));
|
| 763 | kfree(loaded_info);
|
| 764 | return;
|
| 765 | }
|
| 766 | }
|
| 767 |
|
| 768 | kfree(node->loaded_info);
|
| 769 | node->loaded_info = loaded_info;
|
| 770 | node->num_loaded = num + 1;
|
| 771 | }
|
| 772 |
|
| 773 | |
| 774 | * Return the index of a profiling data set associated with a node.
|
| 775 |
|
| 776 | static int get_info_index(struct gcov_node *node, struct gcov_info *info)
|
| 777 | {
|
| 778 | int i;
|
| 779 |
|
| 780 | for (i = 0; i < node->num_loaded; i++) {
|
| 781 | if (node->loaded_info[i] == info)
|
| 782 | return i;
|
| 783 | }
|
| 784 | return -ENOENT;
|
| 785 | }
|
| 786 |
|
| 787 | |
| 788 | * Save the data of a profiling data set which is being unloaded.
|
| 789 |
|
| 790 | static void save_info(struct gcov_node *node, struct gcov_info *info)
|
| 791 | {
|
| 792 | if (node->unloaded_info)
|
| 793 | gcov_info_add(node->unloaded_info, info);
|
| 794 | else {
|
| 795 | node->unloaded_info = gcov_info_dup(info);
|
| 796 | if (!node->unloaded_info) {
|
| 797 | pr_warn("could not save data for '%s' "
|
| 798 | "(out of memory)\n",
|
| 799 | gcov_info_filename(info));
|
| 800 | }
|
| 801 | }
|
| 802 | }
|
| 803 |
|
| 804 | |
| 805 | * Disassociate a profiling data set from a node. Needs to be called with
|
| 806 | * node_lock held.
|
| 807 |
|
| 808 | static void remove_info(struct gcov_node *node, struct gcov_info *info)
|
| 809 | {
|
| 810 | int i;
|
| 811 |
|
| 812 | i = get_info_index(node, info);
|
| 813 | if (i < 0) {
|
| 814 | pr_warn("could not remove '%s' (not found)\n",
|
| 815 | gcov_info_filename(info));
|
| 816 | return;
|
| 817 | }
|
| 818 | if (gcov_persist)
|
| 819 | save_info(node, info);
|
| 820 |
|
| 821 | node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
|
| 822 | node->num_loaded--;
|
| 823 | if (node->num_loaded > 0)
|
| 824 | return;
|
| 825 |
|
| 826 | kfree(node->loaded_info);
|
| 827 | node->loaded_info = NULL;
|
| 828 | node->num_loaded = 0;
|
| 829 | if (!node->unloaded_info)
|
| 830 | remove_node(node);
|
| 831 | }
|
| 832 |
|
| 833 | |
| 834 | * Callback to create/remove profiling files when code compiled with
|
| 835 | * -fprofile-arcs is loaded/unloaded.
|
| 836 |
|
| 837 | void gcov_event(enum gcov_action action, struct gcov_info *info)
|
| 838 | {
|
| 839 | struct gcov_node *node;
|
| 840 |
|
| 841 | mutex_lock(&node_lock);
|
| 842 | node = get_node_by_name(gcov_info_filename(info));
|
| 843 | switch (action) {
|
| 844 | case GCOV_ADD:
|
| 845 | if (node)
|
| 846 | add_info(node, info);
|
| 847 | else
|
| 848 | add_node(info);
|
| 849 | break;
|
| 850 | case GCOV_REMOVE:
|
| 851 | if (node)
|
| 852 | remove_info(node, info);
|
| 853 | else {
|
| 854 | pr_warn("could not remove '%s' (not found)\n",
|
| 855 | gcov_info_filename(info));
|
| 856 | }
|
| 857 | break;
|
| 858 | }
|
| 859 | mutex_unlock(&node_lock);
|
| 860 | }
|
| 861 |
|
| 862 |
|
| 863 | static __init int gcov_fs_init(void)
|
| 864 | {
|
| 865 | init_node(&root_node, NULL, NULL, NULL);
|
| 866 | |
| 867 | * /sys/kernel/debug/gcov will be parent for the reset control file
|
| 868 | * and all profiling files.
|
| 869 |
|
| 870 | root_node.dentry = debugfs_create_dir("gcov", NULL);
|
| 871 | |
| 872 | * Create reset file which resets all profiling counts when written
|
| 873 | * to.
|
| 874 |
|
| 875 | debugfs_create_file("reset", 0600, root_node.dentry, NULL,
|
| 876 | &gcov_reset_fops);
|
| 877 |
|
| 878 | gcov_enable_events();
|
| 879 | return 0;
|
| 880 | }
|
| 881 | device_initcall(gcov_fs_init);
|