1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/export.h>
#include <linux/ref_tracker.h>
#include <linux/slab.h>
#include <linux/stacktrace.h>
#include <linux/stackdepot.h>
#define REF_TRACKER_STACK_ENTRIES 16
struct ref_tracker {
struct list_head head; /* anchor into dir->list or dir->quarantine */
bool dead;
depot_stack_handle_t alloc_stack_handle;
depot_stack_handle_t free_stack_handle;
};
void ref_tracker_dir_exit(struct ref_tracker_dir *dir)
{
struct ref_tracker *tracker, *n;
unsigned long flags;
bool leak = false;
dir->dead = true;
spin_lock_irqsave(&dir->lock, flags);
list_for_each_entry_safe(tracker, n, &dir->quarantine, head) {
list_del(&tracker->head);
kfree(tracker);
dir->quarantine_avail++;
}
list_for_each_entry_safe(tracker, n, &dir->list, head) {
pr_err("leaked reference.\n");
if (tracker->alloc_stack_handle)
stack_depot_print(tracker->alloc_stack_handle);
leak = true;
list_del(&tracker->head);
kfree(tracker);
}
spin_unlock_irqrestore(&dir->lock, flags);
WARN_ON_ONCE(leak);
WARN_ON_ONCE(refcount_read(&dir->untracked) != 1);
WARN_ON_ONCE(refcount_read(&dir->no_tracker) != 1);
}
EXPORT_SYMBOL(ref_tracker_dir_exit);
void ref_tracker_dir_print(struct ref_tracker_dir *dir,
unsigned int display_limit)
{
struct ref_tracker *tracker;
unsigned long flags;
unsigned int i = 0;
spin_lock_irqsave(&dir->lock, flags);
list_for_each_entry(tracker, &dir->list, head) {
if (i < display_limit) {
pr_err("leaked reference.\n");
if (tracker->alloc_stack_handle)
stack_depot_print(tracker->alloc_stack_handle);
i++;
} else {
break;
}
}
spin_unlock_irqrestore(&dir->lock, flags);
}
EXPORT_SYMBOL(ref_tracker_dir_print);
int ref_tracker_alloc(struct ref_tracker_dir *dir,
struct ref_tracker **trackerp,
gfp_t gfp)
{
unsigned long entries[REF_TRACKER_STACK_ENTRIES];
struct ref_tracker *tracker;
unsigned int nr_entries;
gfp_t gfp_mask = gfp;
unsigned long flags;
WARN_ON_ONCE(dir->dead);
if (!trackerp) {
refcount_inc(&dir->no_tracker);
return 0;
}
if (gfp & __GFP_DIRECT_RECLAIM)
gfp_mask |= __GFP_NOFAIL;
*trackerp = tracker = kzalloc(sizeof(*tracker), gfp_mask);
if (unlikely(!tracker)) {
pr_err_once("memory allocation failure, unreliable refcount tracker.\n");
refcount_inc(&dir->untracked);
return -ENOMEM;
}
nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1);
tracker->alloc_stack_handle = stack_depot_save(entries, nr_entries, gfp);
spin_lock_irqsave(&dir->lock, flags);
list_add(&tracker->head, &dir->list);
spin_unlock_irqrestore(&dir->lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(ref_tracker_alloc);
int ref_tracker_free(struct ref_tracker_dir *dir,
struct ref_tracker **trackerp)
{
unsigned long entries[REF_TRACKER_STACK_ENTRIES];
depot_stack_handle_t stack_handle;
struct ref_tracker *tracker;
unsigned int nr_entries;
unsigned long flags;
WARN_ON_ONCE(dir->dead);
if (!trackerp) {
refcount_dec(&dir->no_tracker);
return 0;
}
tracker = *trackerp;
if (!tracker) {
refcount_dec(&dir->untracked);
return -EEXIST;
}
nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 1);
stack_handle = stack_depot_save(entries, nr_entries, GFP_ATOMIC);
spin_lock_irqsave(&dir->lock, flags);
if (tracker->dead) {
pr_err("reference already released.\n");
if (tracker->alloc_stack_handle) {
pr_err("allocated in:\n");
stack_depot_print(tracker->alloc_stack_handle);
}
if (tracker->free_stack_handle) {
pr_err("freed in:\n");
stack_depot_print(tracker->free_stack_handle);
}
spin_unlock_irqrestore(&dir->lock, flags);
WARN_ON_ONCE(1);
return -EINVAL;
}
tracker->dead = true;
tracker->free_stack_handle = stack_handle;
list_move_tail(&tracker->head, &dir->quarantine);
if (!dir->quarantine_avail) {
tracker = list_first_entry(&dir->quarantine, struct ref_tracker, head);
list_del(&tracker->head);
} else {
dir->quarantine_avail--;
tracker = NULL;
}
spin_unlock_irqrestore(&dir->lock, flags);
kfree(tracker);
return 0;
}
EXPORT_SYMBOL_GPL(ref_tracker_free);
|