Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

ref_tracker: automatically register a file in debugfs for a ref_tracker_dir

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

While ref_tracker_dir_init() is generally called from a context where
sleeping is OK, ref_tracker_dir_exit() can be called from anywhere.
Thus, dentry cleanup must be handled asynchronously.

Add a new global xarray that has entries with the ref_tracker_dir
pointer as the index and the corresponding debugfs dentry pointer as the
value. Instead of removing the debugfs dentry, have
ref_tracker_dir_exit() set a mark on the xarray entry and schedule a
workqueue job. The workqueue job then walks the xarray looking for
marked entries, and removes their xarray entries and the debugfs
dentries.

Because of this, the debugfs dentry can outlive the corresponding
ref_tracker_dir. Have ref_tracker_debugfs_show() take extra care to
ensure that it's safe to dereference the dir pointer before using it.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20250618-reftrack-dbgfs-v15-6-24fc37ead144@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Jeff Layton and committed by
Jakub Kicinski
65b584f5 f6dbe294

+164 -5
+17
include/linux/ref_tracker.h
··· 26 26 27 27 #ifdef CONFIG_REF_TRACKER 28 28 29 + #ifdef CONFIG_DEBUG_FS 30 + 31 + void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir); 32 + 33 + #else /* CONFIG_DEBUG_FS */ 34 + 35 + static inline void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir) 36 + { 37 + } 38 + 39 + #endif /* CONFIG_DEBUG_FS */ 40 + 29 41 static inline void ref_tracker_dir_init(struct ref_tracker_dir *dir, 30 42 unsigned int quarantine_count, 31 43 const char *class, ··· 52 40 refcount_set(&dir->no_tracker, 1); 53 41 dir->class = class; 54 42 strscpy(dir->name, name, sizeof(dir->name)); 43 + ref_tracker_dir_debugfs(dir); 55 44 stack_depot_init(); 56 45 } 57 46 ··· 78 65 unsigned int quarantine_count, 79 66 const char *class, 80 67 const char *name) 68 + { 69 + } 70 + 71 + static inline void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir) 81 72 { 82 73 } 83 74
+147 -5
lib/ref_tracker.c
··· 29 29 } stacks[]; 30 30 }; 31 31 32 + #ifdef CONFIG_DEBUG_FS 33 + #include <linux/xarray.h> 34 + 35 + /* 36 + * ref_tracker_dir_init() is usually called in allocation-safe contexts, but 37 + * the same is not true of ref_tracker_dir_exit() which can be called from 38 + * anywhere an object is freed. Removing debugfs dentries is a blocking 39 + * operation, so we defer that work to the debugfs_reap_worker. 40 + * 41 + * Each dentry is tracked in the appropriate xarray. When 42 + * ref_tracker_dir_exit() is called, its entries in the xarrays are marked and 43 + * the workqueue job is scheduled. The worker then runs and deletes any marked 44 + * dentries asynchronously. 45 + */ 46 + static struct xarray debugfs_dentries; 47 + static struct work_struct debugfs_reap_worker; 48 + 49 + #define REF_TRACKER_DIR_DEAD XA_MARK_0 50 + static inline void ref_tracker_debugfs_mark(struct ref_tracker_dir *dir) 51 + { 52 + unsigned long flags; 53 + 54 + xa_lock_irqsave(&debugfs_dentries, flags); 55 + __xa_set_mark(&debugfs_dentries, (unsigned long)dir, REF_TRACKER_DIR_DEAD); 56 + xa_unlock_irqrestore(&debugfs_dentries, flags); 57 + 58 + schedule_work(&debugfs_reap_worker); 59 + } 60 + #else 61 + static inline void ref_tracker_debugfs_mark(struct ref_tracker_dir *dir) 62 + { 63 + } 64 + #endif 65 + 32 66 static struct ref_tracker_dir_stats * 33 67 ref_tracker_get_stats(struct ref_tracker_dir *dir, unsigned int limit) 34 68 { ··· 219 185 bool leak = false; 220 186 221 187 dir->dead = true; 188 + /* 189 + * The xarray entries must be marked before the dir->lock is taken to 190 + * protect simultaneous debugfs readers. 191 + */ 192 + ref_tracker_debugfs_mark(dir); 222 193 spin_lock_irqsave(&dir->lock, flags); 223 194 list_for_each_entry_safe(tracker, n, &dir->quarantine, head) { 224 195 list_del(&tracker->head); ··· 351 312 va_end(args); 352 313 } 353 314 354 - static __maybe_unused int 355 - ref_tracker_dir_seq_print(struct ref_tracker_dir *dir, struct seq_file *seq) 315 + static int ref_tracker_dir_seq_print(struct ref_tracker_dir *dir, struct seq_file *seq) 356 316 { 357 317 struct ostream os = { .func = pr_ostream_seq, 358 318 .prefix = "", 359 319 .seq = seq }; 360 - unsigned long flags; 361 320 362 - spin_lock_irqsave(&dir->lock, flags); 363 321 __ref_tracker_dir_pr_ostream(dir, 16, &os); 364 - spin_unlock_irqrestore(&dir->lock, flags); 365 322 366 323 return os.used; 367 324 } 368 325 326 + static int ref_tracker_debugfs_show(struct seq_file *f, void *v) 327 + { 328 + struct ref_tracker_dir *dir = f->private; 329 + unsigned long index = (unsigned long)dir; 330 + unsigned long flags; 331 + int ret; 332 + 333 + /* 334 + * "dir" may not exist at this point if ref_tracker_dir_exit() has 335 + * already been called. Take care not to dereference it until its 336 + * legitimacy is established. 337 + * 338 + * The xa_lock is necessary to ensure that "dir" doesn't disappear 339 + * before its lock can be taken. If it's in the hash and not marked 340 + * dead, then it's safe to take dir->lock which prevents 341 + * ref_tracker_dir_exit() from completing. Once the dir->lock is 342 + * acquired, the xa_lock can be released. All of this must be IRQ-safe. 343 + */ 344 + xa_lock_irqsave(&debugfs_dentries, flags); 345 + if (!xa_load(&debugfs_dentries, index) || 346 + xa_get_mark(&debugfs_dentries, index, REF_TRACKER_DIR_DEAD)) { 347 + xa_unlock_irqrestore(&debugfs_dentries, flags); 348 + return -ENODATA; 349 + } 350 + 351 + spin_lock(&dir->lock); 352 + xa_unlock(&debugfs_dentries); 353 + ret = ref_tracker_dir_seq_print(dir, f); 354 + spin_unlock_irqrestore(&dir->lock, flags); 355 + return ret; 356 + } 357 + 358 + static int ref_tracker_debugfs_open(struct inode *inode, struct file *filp) 359 + { 360 + struct ref_tracker_dir *dir = inode->i_private; 361 + 362 + return single_open(filp, ref_tracker_debugfs_show, dir); 363 + } 364 + 365 + static const struct file_operations ref_tracker_debugfs_fops = { 366 + .owner = THIS_MODULE, 367 + .open = ref_tracker_debugfs_open, 368 + .read = seq_read, 369 + .llseek = seq_lseek, 370 + .release = single_release, 371 + }; 372 + 373 + /** 374 + * ref_tracker_dir_debugfs - create debugfs file for ref_tracker_dir 375 + * @dir: ref_tracker_dir to be associated with debugfs file 376 + * 377 + * In most cases, a debugfs file will be created automatically for every 378 + * ref_tracker_dir. If the object was created before debugfs is brought up 379 + * then that may fail. In those cases, it is safe to call this at a later 380 + * time to create the file. 381 + */ 382 + void ref_tracker_dir_debugfs(struct ref_tracker_dir *dir) 383 + { 384 + char name[NAME_MAX + 1]; 385 + struct dentry *dentry; 386 + int ret; 387 + 388 + /* No-op if already created */ 389 + dentry = xa_load(&debugfs_dentries, (unsigned long)dir); 390 + if (dentry && !xa_is_err(dentry)) 391 + return; 392 + 393 + ret = snprintf(name, sizeof(name), "%s@%px", dir->class, dir); 394 + name[sizeof(name) - 1] = '\0'; 395 + 396 + if (ret < sizeof(name)) { 397 + dentry = debugfs_create_file(name, S_IFREG | 0400, 398 + ref_tracker_debug_dir, dir, 399 + &ref_tracker_debugfs_fops); 400 + if (!IS_ERR(dentry)) { 401 + void *old; 402 + 403 + old = xa_store_irq(&debugfs_dentries, (unsigned long)dir, 404 + dentry, GFP_KERNEL); 405 + 406 + if (xa_is_err(old)) 407 + debugfs_remove(dentry); 408 + else 409 + WARN_ON_ONCE(old); 410 + } 411 + } 412 + } 413 + EXPORT_SYMBOL(ref_tracker_dir_debugfs); 414 + 415 + static void debugfs_reap_work(struct work_struct *work) 416 + { 417 + struct dentry *dentry; 418 + unsigned long index; 419 + bool reaped; 420 + 421 + do { 422 + reaped = false; 423 + xa_for_each_marked(&debugfs_dentries, index, dentry, REF_TRACKER_DIR_DEAD) { 424 + xa_erase_irq(&debugfs_dentries, index); 425 + debugfs_remove(dentry); 426 + reaped = true; 427 + } 428 + } while (reaped); 429 + } 430 + 369 431 static int __init ref_tracker_debugfs_init(void) 370 432 { 433 + INIT_WORK(&debugfs_reap_worker, debugfs_reap_work); 434 + xa_init_flags(&debugfs_dentries, XA_FLAGS_LOCK_IRQ); 371 435 ref_tracker_debug_dir = debugfs_create_dir("ref_tracker", NULL); 372 436 return 0; 373 437 }