Memory Leak Detection
Subsystems (9)
I'm sure that you would all have an interest in memory leak detection, because of the nature of the work that's done on this list. A series of patches were sent out on LKML to add support for memory leak detection. I patched my tree and then did a diff -uprN (and removed all the extra things I have added myself), and attatched to this email is that patch.
Documentation/kmemleak.txt +92 -0
@@ -0,0 +1,92 @@ +Kernel Memory Leak Detector +=========================== + + +Introduction +------------ + +Kmemleak provides a way of detecting possible kernel memory leaks in a +way similar to a tracing garbage collector +(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tracing_garbage_collectors), +with the difference that the orphan pointers are not freed but only +reported via /sys/kernel/debug/memleak. A similar method is used by +the Valgrind tool (memcheck --leak-check) to detect the memory leaks +in user-space applications. + + +Basic Algorithm +--------------- + +The memory allocations via kmalloc, vmalloc, kmem_cache_alloc and +friends are tracked and the pointers, together with additional +information like size and stack trace, are stored in a radix tree. The +corresponding freeing function calls are tracked and the pointers +removed from the radix tree. + +An allocated block of memory is considered orphan if a pointer to its +start address or to an alias (pointer aliases are explained later) +cannot be found by scanning the memory (including saved +registers). This means that there might be no way for the kernel to +pass the address of the allocated block to a freeing function and +therefore the block is considered a leak. + +The scanning algorithm steps: + + 1. mark all pointers as white (remaining white pointers will later + be considered orphan) + 2. scan the memory starting with the data section and stacks, + checking the values against the addresses stored in the radix + tree. If a white pointer is found, it is added to the grey list + 3. scan the grey pointers for matching addresses (some white + pointers can become grey and added at the end of the grey list) + until the grey set is finished + 4. the remaining white pointers are considered orphan and reported + via /sys/kernel/debug/memleak + + +Improvements +------------ + +Because the Linux kernel calculates many pointers at run-time via the +container_of macro (see the lists implementation), a lot of false +positives would be reported. This tool re-writes the container_of +macro so that the offset and size information is stored in the +.init.memleak_offsets section. The memleak_init() function creates a +radix tree with corresponding offsets for every encountered block +size. The memory allocations hook stores the pointer address together +with its aliases based on the size of the allocated block. + +While one level of offsets should be enough for most cases, two levels +are considered, i.e. container_of(container_of(...)) (one false +positive is the "struct socket_alloc" allocation in the +sock_alloc_inode() function). + +Some allocated memory blocks have pointers stored in the kernel's +internal data structures and they cannot be detected as orphans. To +avoid this, kmemleak can also store the number of values equal to the +pointer (or aliases) that need to be found so that the block is not +considered a leak. One example is __vmalloc(). + + +Limitations and Drawbacks +------------------------- + +The biggest drawback is the reduced performance of memory allocation +and freeing. To avoid other penalties, the memory scanning is only +performed when the /sys/kernel/debug/memleak file is read. Anyway, +this tool is intended for debugging purposes where the performance +might not be the most important requirement. + +The tool can report false positives. These are cases where an +allocated block doesn't need to be freed (some cases in the init_call +functions), the pointer is calculated by other methods than the +container_of macro or the pointer is stored in a location not scanned +by kmemleak. If the "member" argument in the offsetof(type, member) +call is not constant, kmemleak considers the offset as zero since it +cannot be determined at compilation time (as a side node, it seems +that gcc-4.0 doesn't compile these offsetof constructs either). + +Page allocations and ioremap are not tracked. NUMA architectures are +not supported yet. + +Only the ARM and i386 architectures are currently supported.
arch/arm/kernel/vmlinux.lds.S +7 -0
@@ -68,6 +68,11 @@ SECTIONS __per_cpu_start = .; *(.data.percpu) __per_cpu_end = .; +#ifdef CONFIG_DEBUG_MEMLEAK + __memleak_offsets_start = .; + *(.init.memleak_offsets) + __memleak_offsets_end = .; +#endif #ifndef CONFIG_XIP_KERNEL __init_begin = _stext; *(.init.data)
@@ -110,6 +115,7 @@ SECTIONS .data : AT(__data_loc) { __data_start = .; /* address in memory */ + _sdata = .; /* * first, the init task union, aligned
@@ -158,6 +164,7 @@ SECTIONS __bss_start = .; /* BSS */ *(.bss) *(COMMON) + __bss_stop = .; _end = .; } /* Stabs debugging sections. */
arch/i386/kernel/vmlinux.lds.S +4 -0
@@ -38,6 +38,7 @@ SECTIONS RODATA /* writeable */ + _sdata = .; /* Start of data section */ .data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */ *(.data) CONSTRUCTORS
@@ -140,6 +141,9 @@ SECTIONS __per_cpu_start = .; .data.percpu : AT(ADDR(.data.percpu) - LOAD_OFFSET) { *(.data.percpu) } __per_cpu_end = .; + __memleak_offsets_start = .; + .init.memleak_offsets : AT(ADDR(.init.memleak_offsets) - LOAD_OFFSET) { *(.init.memleak_offsets) } + __memleak_offsets_end = .; . = ALIGN(4096); __init_end = .; /* freed after init ends here */
drivers/base/platform.c +1 -0
@@ -166,6 +166,7 @@ struct platform_device *platform_device_struct platform_object *pa; pa =kzalloc (sizeof (struct platform_object ) +strlen (name),GFP_KERNEL ); +memleak_debug_resize (pa,sizeof (struct platform_object ));if (pa) {strcpy (pa->name, name); pa->pdev.name = pa->name;
include/asm-arm/processor.h +12 -0
@@ -121,6 +121,18 @@ extern int kernel_thread(int (*fn)(void#endif +#ifdef CONFIG_FRAME_POINTER +static inline unsigned long arch_call_address (void *frame) +{ +return *(unsigned long *) (frame -4 ) -4 ; +} + +static inline void *arch_prev_frame (void *frame) +{ +return *(void **) (frame -12 ); +} +#endif +#endif #endif /* __ASM_ARM_PROCESSOR_H */
include/asm-i386/processor.h +12 -0
@@ -743,4 +743,16 @@ extern void mcheck_init(struct cpuinfo_x#define mcheck_init (c) do {} while(0)#endif +#ifdef CONFIG_FRAME_POINTER +static inline unsigned long arch_call_address (void *frame) +{ +return *(unsigned long *) (frame +4 ); +} + +static inline void *arch_prev_frame (void *frame) +{ +return *(void **) frame; +} +#endif +#endif /* __ASM_I386_PROCESSOR_H */
include/linux/kernel.h +6 -1
@@ -13,6 +13,7 @@#include <linux/types.h> #include <linux/compiler.h> #include <linux/bitops.h> +#include <linux/memleak.h> #include <asm/byteorder.h> #include <asm/bug.h>
@@ -284,9 +285,13 @@ extern void dump_stack(void);* @member: the name of the member within the struct. * */ -#define container_of (ptr, type, member) ({ \ +#define __container_of (ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) +#define container_of (ptr, type, member) ({ \ + DECLARE_MEMLEAK_OFFSET(container_of, type, member); \ + __container_of(ptr, type, member); \ +})/* * Check at compile time that something is of a particular type.
include/linux/memleak.h +72 -0
72 lines not on this page · raw message
include/linux/slab.h +4 -0
@@ -89,6 +89,7 @@ extern void *__kmalloc_track_caller(sizestatic inline void *kmalloc (size_t size,gfp_t flags) { +#ifndef CONFIG_DEBUG_MEMLEAK if (__builtin_constant_p (size)) {int i =0 ;#define CACHE (x) \
@@ -107,6 +108,7 @@ found: malloc_sizes[i].cs_dmacachep : malloc_sizes[i].cs_cachep, flags); } +#endif return __kmalloc (size, flags); }
@@ -114,6 +116,7 @@ extern void *__kzalloc(size_t, gfp_t);static inline void *kzalloc (size_t size,gfp_t flags) { +#ifndef CONFIG_DEBUG_MEMLEAK if (__builtin_constant_p (size)) {int i =0 ;#define CACHE (x) \
@@ -132,6 +135,7 @@ found: malloc_sizes[i].cs_dmacachep : malloc_sizes[i].cs_cachep, flags); } +#endif return __kzalloc (size, flags); }
init/main.c +2 -1
@@ -513,6 +513,8 @@ asmlinkage void __init start_kernel(voidcpuset_init_early ();mem_init ();kmem_cache_init (); +radix_tree_init (); +memleak_debug_init ();setup_per_cpu_pageset ();numa_policy_init ();if (late_time_init)
@@ -533,7 +535,6 @@ asmlinkage void __init start_kernel(voidkey_init ();security_init ();vfs_caches_init (num_physpages); -radix_tree_init ();signals_init ();/* rootfs populating might need page-writeback */ page_writeback_init ();
ipc/util.c +2 -0
@@ -389,6 +389,7 @@ void* ipc_rcu_alloc(int size)*/ if (rcu_use_vmalloc (size)) { out =vmalloc (HDRLEN_VMALLOC + size); +memleak_debug_false_alarm (out);if (out) { out +=HDRLEN_VMALLOC ; container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 1;
@@ -396,6 +397,7 @@ void* ipc_rcu_alloc(int size) } }else { out =kmalloc (HDRLEN_KMALLOC + size,GFP_KERNEL ); +memleak_debug_false_alarm (out);if (out) { out +=HDRLEN_KMALLOC ; container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 0;
kernel/params.c +5 -2
@@ -548,6 +548,7 @@ static void __init kernel_param_sysfs_se unsigned int name_skip) {struct module_kobject *mk; +struct module_param_attrs *mp; mk =kzalloc (sizeof (struct module_kobject ),GFP_KERNEL );BUG_ON (!mk);
@@ -557,11 +558,13 @@ static void __init kernel_param_sysfs_sekobject_set_name (&mk->kobj, name);kobject_register (&mk->kobj); + mp =param_sysfs_setup (mk, kparam, num_params, name_skip);/* no need to keep the kobject if no parameter is exported */ -if (!param_sysfs_setup (mk, kparam, num_params, name_skip)) { +if (!mp) {kobject_unregister (&mk->kobj);kfree (mk); - } + }else +memleak_debug_false_alarm (mp); }/*
lib/Kconfig.debug +21 -0
@@ -89,6 +89,27 @@ config DEBUG_SLAB_LEAK bool "Memory leak debugging" depends on DEBUG_SLAB +config DEBUG_MEMLEAK + bool "Kernel memory leak detector" + default n + depends on EXPERIMENTAL && DEBUG_KERNEL && SLAB + depends on !NUMA + help + Say Y here if you want to enable the memory leak + detector. The memory allocation/freeing is traced in a way + similar to the Boehm's conservative garbage collector, the + difference being that the orphan pointers are not freed but + only shown in /sys/kernel/debug/memleak. Enabling this + feature will introduce an overhead to memory allocations. + +config DEBUG_MEMLEAK_TEST + tristate "Test the kernel memory leak detector" + default n + depends on DEBUG_MEMLEAK + help + Say Y here to build the test harness for the kernel memory + leak detector. + config DEBUG_PREEMPT bool "Debug preemptible kernel" depends on DEBUG_KERNEL && PREEMPT
mm/Makefile +2 -1
@@ -23,4 +23,5 @@ obj-$(CONFIG_SLAB) += slab.o obj-$(CONFIG_MEMORY_HOTPLUG) += memory_hotplug.o obj-$(CONFIG_FS_XIP) += filemap_xip.o obj-$(CONFIG_MIGRATION) += migrate.o - +obj-$(CONFIG_DEBUG_MEMLEAK) += memleak.o +obj-$(CONFIG_DEBUG_MEMLEAK_TEST) += memleak-test.o
mm/memleak-test.c +54 -0
54 lines not on this page · raw message
mm/memleak.c +813 -0
813 lines not on this page · raw message
mm/page_alloc.c +2 -0
@@ -2800,6 +2800,8 @@ void *__init alloc_large_system_hash(conif (_hash_mask) *_hash_mask = (1 << log2qty) -1 ; +memleak_debug_alloc (table, size,1 ); +return table; }
mm/slab.c +27 -4
@@ -434,6 +434,8 @@ struct kmem_cache {* variables contain the offset to the user object and its size. */ int obj_offset; +#endif +#if DEBUG || defined(CONFIG_DEBUG_MEMLEAK )int obj_size;#endif };
@@ -672,7 +674,7 @@ static struct kmem_cache cache_cache = { .shared = 1, .buffer_size = sizeof(struct kmem_cache), .name = "kmem_cache", -#if DEBUG +#if DEBUG || defined(CONFIG_DEBUG_MEMLEAK ) .obj_size = sizeof(struct kmem_cache),#endif };
@@ -2042,9 +2044,11 @@ kmem_cache_create (const char *name, sizif (!cachep) goto oops; -#if DEBUG +#if DEBUG || defined(CONFIG_DEBUG_MEMLEAK ) cachep->obj_size = size; +#endif +#if DEBUG if (flags &SLAB_RED_ZONE ) {/* redzoning only works with word aligned caches */ align =BYTES_PER_WORD ;
@@ -2879,6 +2883,7 @@ static inline void *____cache_alloc(struSTATS_INC_ALLOCMISS (cachep); objp =cache_alloc_refill (cachep, flags); } +memleak_debug_erase (ac->entry[ac->avail]);return objp; }
@@ -3144,7 +3149,11 @@ static inline void __cache_free(struct k*/ void *kmem_cache_alloc (struct kmem_cache *cachep,gfp_t flags) { -return __cache_alloc (cachep, flags,__builtin_return_address (0 )); +void *ptr =__cache_alloc (cachep, flags,__builtin_return_address (0 )); + +memleak_debug_alloc (ptr, cachep->obj_size,1 ); + +return ptr; }EXPORT_SYMBOL (kmem_cache_alloc);
@@ -3159,6 +3168,9 @@ EXPORT_SYMBOL(kmem_cache_alloc);void *kmem_cache_zalloc (struct kmem_cache *cache,gfp_t flags) {void *ret =__cache_alloc (cache, flags,__builtin_return_address (0 )); + +memleak_debug_alloc (ret, cache->obj_size,1 ); +if (ret)memset (ret,0 ,obj_size (cache));return ret;
@@ -3280,6 +3292,7 @@ static __always_inline void *__do_kmallo void *caller) {struct kmem_cache *cachep; +void *ptr;/* If you want to save a few bytes .text space: replace * __ with kmem_.
@@ -3289,7 +3302,11 @@ static __always_inline void *__do_kmallo cachep =__find_general_cachep (size, flags);if (unlikely (cachep ==NULL ))return NULL ; -return __cache_alloc (cachep, flags, caller); + ptr =__cache_alloc (cachep, flags, caller); + +memleak_debug_alloc (ptr, size,1 ); + +return ptr; }
@@ -3345,6 +3362,7 @@ void *__alloc_percpu(size_t size)memset (pdata->ptrs[i],0 , size); } +memleak_debug_false_alarm (pdata);/* Catch derefs w/o wrappers */ return (void *)(~(unsigned long )pdata);
@@ -3373,6 +3391,9 @@ void kmem_cache_free(struct kmem_cache *unsigned long flags;local_irq_save (flags); + +memleak_debug_free (objp); +__cache_free (cachep, objp);local_irq_restore (flags); }
@@ -3396,6 +3417,8 @@ void kfree(const void *objp)return ;local_irq_save (flags);kfree_debugcheck (objp); +memleak_debug_free (objp); + c =virt_to_cache (objp);mutex_debug_check_no_locks_freed (objp,obj_size (c));__cache_free (c, (void *)objp);
mm/vmalloc.c +19 -2
@@ -349,6 +349,9 @@ void __vunmap(void *addr, int deallocatevoid vfree (void *addr) {BUG_ON (in_interrupt ()); + +memleak_debug_free (addr); +__vunmap (addr,1 ); }EXPORT_SYMBOL (vfree);
@@ -447,7 +450,14 @@ fail:void *__vmalloc_area (struct vm_struct *area,gfp_t gfp_mask,pgprot_t prot) { -return __vmalloc_area_node (area, gfp_mask, prot,-1 ); +void *addr =__vmalloc_area_node (area, gfp_mask, prot,-1 ); + +/* this needs ref_count = 2 since vm_struct also contains a +pointer to this address. The guard page is also subtracted +from the size */ +memleak_debug_alloc (addr, area->size -PAGE_SIZE ,2 ); + +return addr; }/**
@@ -466,6 +476,7 @@ void *__vmalloc_node(unsigned long size, int node) {struct vm_struct *area; +void *addr; size =PAGE_ALIGN (size);if (!size || (size >>PAGE_SHIFT ) > num_physpages)
@@ -475,7 +486,13 @@ void *__vmalloc_node(unsigned long size,if (!area)return NULL ; -return __vmalloc_area_node (area, gfp_mask, prot, node); + addr =__vmalloc_area_node (area, gfp_mask, prot, node); + +/* this needs ref_count = 2 since the vm_struct also contains +a pointer to this address */ +memleak_debug_alloc (addr, size,2 ); + +return addr; }EXPORT_SYMBOL (__vmalloc_node);
_______________________________________________
Kernel-janitors mailing list
[email protected]
https://lists.osdl.org/mailman/listinfo/kernel-janitors