1 // SPDX-License-Identifier: GPL-2.0-or-later
3 #define pr_fmt(fmt) "ref_tracker: " fmt
5 #include <linux/export.h>
6 #include <linux/list_sort.h>
7 #include <linux/ref_tracker.h>
8 #include <linux/slab.h>
9 #include <linux/stacktrace.h>
10 #include <linux/stackdepot.h>
12 #define REF_TRACKER_STACK_ENTRIES 16
13 #define STACK_BUF_SIZE 1024
16 struct list_head head
; /* anchor into dir->list or dir->quarantine */
18 depot_stack_handle_t alloc_stack_handle
;
19 depot_stack_handle_t free_stack_handle
;
22 struct ref_tracker_dir_stats
{
26 depot_stack_handle_t stack_handle
;
31 static struct ref_tracker_dir_stats
*
32 ref_tracker_get_stats(struct ref_tracker_dir
*dir
, unsigned int limit
)
34 struct ref_tracker_dir_stats
*stats
;
35 struct ref_tracker
*tracker
;
37 stats
= kmalloc(struct_size(stats
, stacks
, limit
),
38 GFP_NOWAIT
| __GFP_NOWARN
);
40 return ERR_PTR(-ENOMEM
);
44 list_for_each_entry(tracker
, &dir
->list
, head
) {
45 depot_stack_handle_t stack
= tracker
->alloc_stack_handle
;
49 for (i
= 0; i
< stats
->count
; ++i
)
50 if (stats
->stacks
[i
].stack_handle
== stack
)
54 if (i
>= stats
->count
) {
55 stats
->stacks
[i
].stack_handle
= stack
;
56 stats
->stacks
[i
].count
= 0;
59 ++stats
->stacks
[i
].count
;
70 #define pr_ostream(stream, fmt, args...) \
72 struct ostream *_s = (stream); \
75 pr_err(fmt, ##args); \
77 int ret, len = _s->size - _s->used; \
78 ret = snprintf(_s->buf + _s->used, len, pr_fmt(fmt), ##args); \
79 _s->used += min(ret, len); \
84 __ref_tracker_dir_pr_ostream(struct ref_tracker_dir
*dir
,
85 unsigned int display_limit
, struct ostream
*s
)
87 struct ref_tracker_dir_stats
*stats
;
88 unsigned int i
= 0, skipped
;
89 depot_stack_handle_t stack
;
92 lockdep_assert_held(&dir
->lock
);
94 if (list_empty(&dir
->list
))
97 stats
= ref_tracker_get_stats(dir
, display_limit
);
99 pr_ostream(s
, "%s@%pK: couldn't get stats, error %pe\n",
100 dir
->name
, dir
, stats
);
104 sbuf
= kmalloc(STACK_BUF_SIZE
, GFP_NOWAIT
| __GFP_NOWARN
);
106 for (i
= 0, skipped
= stats
->total
; i
< stats
->count
; ++i
) {
107 stack
= stats
->stacks
[i
].stack_handle
;
108 if (sbuf
&& !stack_depot_snprint(stack
, sbuf
, STACK_BUF_SIZE
, 4))
110 pr_ostream(s
, "%s@%pK has %d/%d users at\n%s\n", dir
->name
, dir
,
111 stats
->stacks
[i
].count
, stats
->total
, sbuf
);
112 skipped
-= stats
->stacks
[i
].count
;
116 pr_ostream(s
, "%s@%pK skipped reports about %d/%d users.\n",
117 dir
->name
, dir
, skipped
, stats
->total
);
124 void ref_tracker_dir_print_locked(struct ref_tracker_dir
*dir
,
125 unsigned int display_limit
)
127 struct ostream os
= {};
129 __ref_tracker_dir_pr_ostream(dir
, display_limit
, &os
);
131 EXPORT_SYMBOL(ref_tracker_dir_print_locked
);
133 void ref_tracker_dir_print(struct ref_tracker_dir
*dir
,
134 unsigned int display_limit
)
138 spin_lock_irqsave(&dir
->lock
, flags
);
139 ref_tracker_dir_print_locked(dir
, display_limit
);
140 spin_unlock_irqrestore(&dir
->lock
, flags
);
142 EXPORT_SYMBOL(ref_tracker_dir_print
);
144 int ref_tracker_dir_snprint(struct ref_tracker_dir
*dir
, char *buf
, size_t size
)
146 struct ostream os
= { .buf
= buf
, .size
= size
};
149 spin_lock_irqsave(&dir
->lock
, flags
);
150 __ref_tracker_dir_pr_ostream(dir
, 16, &os
);
151 spin_unlock_irqrestore(&dir
->lock
, flags
);
155 EXPORT_SYMBOL(ref_tracker_dir_snprint
);
157 void ref_tracker_dir_exit(struct ref_tracker_dir
*dir
)
159 struct ref_tracker
*tracker
, *n
;
164 spin_lock_irqsave(&dir
->lock
, flags
);
165 list_for_each_entry_safe(tracker
, n
, &dir
->quarantine
, head
) {
166 list_del(&tracker
->head
);
168 dir
->quarantine_avail
++;
170 if (!list_empty(&dir
->list
)) {
171 ref_tracker_dir_print_locked(dir
, 16);
173 list_for_each_entry_safe(tracker
, n
, &dir
->list
, head
) {
174 list_del(&tracker
->head
);
178 spin_unlock_irqrestore(&dir
->lock
, flags
);
180 WARN_ON_ONCE(refcount_read(&dir
->untracked
) != 1);
181 WARN_ON_ONCE(refcount_read(&dir
->no_tracker
) != 1);
183 EXPORT_SYMBOL(ref_tracker_dir_exit
);
185 int ref_tracker_alloc(struct ref_tracker_dir
*dir
,
186 struct ref_tracker
**trackerp
,
189 unsigned long entries
[REF_TRACKER_STACK_ENTRIES
];
190 struct ref_tracker
*tracker
;
191 unsigned int nr_entries
;
192 gfp_t gfp_mask
= gfp
| __GFP_NOWARN
;
195 WARN_ON_ONCE(dir
->dead
);
198 refcount_inc(&dir
->no_tracker
);
201 if (gfp
& __GFP_DIRECT_RECLAIM
)
202 gfp_mask
|= __GFP_NOFAIL
;
203 *trackerp
= tracker
= kzalloc(sizeof(*tracker
), gfp_mask
);
204 if (unlikely(!tracker
)) {
205 pr_err_once("memory allocation failure, unreliable refcount tracker.\n");
206 refcount_inc(&dir
->untracked
);
209 nr_entries
= stack_trace_save(entries
, ARRAY_SIZE(entries
), 1);
210 tracker
->alloc_stack_handle
= stack_depot_save(entries
, nr_entries
, gfp
);
212 spin_lock_irqsave(&dir
->lock
, flags
);
213 list_add(&tracker
->head
, &dir
->list
);
214 spin_unlock_irqrestore(&dir
->lock
, flags
);
217 EXPORT_SYMBOL_GPL(ref_tracker_alloc
);
219 int ref_tracker_free(struct ref_tracker_dir
*dir
,
220 struct ref_tracker
**trackerp
)
222 unsigned long entries
[REF_TRACKER_STACK_ENTRIES
];
223 depot_stack_handle_t stack_handle
;
224 struct ref_tracker
*tracker
;
225 unsigned int nr_entries
;
228 WARN_ON_ONCE(dir
->dead
);
231 refcount_dec(&dir
->no_tracker
);
236 refcount_dec(&dir
->untracked
);
239 nr_entries
= stack_trace_save(entries
, ARRAY_SIZE(entries
), 1);
240 stack_handle
= stack_depot_save(entries
, nr_entries
,
241 GFP_NOWAIT
| __GFP_NOWARN
);
243 spin_lock_irqsave(&dir
->lock
, flags
);
245 pr_err("reference already released.\n");
246 if (tracker
->alloc_stack_handle
) {
247 pr_err("allocated in:\n");
248 stack_depot_print(tracker
->alloc_stack_handle
);
250 if (tracker
->free_stack_handle
) {
251 pr_err("freed in:\n");
252 stack_depot_print(tracker
->free_stack_handle
);
254 spin_unlock_irqrestore(&dir
->lock
, flags
);
258 tracker
->dead
= true;
260 tracker
->free_stack_handle
= stack_handle
;
262 list_move_tail(&tracker
->head
, &dir
->quarantine
);
263 if (!dir
->quarantine_avail
) {
264 tracker
= list_first_entry(&dir
->quarantine
, struct ref_tracker
, head
);
265 list_del(&tracker
->head
);
267 dir
->quarantine_avail
--;
270 spin_unlock_irqrestore(&dir
->lock
, flags
);
275 EXPORT_SYMBOL_GPL(ref_tracker_free
);