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