mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
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…
Reference in a new issue