kmemleak: Handle percpu memory allocation
This patch adds kmemleak callbacks from the percpu allocator, reducing a number of false positives caused by kmemleak not scanning such memory blocks. The percpu chunks are never reported as leaks because of current kmemleak limitations with the __percpu pointer not pointing directly to the actual chunks. Reported-by: Huajun Li <huajun.li.lee@gmail.com> Acked-by: Christoph Lameter <cl@gentwo.org> Acked-by: Tejun Heo <tj@kernel.org> Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
This commit is contained in:
		
					parent
					
						
							
								74341703ed
							
						
					
				
			
			
				commit
				
					
						f528f0b8e5
					
				
			
		
					 4 changed files with 94 additions and 1 deletions
				
			
		| 
						 | 
					@ -127,7 +127,10 @@ See the include/linux/kmemleak.h header for the functions prototype.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
kmemleak_init		 - initialize kmemleak
 | 
					kmemleak_init		 - initialize kmemleak
 | 
				
			||||||
kmemleak_alloc		 - notify of a memory block allocation
 | 
					kmemleak_alloc		 - notify of a memory block allocation
 | 
				
			||||||
 | 
					kmemleak_alloc_percpu	 - notify of a percpu memory block allocation
 | 
				
			||||||
kmemleak_free		 - notify of a memory block freeing
 | 
					kmemleak_free		 - notify of a memory block freeing
 | 
				
			||||||
 | 
					kmemleak_free_part	 - notify of a partial memory block freeing
 | 
				
			||||||
 | 
					kmemleak_free_percpu	 - notify of a percpu memory block freeing
 | 
				
			||||||
kmemleak_not_leak	 - mark an object as not a leak
 | 
					kmemleak_not_leak	 - mark an object as not a leak
 | 
				
			||||||
kmemleak_ignore		 - do not scan or report an object as leak
 | 
					kmemleak_ignore		 - do not scan or report an object as leak
 | 
				
			||||||
kmemleak_scan_area	 - add scan areas inside a memory block
 | 
					kmemleak_scan_area	 - add scan areas inside a memory block
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,8 +26,10 @@
 | 
				
			||||||
extern void kmemleak_init(void) __ref;
 | 
					extern void kmemleak_init(void) __ref;
 | 
				
			||||||
extern void kmemleak_alloc(const void *ptr, size_t size, int min_count,
 | 
					extern void kmemleak_alloc(const void *ptr, size_t size, int min_count,
 | 
				
			||||||
			   gfp_t gfp) __ref;
 | 
								   gfp_t gfp) __ref;
 | 
				
			||||||
 | 
					extern void kmemleak_alloc_percpu(const void __percpu *ptr, size_t size) __ref;
 | 
				
			||||||
extern void kmemleak_free(const void *ptr) __ref;
 | 
					extern void kmemleak_free(const void *ptr) __ref;
 | 
				
			||||||
extern void kmemleak_free_part(const void *ptr, size_t size) __ref;
 | 
					extern void kmemleak_free_part(const void *ptr, size_t size) __ref;
 | 
				
			||||||
 | 
					extern void kmemleak_free_percpu(const void __percpu *ptr) __ref;
 | 
				
			||||||
extern void kmemleak_padding(const void *ptr, unsigned long offset,
 | 
					extern void kmemleak_padding(const void *ptr, unsigned long offset,
 | 
				
			||||||
			     size_t size) __ref;
 | 
								     size_t size) __ref;
 | 
				
			||||||
extern void kmemleak_not_leak(const void *ptr) __ref;
 | 
					extern void kmemleak_not_leak(const void *ptr) __ref;
 | 
				
			||||||
| 
						 | 
					@ -68,6 +70,9 @@ static inline void kmemleak_alloc_recursive(const void *ptr, size_t size,
 | 
				
			||||||
					    gfp_t gfp)
 | 
										    gfp_t gfp)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					static inline void kmemleak_alloc_percpu(const void __percpu *ptr, size_t size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
static inline void kmemleak_free(const void *ptr)
 | 
					static inline void kmemleak_free(const void *ptr)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -77,6 +82,9 @@ static inline void kmemleak_free_part(const void *ptr, size_t size)
 | 
				
			||||||
static inline void kmemleak_free_recursive(const void *ptr, unsigned long flags)
 | 
					static inline void kmemleak_free_recursive(const void *ptr, unsigned long flags)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					static inline void kmemleak_free_percpu(const void __percpu *ptr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
static inline void kmemleak_not_leak(const void *ptr)
 | 
					static inline void kmemleak_not_leak(const void *ptr)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,8 +230,10 @@ static int kmemleak_skip_disable;
 | 
				
			||||||
/* kmemleak operation type for early logging */
 | 
					/* kmemleak operation type for early logging */
 | 
				
			||||||
enum {
 | 
					enum {
 | 
				
			||||||
	KMEMLEAK_ALLOC,
 | 
						KMEMLEAK_ALLOC,
 | 
				
			||||||
 | 
						KMEMLEAK_ALLOC_PERCPU,
 | 
				
			||||||
	KMEMLEAK_FREE,
 | 
						KMEMLEAK_FREE,
 | 
				
			||||||
	KMEMLEAK_FREE_PART,
 | 
						KMEMLEAK_FREE_PART,
 | 
				
			||||||
 | 
						KMEMLEAK_FREE_PERCPU,
 | 
				
			||||||
	KMEMLEAK_NOT_LEAK,
 | 
						KMEMLEAK_NOT_LEAK,
 | 
				
			||||||
	KMEMLEAK_IGNORE,
 | 
						KMEMLEAK_IGNORE,
 | 
				
			||||||
	KMEMLEAK_SCAN_AREA,
 | 
						KMEMLEAK_SCAN_AREA,
 | 
				
			||||||
| 
						 | 
					@ -852,6 +854,20 @@ out:
 | 
				
			||||||
	rcu_read_unlock();
 | 
						rcu_read_unlock();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Log an early allocated block and populate the stack trace.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					static void early_alloc_percpu(struct early_log *log)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned int cpu;
 | 
				
			||||||
 | 
						const void __percpu *ptr = log->ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for_each_possible_cpu(cpu) {
 | 
				
			||||||
 | 
							log->ptr = per_cpu_ptr(ptr, cpu);
 | 
				
			||||||
 | 
							early_alloc(log);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * kmemleak_alloc - register a newly allocated object
 | 
					 * kmemleak_alloc - register a newly allocated object
 | 
				
			||||||
 * @ptr:	pointer to beginning of the object
 | 
					 * @ptr:	pointer to beginning of the object
 | 
				
			||||||
| 
						 | 
					@ -878,6 +894,34 @@ void __ref kmemleak_alloc(const void *ptr, size_t size, int min_count,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(kmemleak_alloc);
 | 
					EXPORT_SYMBOL_GPL(kmemleak_alloc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * kmemleak_alloc_percpu - register a newly allocated __percpu object
 | 
				
			||||||
 | 
					 * @ptr:	__percpu pointer to beginning of the object
 | 
				
			||||||
 | 
					 * @size:	size of the object
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This function is called from the kernel percpu allocator when a new object
 | 
				
			||||||
 | 
					 * (memory block) is allocated (alloc_percpu). It assumes GFP_KERNEL
 | 
				
			||||||
 | 
					 * allocation.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void __ref kmemleak_alloc_percpu(const void __percpu *ptr, size_t size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned int cpu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pr_debug("%s(0x%p, %zu)\n", __func__, ptr, size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						 * Percpu allocations are only scanned and not reported as leaks
 | 
				
			||||||
 | 
						 * (min_count is set to 0).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr))
 | 
				
			||||||
 | 
							for_each_possible_cpu(cpu)
 | 
				
			||||||
 | 
								create_object((unsigned long)per_cpu_ptr(ptr, cpu),
 | 
				
			||||||
 | 
									      size, 0, GFP_KERNEL);
 | 
				
			||||||
 | 
						else if (atomic_read(&kmemleak_early_log))
 | 
				
			||||||
 | 
							log_early(KMEMLEAK_ALLOC_PERCPU, ptr, size, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(kmemleak_alloc_percpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * kmemleak_free - unregister a previously registered object
 | 
					 * kmemleak_free - unregister a previously registered object
 | 
				
			||||||
 * @ptr:	pointer to beginning of the object
 | 
					 * @ptr:	pointer to beginning of the object
 | 
				
			||||||
| 
						 | 
					@ -916,6 +960,28 @@ void __ref kmemleak_free_part(const void *ptr, size_t size)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
EXPORT_SYMBOL_GPL(kmemleak_free_part);
 | 
					EXPORT_SYMBOL_GPL(kmemleak_free_part);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * kmemleak_free_percpu - unregister a previously registered __percpu object
 | 
				
			||||||
 | 
					 * @ptr:	__percpu pointer to beginning of the object
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This function is called from the kernel percpu allocator when an object
 | 
				
			||||||
 | 
					 * (memory block) is freed (free_percpu).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void __ref kmemleak_free_percpu(const void __percpu *ptr)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned int cpu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pr_debug("%s(0x%p)\n", __func__, ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr))
 | 
				
			||||||
 | 
							for_each_possible_cpu(cpu)
 | 
				
			||||||
 | 
								delete_object_full((unsigned long)per_cpu_ptr(ptr,
 | 
				
			||||||
 | 
													      cpu));
 | 
				
			||||||
 | 
						else if (atomic_read(&kmemleak_early_log))
 | 
				
			||||||
 | 
							log_early(KMEMLEAK_FREE_PERCPU, ptr, 0, 0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL_GPL(kmemleak_free_percpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * kmemleak_not_leak - mark an allocated object as false positive
 | 
					 * kmemleak_not_leak - mark an allocated object as false positive
 | 
				
			||||||
 * @ptr:	pointer to beginning of the object
 | 
					 * @ptr:	pointer to beginning of the object
 | 
				
			||||||
| 
						 | 
					@ -1727,12 +1793,18 @@ void __init kmemleak_init(void)
 | 
				
			||||||
		case KMEMLEAK_ALLOC:
 | 
							case KMEMLEAK_ALLOC:
 | 
				
			||||||
			early_alloc(log);
 | 
								early_alloc(log);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
							case KMEMLEAK_ALLOC_PERCPU:
 | 
				
			||||||
 | 
								early_alloc_percpu(log);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
		case KMEMLEAK_FREE:
 | 
							case KMEMLEAK_FREE:
 | 
				
			||||||
			kmemleak_free(log->ptr);
 | 
								kmemleak_free(log->ptr);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		case KMEMLEAK_FREE_PART:
 | 
							case KMEMLEAK_FREE_PART:
 | 
				
			||||||
			kmemleak_free_part(log->ptr, log->size);
 | 
								kmemleak_free_part(log->ptr, log->size);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
 | 
							case KMEMLEAK_FREE_PERCPU:
 | 
				
			||||||
 | 
								kmemleak_free_percpu(log->ptr);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
		case KMEMLEAK_NOT_LEAK:
 | 
							case KMEMLEAK_NOT_LEAK:
 | 
				
			||||||
			kmemleak_not_leak(log->ptr);
 | 
								kmemleak_not_leak(log->ptr);
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								mm/percpu.c
									
										
									
									
									
								
							
							
						
						
									
										12
									
								
								mm/percpu.c
									
										
									
									
									
								
							| 
						 | 
					@ -67,6 +67,7 @@
 | 
				
			||||||
#include <linux/spinlock.h>
 | 
					#include <linux/spinlock.h>
 | 
				
			||||||
#include <linux/vmalloc.h>
 | 
					#include <linux/vmalloc.h>
 | 
				
			||||||
#include <linux/workqueue.h>
 | 
					#include <linux/workqueue.h>
 | 
				
			||||||
 | 
					#include <linux/kmemleak.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <asm/cacheflush.h>
 | 
					#include <asm/cacheflush.h>
 | 
				
			||||||
#include <asm/sections.h>
 | 
					#include <asm/sections.h>
 | 
				
			||||||
| 
						 | 
					@ -710,6 +711,7 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
 | 
				
			||||||
	const char *err;
 | 
						const char *err;
 | 
				
			||||||
	int slot, off, new_alloc;
 | 
						int slot, off, new_alloc;
 | 
				
			||||||
	unsigned long flags;
 | 
						unsigned long flags;
 | 
				
			||||||
 | 
						void __percpu *ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (unlikely(!size || size > PCPU_MIN_UNIT_SIZE || align > PAGE_SIZE)) {
 | 
						if (unlikely(!size || size > PCPU_MIN_UNIT_SIZE || align > PAGE_SIZE)) {
 | 
				
			||||||
		WARN(true, "illegal size (%zu) or align (%zu) for "
 | 
							WARN(true, "illegal size (%zu) or align (%zu) for "
 | 
				
			||||||
| 
						 | 
					@ -802,7 +804,9 @@ area_found:
 | 
				
			||||||
	mutex_unlock(&pcpu_alloc_mutex);
 | 
						mutex_unlock(&pcpu_alloc_mutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* return address relative to base address */
 | 
						/* return address relative to base address */
 | 
				
			||||||
	return __addr_to_pcpu_ptr(chunk->base_addr + off);
 | 
						ptr = __addr_to_pcpu_ptr(chunk->base_addr + off);
 | 
				
			||||||
 | 
						kmemleak_alloc_percpu(ptr, size);
 | 
				
			||||||
 | 
						return ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fail_unlock:
 | 
					fail_unlock:
 | 
				
			||||||
	spin_unlock_irqrestore(&pcpu_lock, flags);
 | 
						spin_unlock_irqrestore(&pcpu_lock, flags);
 | 
				
			||||||
| 
						 | 
					@ -916,6 +920,8 @@ void free_percpu(void __percpu *ptr)
 | 
				
			||||||
	if (!ptr)
 | 
						if (!ptr)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kmemleak_free_percpu(ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	addr = __pcpu_ptr_to_addr(ptr);
 | 
						addr = __pcpu_ptr_to_addr(ptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	spin_lock_irqsave(&pcpu_lock, flags);
 | 
						spin_lock_irqsave(&pcpu_lock, flags);
 | 
				
			||||||
| 
						 | 
					@ -1637,6 +1643,8 @@ int __init pcpu_embed_first_chunk(size_t reserved_size, size_t dyn_size,
 | 
				
			||||||
			rc = -ENOMEM;
 | 
								rc = -ENOMEM;
 | 
				
			||||||
			goto out_free_areas;
 | 
								goto out_free_areas;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							/* kmemleak tracks the percpu allocations separately */
 | 
				
			||||||
 | 
							kmemleak_free(ptr);
 | 
				
			||||||
		areas[group] = ptr;
 | 
							areas[group] = ptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		base = min(ptr, base);
 | 
							base = min(ptr, base);
 | 
				
			||||||
| 
						 | 
					@ -1751,6 +1759,8 @@ int __init pcpu_page_first_chunk(size_t reserved_size,
 | 
				
			||||||
					   "for cpu%u\n", psize_str, cpu);
 | 
										   "for cpu%u\n", psize_str, cpu);
 | 
				
			||||||
				goto enomem;
 | 
									goto enomem;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								/* kmemleak tracks the percpu allocations separately */
 | 
				
			||||||
 | 
								kmemleak_free(ptr);
 | 
				
			||||||
			pages[j++] = virt_to_page(ptr);
 | 
								pages[j++] = virt_to_page(ptr);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue