0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018 #define pr_fmt(fmt) "gcov: " fmt
0019
0020 #include <linux/init.h>
0021 #include <linux/module.h>
0022 #include <linux/debugfs.h>
0023 #include <linux/fs.h>
0024 #include <linux/list.h>
0025 #include <linux/string.h>
0026 #include <linux/slab.h>
0027 #include <linux/mutex.h>
0028 #include <linux/seq_file.h>
0029 #include <linux/mm.h>
0030 #include "gcov.h"
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050
0051
0052 struct gcov_node {
0053 struct list_head list;
0054 struct list_head children;
0055 struct list_head all;
0056 struct gcov_node *parent;
0057 struct gcov_info **loaded_info;
0058 struct gcov_info *unloaded_info;
0059 struct dentry *dentry;
0060 struct dentry **links;
0061 int num_loaded;
0062 char name[];
0063 };
0064
0065 static const char objtree[] = OBJTREE;
0066 static const char srctree[] = SRCTREE;
0067 static struct gcov_node root_node;
0068 static LIST_HEAD(all_head);
0069 static DEFINE_MUTEX(node_lock);
0070
0071
0072 static int gcov_persist = 1;
0073
0074 static int __init gcov_persist_setup(char *str)
0075 {
0076 unsigned long val;
0077
0078 if (kstrtoul(str, 0, &val)) {
0079 pr_warn("invalid gcov_persist parameter '%s'\n", str);
0080 return 0;
0081 }
0082 gcov_persist = val;
0083 pr_info("setting gcov_persist to %d\n", gcov_persist);
0084
0085 return 1;
0086 }
0087 __setup("gcov_persist=", gcov_persist_setup);
0088
0089 #define ITER_STRIDE PAGE_SIZE
0090
0091
0092
0093
0094
0095
0096
0097
0098 struct gcov_iterator {
0099 struct gcov_info *info;
0100 size_t size;
0101 loff_t pos;
0102 char buffer[];
0103 };
0104
0105
0106
0107
0108
0109
0110
0111 static struct gcov_iterator *gcov_iter_new(struct gcov_info *info)
0112 {
0113 struct gcov_iterator *iter;
0114 size_t size;
0115
0116
0117 size = convert_to_gcda(NULL, info);
0118
0119 iter = kvmalloc(struct_size(iter, buffer, size), GFP_KERNEL);
0120 if (!iter)
0121 return NULL;
0122
0123 iter->info = info;
0124 iter->size = size;
0125 convert_to_gcda(iter->buffer, info);
0126
0127 return iter;
0128 }
0129
0130
0131
0132
0133
0134
0135 static void gcov_iter_free(struct gcov_iterator *iter)
0136 {
0137 kvfree(iter);
0138 }
0139
0140
0141
0142
0143
0144 static struct gcov_info *gcov_iter_get_info(struct gcov_iterator *iter)
0145 {
0146 return iter->info;
0147 }
0148
0149
0150
0151
0152
0153 static void gcov_iter_start(struct gcov_iterator *iter)
0154 {
0155 iter->pos = 0;
0156 }
0157
0158
0159
0160
0161
0162
0163
0164 static int gcov_iter_next(struct gcov_iterator *iter)
0165 {
0166 if (iter->pos < iter->size)
0167 iter->pos += ITER_STRIDE;
0168
0169 if (iter->pos >= iter->size)
0170 return -EINVAL;
0171
0172 return 0;
0173 }
0174
0175
0176
0177
0178
0179
0180
0181
0182 static int gcov_iter_write(struct gcov_iterator *iter, struct seq_file *seq)
0183 {
0184 size_t len;
0185
0186 if (iter->pos >= iter->size)
0187 return -EINVAL;
0188
0189 len = ITER_STRIDE;
0190 if (iter->pos + len > iter->size)
0191 len = iter->size - iter->pos;
0192
0193 seq_write(seq, iter->buffer + iter->pos, len);
0194
0195 return 0;
0196 }
0197
0198
0199
0200
0201
0202
0203
0204 static void *gcov_seq_start(struct seq_file *seq, loff_t *pos)
0205 {
0206 loff_t i;
0207
0208 gcov_iter_start(seq->private);
0209 for (i = 0; i < *pos; i++) {
0210 if (gcov_iter_next(seq->private))
0211 return NULL;
0212 }
0213 return seq->private;
0214 }
0215
0216
0217 static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
0218 {
0219 struct gcov_iterator *iter = data;
0220
0221 (*pos)++;
0222 if (gcov_iter_next(iter))
0223 return NULL;
0224
0225 return iter;
0226 }
0227
0228
0229 static int gcov_seq_show(struct seq_file *seq, void *data)
0230 {
0231 struct gcov_iterator *iter = data;
0232
0233 if (gcov_iter_write(iter, seq))
0234 return -EINVAL;
0235 return 0;
0236 }
0237
0238 static void gcov_seq_stop(struct seq_file *seq, void *data)
0239 {
0240
0241 }
0242
0243 static const struct seq_operations gcov_seq_ops = {
0244 .start = gcov_seq_start,
0245 .next = gcov_seq_next,
0246 .show = gcov_seq_show,
0247 .stop = gcov_seq_stop,
0248 };
0249
0250
0251
0252
0253
0254
0255 static struct gcov_info *get_node_info(struct gcov_node *node)
0256 {
0257 if (node->num_loaded > 0)
0258 return node->loaded_info[0];
0259
0260 return node->unloaded_info;
0261 }
0262
0263
0264
0265
0266
0267 static struct gcov_info *get_accumulated_info(struct gcov_node *node)
0268 {
0269 struct gcov_info *info;
0270 int i = 0;
0271
0272 if (node->unloaded_info)
0273 info = gcov_info_dup(node->unloaded_info);
0274 else
0275 info = gcov_info_dup(node->loaded_info[i++]);
0276 if (!info)
0277 return NULL;
0278 for (; i < node->num_loaded; i++)
0279 gcov_info_add(info, node->loaded_info[i]);
0280
0281 return info;
0282 }
0283
0284
0285
0286
0287
0288 static int gcov_seq_open(struct inode *inode, struct file *file)
0289 {
0290 struct gcov_node *node = inode->i_private;
0291 struct gcov_iterator *iter;
0292 struct seq_file *seq;
0293 struct gcov_info *info;
0294 int rc = -ENOMEM;
0295
0296 mutex_lock(&node_lock);
0297
0298
0299
0300
0301
0302 info = get_accumulated_info(node);
0303 if (!info)
0304 goto out_unlock;
0305 iter = gcov_iter_new(info);
0306 if (!iter)
0307 goto err_free_info;
0308 rc = seq_open(file, &gcov_seq_ops);
0309 if (rc)
0310 goto err_free_iter_info;
0311 seq = file->private_data;
0312 seq->private = iter;
0313 out_unlock:
0314 mutex_unlock(&node_lock);
0315 return rc;
0316
0317 err_free_iter_info:
0318 gcov_iter_free(iter);
0319 err_free_info:
0320 gcov_info_free(info);
0321 goto out_unlock;
0322 }
0323
0324
0325
0326
0327
0328 static int gcov_seq_release(struct inode *inode, struct file *file)
0329 {
0330 struct gcov_iterator *iter;
0331 struct gcov_info *info;
0332 struct seq_file *seq;
0333
0334 seq = file->private_data;
0335 iter = seq->private;
0336 info = gcov_iter_get_info(iter);
0337 gcov_iter_free(iter);
0338 gcov_info_free(info);
0339 seq_release(inode, file);
0340
0341 return 0;
0342 }
0343
0344
0345
0346
0347
0348 static struct gcov_node *get_node_by_name(const char *name)
0349 {
0350 struct gcov_node *node;
0351 struct gcov_info *info;
0352
0353 list_for_each_entry(node, &all_head, all) {
0354 info = get_node_info(node);
0355 if (info && (strcmp(gcov_info_filename(info), name) == 0))
0356 return node;
0357 }
0358
0359 return NULL;
0360 }
0361
0362
0363
0364
0365 static void reset_node(struct gcov_node *node)
0366 {
0367 int i;
0368
0369 if (node->unloaded_info)
0370 gcov_info_reset(node->unloaded_info);
0371 for (i = 0; i < node->num_loaded; i++)
0372 gcov_info_reset(node->loaded_info[i]);
0373 }
0374
0375 static void remove_node(struct gcov_node *node);
0376
0377
0378
0379
0380
0381
0382 static ssize_t gcov_seq_write(struct file *file, const char __user *addr,
0383 size_t len, loff_t *pos)
0384 {
0385 struct seq_file *seq;
0386 struct gcov_info *info;
0387 struct gcov_node *node;
0388
0389 seq = file->private_data;
0390 info = gcov_iter_get_info(seq->private);
0391 mutex_lock(&node_lock);
0392 node = get_node_by_name(gcov_info_filename(info));
0393 if (node) {
0394
0395 if (node->num_loaded == 0)
0396 remove_node(node);
0397 else
0398 reset_node(node);
0399 }
0400
0401 gcov_info_reset(info);
0402 mutex_unlock(&node_lock);
0403
0404 return len;
0405 }
0406
0407
0408
0409
0410
0411
0412
0413 static char *link_target(const char *dir, const char *path, const char *ext)
0414 {
0415 char *target;
0416 char *old_ext;
0417 char *copy;
0418
0419 copy = kstrdup(path, GFP_KERNEL);
0420 if (!copy)
0421 return NULL;
0422 old_ext = strrchr(copy, '.');
0423 if (old_ext)
0424 *old_ext = '\0';
0425 if (dir)
0426 target = kasprintf(GFP_KERNEL, "%s/%s.%s", dir, copy, ext);
0427 else
0428 target = kasprintf(GFP_KERNEL, "%s.%s", copy, ext);
0429 kfree(copy);
0430
0431 return target;
0432 }
0433
0434
0435
0436
0437
0438
0439
0440 static char *get_link_target(const char *filename, const struct gcov_link *ext)
0441 {
0442 const char *rel;
0443 char *result;
0444
0445 if (strncmp(filename, objtree, strlen(objtree)) == 0) {
0446 rel = filename + strlen(objtree) + 1;
0447 if (ext->dir == SRC_TREE)
0448 result = link_target(srctree, rel, ext->ext);
0449 else
0450 result = link_target(objtree, rel, ext->ext);
0451 } else {
0452
0453 result = link_target(NULL, filename, ext->ext);
0454 }
0455
0456 return result;
0457 }
0458
0459 #define SKEW_PREFIX ".tmp_"
0460
0461
0462
0463
0464
0465 static const char *deskew(const char *basename)
0466 {
0467 if (strncmp(basename, SKEW_PREFIX, sizeof(SKEW_PREFIX) - 1) == 0)
0468 return basename + sizeof(SKEW_PREFIX) - 1;
0469 return basename;
0470 }
0471
0472
0473
0474
0475
0476 static void add_links(struct gcov_node *node, struct dentry *parent)
0477 {
0478 const char *basename;
0479 char *target;
0480 int num;
0481 int i;
0482
0483 for (num = 0; gcov_link[num].ext; num++)
0484 ;
0485 node->links = kcalloc(num, sizeof(struct dentry *), GFP_KERNEL);
0486 if (!node->links)
0487 return;
0488 for (i = 0; i < num; i++) {
0489 target = get_link_target(
0490 gcov_info_filename(get_node_info(node)),
0491 &gcov_link[i]);
0492 if (!target)
0493 goto out_err;
0494 basename = kbasename(target);
0495 if (basename == target)
0496 goto out_err;
0497 node->links[i] = debugfs_create_symlink(deskew(basename),
0498 parent, target);
0499 kfree(target);
0500 }
0501
0502 return;
0503 out_err:
0504 kfree(target);
0505 while (i-- > 0)
0506 debugfs_remove(node->links[i]);
0507 kfree(node->links);
0508 node->links = NULL;
0509 }
0510
0511 static const struct file_operations gcov_data_fops = {
0512 .open = gcov_seq_open,
0513 .release = gcov_seq_release,
0514 .read = seq_read,
0515 .llseek = seq_lseek,
0516 .write = gcov_seq_write,
0517 };
0518
0519
0520 static void init_node(struct gcov_node *node, struct gcov_info *info,
0521 const char *name, struct gcov_node *parent)
0522 {
0523 INIT_LIST_HEAD(&node->list);
0524 INIT_LIST_HEAD(&node->children);
0525 INIT_LIST_HEAD(&node->all);
0526 if (node->loaded_info) {
0527 node->loaded_info[0] = info;
0528 node->num_loaded = 1;
0529 }
0530 node->parent = parent;
0531 if (name)
0532 strcpy(node->name, name);
0533 }
0534
0535
0536
0537
0538
0539 static struct gcov_node *new_node(struct gcov_node *parent,
0540 struct gcov_info *info, const char *name)
0541 {
0542 struct gcov_node *node;
0543
0544 node = kzalloc(sizeof(struct gcov_node) + strlen(name) + 1, GFP_KERNEL);
0545 if (!node)
0546 goto err_nomem;
0547 if (info) {
0548 node->loaded_info = kcalloc(1, sizeof(struct gcov_info *),
0549 GFP_KERNEL);
0550 if (!node->loaded_info)
0551 goto err_nomem;
0552 }
0553 init_node(node, info, name, parent);
0554
0555 if (info) {
0556 node->dentry = debugfs_create_file(deskew(node->name), 0600,
0557 parent->dentry, node, &gcov_data_fops);
0558 } else
0559 node->dentry = debugfs_create_dir(node->name, parent->dentry);
0560 if (info)
0561 add_links(node, parent->dentry);
0562 list_add(&node->list, &parent->children);
0563 list_add(&node->all, &all_head);
0564
0565 return node;
0566
0567 err_nomem:
0568 kfree(node);
0569 pr_warn("out of memory\n");
0570 return NULL;
0571 }
0572
0573
0574 static void remove_links(struct gcov_node *node)
0575 {
0576 int i;
0577
0578 if (!node->links)
0579 return;
0580 for (i = 0; gcov_link[i].ext; i++)
0581 debugfs_remove(node->links[i]);
0582 kfree(node->links);
0583 node->links = NULL;
0584 }
0585
0586
0587
0588
0589
0590 static void release_node(struct gcov_node *node)
0591 {
0592 list_del(&node->list);
0593 list_del(&node->all);
0594 debugfs_remove(node->dentry);
0595 remove_links(node);
0596 kfree(node->loaded_info);
0597 if (node->unloaded_info)
0598 gcov_info_free(node->unloaded_info);
0599 kfree(node);
0600 }
0601
0602
0603 static void remove_node(struct gcov_node *node)
0604 {
0605 struct gcov_node *parent;
0606
0607 while ((node != &root_node) && list_empty(&node->children)) {
0608 parent = node->parent;
0609 release_node(node);
0610 node = parent;
0611 }
0612 }
0613
0614
0615
0616
0617
0618 static struct gcov_node *get_child_by_name(struct gcov_node *parent,
0619 const char *name)
0620 {
0621 struct gcov_node *node;
0622
0623 list_for_each_entry(node, &parent->children, list) {
0624 if (strcmp(node->name, name) == 0)
0625 return node;
0626 }
0627
0628 return NULL;
0629 }
0630
0631
0632
0633
0634
0635 static ssize_t reset_write(struct file *file, const char __user *addr,
0636 size_t len, loff_t *pos)
0637 {
0638 struct gcov_node *node;
0639
0640 mutex_lock(&node_lock);
0641 restart:
0642 list_for_each_entry(node, &all_head, all) {
0643 if (node->num_loaded > 0)
0644 reset_node(node);
0645 else if (list_empty(&node->children)) {
0646 remove_node(node);
0647
0648 goto restart;
0649 }
0650 }
0651 mutex_unlock(&node_lock);
0652
0653 return len;
0654 }
0655
0656
0657 static ssize_t reset_read(struct file *file, char __user *addr, size_t len,
0658 loff_t *pos)
0659 {
0660
0661 return 0;
0662 }
0663
0664 static const struct file_operations gcov_reset_fops = {
0665 .write = reset_write,
0666 .read = reset_read,
0667 .llseek = noop_llseek,
0668 };
0669
0670
0671
0672
0673
0674 static void add_node(struct gcov_info *info)
0675 {
0676 char *filename;
0677 char *curr;
0678 char *next;
0679 struct gcov_node *parent;
0680 struct gcov_node *node;
0681
0682 filename = kstrdup(gcov_info_filename(info), GFP_KERNEL);
0683 if (!filename)
0684 return;
0685 parent = &root_node;
0686
0687 for (curr = filename; (next = strchr(curr, '/')); curr = next + 1) {
0688 if (curr == next)
0689 continue;
0690 *next = 0;
0691 if (strcmp(curr, ".") == 0)
0692 continue;
0693 if (strcmp(curr, "..") == 0) {
0694 if (!parent->parent)
0695 goto err_remove;
0696 parent = parent->parent;
0697 continue;
0698 }
0699 node = get_child_by_name(parent, curr);
0700 if (!node) {
0701 node = new_node(parent, NULL, curr);
0702 if (!node)
0703 goto err_remove;
0704 }
0705 parent = node;
0706 }
0707
0708 node = new_node(parent, info, curr);
0709 if (!node)
0710 goto err_remove;
0711 out:
0712 kfree(filename);
0713 return;
0714
0715 err_remove:
0716 remove_node(parent);
0717 goto out;
0718 }
0719
0720
0721
0722
0723
0724 static void add_info(struct gcov_node *node, struct gcov_info *info)
0725 {
0726 struct gcov_info **loaded_info;
0727 int num = node->num_loaded;
0728
0729
0730
0731
0732
0733
0734 loaded_info = kcalloc(num + 1, sizeof(struct gcov_info *), GFP_KERNEL);
0735 if (!loaded_info) {
0736 pr_warn("could not add '%s' (out of memory)\n",
0737 gcov_info_filename(info));
0738 return;
0739 }
0740 memcpy(loaded_info, node->loaded_info,
0741 num * sizeof(struct gcov_info *));
0742 loaded_info[num] = info;
0743
0744 if (num == 0) {
0745
0746
0747
0748
0749 if (!gcov_info_is_compatible(node->unloaded_info, info)) {
0750 pr_warn("discarding saved data for %s "
0751 "(incompatible version)\n",
0752 gcov_info_filename(info));
0753 gcov_info_free(node->unloaded_info);
0754 node->unloaded_info = NULL;
0755 }
0756 } else {
0757
0758
0759
0760
0761 if (!gcov_info_is_compatible(node->loaded_info[0], info)) {
0762 pr_warn("could not add '%s' (incompatible "
0763 "version)\n", gcov_info_filename(info));
0764 kfree(loaded_info);
0765 return;
0766 }
0767 }
0768
0769 kfree(node->loaded_info);
0770 node->loaded_info = loaded_info;
0771 node->num_loaded = num + 1;
0772 }
0773
0774
0775
0776
0777 static int get_info_index(struct gcov_node *node, struct gcov_info *info)
0778 {
0779 int i;
0780
0781 for (i = 0; i < node->num_loaded; i++) {
0782 if (node->loaded_info[i] == info)
0783 return i;
0784 }
0785 return -ENOENT;
0786 }
0787
0788
0789
0790
0791 static void save_info(struct gcov_node *node, struct gcov_info *info)
0792 {
0793 if (node->unloaded_info)
0794 gcov_info_add(node->unloaded_info, info);
0795 else {
0796 node->unloaded_info = gcov_info_dup(info);
0797 if (!node->unloaded_info) {
0798 pr_warn("could not save data for '%s' "
0799 "(out of memory)\n",
0800 gcov_info_filename(info));
0801 }
0802 }
0803 }
0804
0805
0806
0807
0808
0809 static void remove_info(struct gcov_node *node, struct gcov_info *info)
0810 {
0811 int i;
0812
0813 i = get_info_index(node, info);
0814 if (i < 0) {
0815 pr_warn("could not remove '%s' (not found)\n",
0816 gcov_info_filename(info));
0817 return;
0818 }
0819 if (gcov_persist)
0820 save_info(node, info);
0821
0822 node->loaded_info[i] = node->loaded_info[node->num_loaded - 1];
0823 node->num_loaded--;
0824 if (node->num_loaded > 0)
0825 return;
0826
0827 kfree(node->loaded_info);
0828 node->loaded_info = NULL;
0829 node->num_loaded = 0;
0830 if (!node->unloaded_info)
0831 remove_node(node);
0832 }
0833
0834
0835
0836
0837
0838 void gcov_event(enum gcov_action action, struct gcov_info *info)
0839 {
0840 struct gcov_node *node;
0841
0842 mutex_lock(&node_lock);
0843 node = get_node_by_name(gcov_info_filename(info));
0844 switch (action) {
0845 case GCOV_ADD:
0846 if (node)
0847 add_info(node, info);
0848 else
0849 add_node(info);
0850 break;
0851 case GCOV_REMOVE:
0852 if (node)
0853 remove_info(node, info);
0854 else {
0855 pr_warn("could not remove '%s' (not found)\n",
0856 gcov_info_filename(info));
0857 }
0858 break;
0859 }
0860 mutex_unlock(&node_lock);
0861 }
0862
0863
0864 static __init int gcov_fs_init(void)
0865 {
0866 init_node(&root_node, NULL, NULL, NULL);
0867
0868
0869
0870
0871 root_node.dentry = debugfs_create_dir("gcov", NULL);
0872
0873
0874
0875
0876 debugfs_create_file("reset", 0600, root_node.dentry, NULL,
0877 &gcov_reset_fops);
0878
0879 gcov_enable_events();
0880 return 0;
0881 }
0882 device_initcall(gcov_fs_init);