Currently, there is no convenient way to see the info that the
ref_tracking infrastructure collects. Attempt to create a file in
debugfs when called from ref_tracker_dir_init().

The file is given the name "class@%px", as having the unmodified address
is helpful for debugging. This should be safe since this directory is only
accessible by root

If debugfs file creation fails, a pr_warn will be isssued.

Signed-off-by: Jeff Layton <jlay...@kernel.org>
---
 include/linux/ref_tracker.h | 14 +++++++++
 lib/ref_tracker.c           | 72 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+)

diff --git a/include/linux/ref_tracker.h b/include/linux/ref_tracker.h
index 
2adae128d4b5e45f156af4775a1d184bb596fa91..c6e65d7ef4d4fc74c60fcabd19166c131d4173e2
 100644
--- a/include/linux/ref_tracker.h
+++ b/include/linux/ref_tracker.h
@@ -5,6 +5,7 @@
 #include <linux/types.h>
 #include <linux/spinlock.h>
 #include <linux/stackdepot.h>
+#include <linux/seq_file.h>
 
 struct ref_tracker;
 
@@ -18,12 +19,17 @@ struct ref_tracker_dir {
        struct list_head        list; /* List of active trackers */
        struct list_head        quarantine; /* List of dead trackers */
        const char              *class; /* object classname */
+#ifdef CONFIG_DEBUG_FS
+       struct dentry           *dentry;
+#endif
        char                    name[32];
 #endif
 };
 
 #ifdef CONFIG_REF_TRACKER
 
+void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir);
+
 static inline void ref_tracker_dir_init(struct ref_tracker_dir *dir,
                                        unsigned int quarantine_count,
                                        const char *class,
@@ -37,7 +43,11 @@ static inline void ref_tracker_dir_init(struct 
ref_tracker_dir *dir,
        refcount_set(&dir->untracked, 1);
        refcount_set(&dir->no_tracker, 1);
        dir->class = class;
+#ifdef CONFIG_DEBUG_FS
+       dir->dentry = NULL;
+#endif
        strscpy(dir->name, name, sizeof(dir->name));
+       ref_tracker_dir_debugfs(dir);
        stack_depot_init();
 }
 
@@ -66,6 +76,10 @@ static inline void ref_tracker_dir_init(struct 
ref_tracker_dir *dir,
 {
 }
 
+static inline void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir)
+{
+}
+
 static inline void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
 {
 }
diff --git a/lib/ref_tracker.c b/lib/ref_tracker.c
index 
4857bcb6d4bf557a0089f51328e75e8209e959e6..f5424857b0fde2339cce31c6744fd4dafbaf0d2a
 100644
--- a/lib/ref_tracker.c
+++ b/lib/ref_tracker.c
@@ -31,6 +31,14 @@ struct ref_tracker_dir_stats {
        } stacks[];
 };
 
+#ifdef CONFIG_DEBUG_FS
+static void ref_tracker_debugfs_remove(struct ref_tracker_dir *dir);
+#else
+static inline void ref_tracker_debugfs_remove(struct ref_tracker_dir *dir)
+{
+}
+#endif
+
 static struct ref_tracker_dir_stats *
 ref_tracker_get_stats(struct ref_tracker_dir *dir, unsigned int limit)
 {
@@ -197,6 +205,7 @@ void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
        bool leak = false;
 
        dir->dead = true;
+       ref_tracker_debugfs_remove(dir);
        spin_lock_irqsave(&dir->lock, flags);
        list_for_each_entry_safe(tracker, n, &dir->quarantine, head) {
                list_del(&tracker->head);
@@ -327,6 +336,69 @@ static int ref_tracker_dir_seq_print(struct 
ref_tracker_dir *dir, struct seq_fil
        return os.used;
 }
 
+static int ref_tracker_debugfs_show(struct seq_file *f, void *v)
+{
+       struct ref_tracker_dir *dir = f->private;
+
+       return ref_tracker_dir_seq_print(dir, f);
+}
+
+static int ref_tracker_debugfs_open(struct inode *inode, struct file *filp)
+{
+       struct ref_tracker_dir *dir = inode->i_private;
+
+       return single_open(filp, ref_tracker_debugfs_show, dir);
+}
+
+static const struct file_operations ref_tracker_debugfs_fops = {
+       .owner          = THIS_MODULE,
+       .open           = ref_tracker_debugfs_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+/**
+ * ref_tracker_dir_debugfs - create debugfs file for ref_tracker_dir
+ * @dir: ref_tracker_dir to be associated with debugfs file
+ * @fmt: format string for filename
+ * @...: arguments for the format string
+ *
+ * Call this function to create a debugfs file for your ref_tracker_dir that
+ * displays the current refcounts for the object. It will be automatically
+ * removed when the ref_tracker_dir exits. The filename must be unique. If
+ * the creation fails then the pr_warn will be emitted.
+ */
+void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir)
+{
+       char name[NAME_MAX + 1];
+       int ret;
+
+       /* No-op if already created */
+       if (!IS_ERR_OR_NULL(dir->dentry))
+               return;
+
+       ret = snprintf(name, sizeof(name), "%s@%px", dir->class, dir);
+       name[sizeof(name) - 1] = '\0';
+
+       if (ret < sizeof(name))
+               dir->dentry = debugfs_create_file(name, S_IFREG | 0400,
+                                                 ref_tracker_debug_dir, dir,
+                                                 &ref_tracker_debugfs_fops);
+       else
+               dir->dentry = ERR_PTR(-ENAMETOOLONG);
+
+       if (IS_ERR(dir->dentry))
+               pr_warn("ref_tracker: unable to create debugfs file for %s: 
%pe\n",
+                       name, dir->dentry);
+}
+EXPORT_SYMBOL(ref_tracker_dir_debugfs);
+
+static void ref_tracker_debugfs_remove(struct ref_tracker_dir *dir)
+{
+       debugfs_remove(dir->dentry);
+}
+
 static int __init ref_tracker_debugfs_init(void)
 {
        ref_tracker_debug_dir = debugfs_create_dir("ref_tracker", NULL);

-- 
2.49.0

Reply via email to