Skip to contentSkip to conversation

Kernel memory leak detector 0.9

PATCH In review Catalin Marinas 3 participants 39 messages Aug 12, 2006 – Aug 29, 2006

Subsystems (13)
lore
Share & tools
lore
#0 lore raw

cover Kernel memory leak detector 0.9

This is a new version (0.9) of the kernel memory leak detector. See the Documentation/kmemleak.txt file for a more detailed description. The patches are downloadable from (the whole patch or the broken-out series):

http://homepage.ntlworld.com/cmarinas/kmemleak/patch-2.6.18-rc4-kmemleak-0.9.bz2
http://homepage.ntlworld.com/cmarinas/kmemleak/broken-out/patches-kmemleak-0.9.tar.bz2

What's new in this version:

- updated for 2.6.18-rc4
- reviewers comments implemented
- the number of reports are limited to avoid soft-lockups in case of a serious memory leak
- some bug-fixes (including one that was preventing the page-aligned blocks from being reported)

To do:

- testing on a wider range of platforms and configurations
- support for ioremap tracking (once the generic ioremap patches are merged)
- eliminate the task stacks scanning (if possible, by marking the allocated blocks as temporary until the return to user-space - Ingo's suggestion)
- precise type identification (after first assessing the efficiency of the current method as it requires changes to the kernel API)

Signature · 1 line
Catalin
#1 lore raw

PATCH 1/10 Base support for kmemleak

From: Catalin Marinas <[email protected]>

This patch adds the base support for the kernel memory leak detector. It traces the memory allocation/freeing 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 /proc/memleak. Enabling this feature would introduce an overhead to memory allocations.

---

 include/linux/kernel.h  |    7 
 include/linux/memleak.h |  111 ++++
 init/main.c             |    3 
 lib/Kconfig.debug       |   80 +++
 mm/Makefile             |    2 
 mm/memleak.c            | 1320 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1520 insertions(+), 3 deletions(-)
include/linux/kernel.h +6 -1
index 181c69c..d071716 100644
@@ -13,6 +13,7 @@ #include <linux/stddef.h>
 #include <linux/types.h>
 #include <linux/compiler.h>
 #include <linux/bitops.h>
+#include <linux/memleak.h>
 #include <asm/byteorder.h>
 #include <asm/bug.h>
 
@@ -291,9 +292,13 @@ #define max_t(type,x,y) \
  * @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 new file +111 -0

111 lines not on this page · raw message

init/main.c +2 -1
index 8651a72..6497f61 100644
@@ -548,6 +548,8 @@ #endif
 	cpuset_init_early();
 	mem_init();
 	kmem_cache_init();
+	radix_tree_init();
+	memleak_init();
 	setup_per_cpu_pageset();
 	numa_policy_init();
 	if (late_time_init)
@@ -568,7 +570,6 @@ #endif
 	key_init();
 	security_init();
 	vfs_caches_init(num_physpages);
-	radix_tree_init();
 	signals_init();
 	/* rootfs populating might need page-writeback */
 	page_writeback_init();
lib/Kconfig.debug +80 -0

86 lines not on this page · raw message

mm/Makefile +1 -1
index 9dd824c..edccbc0 100644
@@ -23,4 +23,4 @@ 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
mm/memleak.c new file +1320 -0

1,320 lines not on this page · raw message

#2 lore raw

PATCH 2/10 Some documentation for kmemleak

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

---

 Documentation/kmemleak.txt |  157 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 157 insertions(+), 0 deletions(-)
Documentation/kmemleak.txt new file +157 -0
new file mode 100644
index 0000000..4d2f608
@@ -0,0 +1,157 @@
+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.
+
+
+Usage
+-----
+
+CONFIG_DEBUG_MEMLEAK has to be enabled. For additional config options,
+look in:
+
+  -> Kernel hacking
+    -> Kernel debugging
+      -> Debug slab memory allocations
+        -> Kernel memory leak detector
+
+To display the possible memory leaks:
+
+  # mount -t debugfs nodev /sys/kernel/debug/
+  # cat /sys/kernel/debug/memleak
+
+In order to reduce the run-time overhead, memory scanning is only
+performed when reading the /sys/kernel/debug/memleak file.
+
+
+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 type information is stored in the
+.init.memleak_offsets section. The memleak_init() function creates a
+radix tree with corresponding offsets for every encountered block
+type. The memory allocations hook stores the pointer address together
+with its aliases based on the type of the allocated block.
+
+While one level of offsets should be enough for most cases, a second
+level, i.e. container_of(container_of(...)), can be enabled via the
+configuration options (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.
+
+Kmemleak currently approximates the type id using the sizeof()
+compiler built-in function. This is not accurate and can lead to false
+negatives. The aim is to gradually change the kernel and kmemleak to
+do more precise type identification.
+
+Another source of false negatives is the data stored in non-pointer
+values. Together with the more precise type identification, kmemleak
+could only scan the pointer members in the allocated structures.
+
+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.
+
+Page allocations and ioremap are not tracked. Only the ARM and i386
+architectures are currently supported.
+
+
+Kmemleak API
+------------
+
+See the include/linux/memleak.h header for the functions prototype.
+
+memleak_init		- initialize kmemleak
+memleak_alloc		- notify of a memory block allocation
+memleak_free		- notify of a memory block freeing
+memleak_padding		- mark the boundaries of the data inside the block
+memleak_not_leak	- mark a pointer as not a leak
+memleak_ignore		- do not scan or report a pointer as leak
+memleak_scan_area	- add scan areas inside a memory block
+memleak_insert_aliases	- add aliases for a given type
+memleak_erase		- erase an old value in a pointer variable
+memleak_typeid_raw	- set the typeid for an allocated block
+memleak_container	- statically declare a pointer alias
+memleak_typeid		- set the typeid for an allocated block (takes
+			  a type rather than typeid as argument)
+
+
+Dealing with false positives/negatives
+--------------------------------------
+
+To reduce the false negatives, kmemleak provides the memleak_ignore,
+memleak_scan_area and memleak_erase functions. The task stacks also
+increase the amount of false negatives and their scanning is not
+enabled by default.
+
+To eliminate the false positives caused by code allocating a different
+size from the object one (either for alignment or for extra memory
+after the end of the structure), kmemleak provides the memleak_padding
+and memleak_typeid functions.
+
+For pointers known not to be leaks, kmemleak provides the
+memleak_not_leak function. The memleak_ignore could also be used if
+the memory block is known not to contain other pointers as it will no
+longer be scanned.
#3 lore raw

PATCH 3/10 Add the memory allocation/freeing hooks for kmemleak

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

This patch adds the callbacks to memleak_(alloc|free) functions from kmalloc/kfree, kmem_cache_(alloc|free), vmalloc/vfree etc.

---

 include/linux/slab.h |    6 ++++++
 mm/page_alloc.c      |    2 ++
 mm/slab.c            |   21 +++++++++++++++++++--
 mm/vmalloc.c         |   22 ++++++++++++++++++++--
 4 files changed, 47 insertions(+), 4 deletions(-)
include/linux/slab.h +6 -0
index 45ad55b..e3f5945 100644
@@ -133,6 +133,8 @@ #endif
  */
 static inline void *kmalloc(size_t size, gfp_t flags)
 {
+#ifndef CONFIG_DEBUG_MEMLEAK
+	/* this block removes the size information needed by kmemleak */
 	if (__builtin_constant_p(size)) {
 		int i = 0;
 #define CACHE(x) \
@@ -151,6 +153,7 @@ found:
 			malloc_sizes[i].cs_dmacachep :
 			malloc_sizes[i].cs_cachep, flags);
 	}
+#endif
 	return __kmalloc(size, flags);
 }
 
@@ -163,6 +166,8 @@ extern void *__kzalloc(size_t, gfp_t);
  */
 static inline void *kzalloc(size_t size, gfp_t flags)
 {
+#ifndef CONFIG_DEBUG_MEMLEAK
+	/* this block removes the size information needed by kmemleak */
 	if (__builtin_constant_p(size)) {
 		int i = 0;
 #define CACHE(x) \
@@ -181,6 +186,7 @@ found:
 			malloc_sizes[i].cs_dmacachep :
 			malloc_sizes[i].cs_cachep, flags);
 	}
+#endif
 	return __kzalloc(size, flags);
 }
 
mm/page_alloc.c +2 -0
index 54a4f53..bdf6445 100644
@@ -2450,6 +2450,8 @@ void *__init alloc_large_system_hash(con
 	if (_hash_mask)
 		*_hash_mask = (1 << log2qty) - 1;
 
+	memleak_alloc(table, size, 1);
+
 	return table;
 }
 
mm/slab.c +19 -2
index 21ba060..42c90aa 100644
@@ -2446,6 +2446,9 @@ static struct slab *alloc_slabmgmt(struc
 		/* Slab management obj is off-slab. */
 		slabp = kmem_cache_alloc_node(cachep->slabp_cache,
 					      local_flags, nodeid);
+		/* only scan the list member to avoid false negatives */
+		memleak_scan_area(slabp, offsetof(struct slab, list),
+				  sizeof(struct list_head));
 		if (!slabp)
 			return NULL;
 	} else {
@@ -2986,6 +2989,8 @@ #endif
 		STATS_INC_ALLOCMISS(cachep);
 		objp = cache_alloc_refill(cachep, flags);
 	}
+	/* avoid false negatives */
+	memleak_erase(&ac->entry[ac->avail]);
 	return objp;
 }
 
@@ -3002,6 +3007,7 @@ static __always_inline void *__cache_all
 	local_irq_restore(save_flags);
 	objp = cache_alloc_debugcheck_after(cachep, flags, objp,
 					    caller);
+	memleak_alloc(objp, obj_size(cachep), 1);
 	prefetchw(objp);
 	return objp;
 }
@@ -3193,6 +3199,7 @@ static inline void __cache_free(struct k
 	struct array_cache *ac = cpu_cache_get(cachep);
 
 	check_irq_off();
+	memleak_free(objp);
 	objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));
 
 	if (cache_free_alien(cachep, objp))
@@ -3312,6 +3319,7 @@ void *kmem_cache_alloc_node(struct kmem_
 
 	ptr = cache_alloc_debugcheck_after(cachep, flags, ptr,
 					   __builtin_return_address(0));
+	memleak_alloc(ptr, obj_size(cachep), 1);
 
 	return ptr;
 }
@@ -3320,11 +3328,15 @@ EXPORT_SYMBOL(kmem_cache_alloc_node);
 void *kmalloc_node(size_t size, gfp_t flags, int node)
 {
 	struct kmem_cache *cachep;
+	void *ptr;
 
 	cachep = kmem_find_general_cachep(size, flags);
 	if (unlikely(cachep == NULL))
 		return NULL;
-	return kmem_cache_alloc_node(cachep, flags, node);
+	ptr = kmem_cache_alloc_node(cachep, flags, node);
+	memleak_padding(ptr, 0, size);
+	memleak_typeid_raw(ptr, ml_guess_typeid(size));
+	return ptr;
 }
 EXPORT_SYMBOL(kmalloc_node);
 #endif
@@ -3339,6 +3351,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_.
@@ -3348,7 +3361,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_padding(ptr, 0, size);
+	memleak_typeid_raw(ptr, ml_guess_typeid(size));
+
+	return ptr;
 }
 
 
mm/vmalloc.c +20 -2
index 266162d..a4d319f 100644
@@ -364,6 +364,9 @@ void __vunmap(void *addr, int deallocate
 void vfree(void *addr)
 {
 	BUG_ON(in_interrupt());
+
+	memleak_free(addr);
+
 	__vunmap(addr, 1);
 }
 EXPORT_SYMBOL(vfree);
@@ -463,7 +466,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_alloc(addr, area->size - PAGE_SIZE, 2);
+
+	return addr;
 }
 
 /**
@@ -482,6 +492,8 @@ void *__vmalloc_node(unsigned long size,
 			int node)
 {
 	struct vm_struct *area;
+	void *addr;
+	unsigned long real_size = size;
 
 	size = PAGE_ALIGN(size);
 	if (!size || (size >> PAGE_SHIFT) > num_physpages)
@@ -491,7 +503,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_alloc(addr, real_size, 2);
+
+	return addr;
 }
 EXPORT_SYMBOL(__vmalloc_node);
 
#4 lore raw

PATCH 4/10 Modules support for kmemleak

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

This patch handles the kmemleak operations needed for modules loading so that memory allocations from inside a module are properly tracked.

---

 kernel/module.c |   41 +++++++++++++++++++++++++++++++++++++++++
 1 files changed, 41 insertions(+), 0 deletions(-)
kernel/module.c +41 -0
index 2a19cd4..a7f8c6d 100644
@@ -1481,6 +1481,11 @@ static struct module *load_module(void _
 	unsigned int unusedcrcindex;
 	unsigned int unusedgplindex;
 	unsigned int unusedgplcrcindex;
+#ifdef CONFIG_DEBUG_MEMLEAK
+	unsigned int dataindex;
+	unsigned int bssindex;
+	unsigned int mloffindex;
+#endif
 	struct module *mod;
 	long err = 0;
 	void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
@@ -1577,6 +1582,11 @@ #endif
 #ifdef ARCH_UNWIND_SECTION_NAME
 	unwindex = find_sec(hdr, sechdrs, secstrings, ARCH_UNWIND_SECTION_NAME);
 #endif
+#ifdef CONFIG_DEBUG_MEMLEAK
+	dataindex = find_sec(hdr, sechdrs, secstrings, ".data");
+	bssindex = find_sec(hdr, sechdrs, secstrings, ".bss");
+	mloffindex = find_sec(hdr, sechdrs, secstrings, ".init.memleak_offsets");
+#endif
 
 	/* Don't keep modinfo section */
 	sechdrs[infoindex].sh_flags &= ~(unsigned long)SHF_ALLOC;
@@ -1646,6 +1656,10 @@ #endif
 
 	/* Do the allocs. */
 	ptr = module_alloc(mod->core_size);
+	/* the pointer to this block is stored in the module structure
+	 * which is inside the block. Just mark it as not being a
+	 * leak */
+	memleak_not_leak(ptr);
 	if (!ptr) {
 		err = -ENOMEM;
 		goto free_percpu;
@@ -1654,6 +1668,11 @@ #endif
 	mod->module_core = ptr;
 
 	ptr = module_alloc(mod->init_size);
+	/* the pointer to this block is stored in the module structure
+	 * which is inside the block. This block doesn't need to be
+	 * scanned as it contains data and code that will be freed
+	 * after the module is initialized */
+	memleak_ignore(ptr);
 	if (!ptr && mod->init_size) {
 		err = -ENOMEM;
 		goto free_core;
@@ -1685,6 +1704,28 @@ #endif
 	/* Module has been moved. */
 	mod = (void *)sechdrs[modindex].sh_addr;
 
+#ifdef CONFIG_DEBUG_MEMLEAK
+	if (mloffindex)
+		memleak_insert_aliases((void *)sechdrs[mloffindex].sh_addr,
+				       (void *)sechdrs[mloffindex].sh_addr
+				         + sechdrs[mloffindex].sh_size);
+
+	/* only scan the sections containing data */
+	memleak_scan_area(mod->module_core,
+			  (unsigned long)mod - (unsigned long)mod->module_core,
+			  sizeof(struct module));
+	if (dataindex)
+		memleak_scan_area(mod->module_core,
+				  sechdrs[dataindex].sh_addr
+				    - (unsigned long)mod->module_core,
+				  sechdrs[dataindex].sh_size);
+	if (bssindex)
+		memleak_scan_area(mod->module_core,
+				  sechdrs[bssindex].sh_addr
+				    - (unsigned long)mod->module_core,
+				  sechdrs[bssindex].sh_size);
+#endif
+
 	/* Now we've moved module, initialize linked lists, etc. */
 	module_unload_init(mod);
 
#5 lore raw

PATCH 5/10 Add kmemleak support for i386

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

This patch modifies the vmlinux.lds.S script and adds the backtrace support for i386 to be used with kmemleak.

---

 arch/i386/kernel/vmlinux.lds.S |    4 ++++
 include/asm-i386/processor.h   |   12 ++++++++++++
 include/asm-i386/thread_info.h |   10 +++++++++-
 3 files changed, 25 insertions(+), 1 deletions(-)
arch/i386/kernel/vmlinux.lds.S +4 -0
index 2d4f138..34fccf1 100644
@@ -45,6 +45,7 @@ SECTIONS
   __tracedata_end = .;
 
   /* writeable */
+  _sdata = .;			/* Start of data section */
   .data : AT(ADDR(.data) - LOAD_OFFSET) {	/* Data */
 	*(.data)
 	CONSTRUCTORS
@@ -156,6 +157,9 @@ #endif
   __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 */
include/asm-i386/processor.h +12 -0
index b32346d..29626e3 100644
@@ -731,4 +731,16 @@ extern unsigned long boot_option_idle_ov
 extern void enable_sep_cpu(void);
 extern int sysenter_setup(void);
 
+#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/asm-i386/thread_info.h +9 -1
index 54d6d7a..054553f 100644
@@ -100,12 +100,20 @@ #define alloc_thread_info(tsk)					\
 		struct thread_info *ret;			\
 								\
 		ret = kmalloc(THREAD_SIZE, GFP_KERNEL);		\
+		memleak_ignore(ret);				\
 		if (ret)					\
 			memset(ret, 0, THREAD_SIZE);		\
 		ret;						\
 	})
 #else
-#define alloc_thread_info(tsk) kmalloc(THREAD_SIZE, GFP_KERNEL)
+#define alloc_thread_info(tsk)					\
+	({							\
+		struct thread_info *ret;			\
+								\
+		ret = kmalloc(THREAD_SIZE, GFP_KERNEL);		\
+		memleak_ignore(ret);				\
+		ret;						\
+	})
 #endif
 
 #define free_thread_info(info)	kfree(info)
#6 lore raw

PATCH 6/10 Add kmemleak support for ARM

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

This patch modifies the vmlinux.lds.S script and adds the backtrace support for ARM to be used with kmemleak.

---

 arch/arm/kernel/vmlinux.lds.S |    7 +++++++
 include/asm-arm/processor.h   |   12 ++++++++++++
 2 files changed, 19 insertions(+), 0 deletions(-)
arch/arm/kernel/vmlinux.lds.S +7 -0
index 3ca574e..59976b8 100644
@@ -67,6 +67,11 @@ #endif
 		__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)
@@ -115,6 +120,7 @@ #endif
 
 	.data : AT(__data_loc) {
 		__data_start = .;	/* address in memory */
+		_sdata = .;
 
 		/*
 		 * first, the init task union, aligned
@@ -165,6 +171,7 @@ #endif
 		__bss_start = .;	/* BSS				*/
 		*(.bss)
 		*(COMMON)
+		__bss_stop = .;
 		_end = .;
 	}
 					/* Stabs debugging sections.	*/
include/asm-arm/processor.h +12 -0
index 04f4d34..34a3bb3 100644
@@ -121,6 +121,18 @@ #define spin_lock_prefetch(x) do { } whi
 
 #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 */
#7 lore raw

PATCH 7/10 Remove some of the kmemleak false positives

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

There are allocations for which the main pointer cannot be found but they are not memory leaks. This patch fixes some of them.

---

 drivers/base/platform.c                |    3 +++
 drivers/hwmon/w83627hf.c               |    4 ++++
 drivers/scsi/hosts.c                   |    3 +++
 fs/ext3/dir.c                          |    3 +++
 ipc/util.c                             |    6 ++++++
 kernel/params.c                        |    8 +++++++-
 mm/slab.c                              |    4 ++++
 net/core/dev.c                         |    6 ++++++
 net/core/skbuff.c                      |    3 +++
 net/ipv4/netfilter/ip_conntrack_core.c |    5 +++++
 net/sched/sch_generic.c                |    6 ++++++
 11 files changed, 50 insertions(+), 1 deletions(-)
drivers/base/platform.c +3 -0
index 2b8755d..1521fe4 100644
@@ -166,6 +166,9 @@ struct platform_device *platform_device_
 	struct platform_object *pa;
 
 	pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
+	/* kmemleak cannot guess the object type because the block
+	 * size is different from the object size */
+	memleak_typeid(pa, struct platform_object);
 	if (pa) {
 		strcpy(pa->name, name);
 		pa->pdev.name = pa->name;
drivers/hwmon/w83627hf.c +4 -0
index 79368d5..f952f02 100644
@@ -1065,6 +1065,10 @@ static int w83627hf_detect(struct i2c_ad
 		err = -ENOMEM;
 		goto ERROR1;
 	}
+	/* the pointer to member is stored but the code doesn't use
+	 * container_of for access and the alias need to be
+	 * explicitely declared here */
+	memleak_container(struct w83627hf_data, client);
 
 	new_client = &data->client;
 	i2c_set_clientdata(new_client, data);
drivers/scsi/hosts.c +3 -0
index dfcb96f..9516d37 100644
@@ -297,6 +297,9 @@ struct Scsi_Host *scsi_host_alloc(struct
 	shost = kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask);
 	if (!shost)
 		return NULL;
+	/* kmemleak cannot guess the object type because the block
+	 * size is different from the object size */
+	memleak_typeid(shost, struct Scsi_Host);
 
 	spin_lock_init(&shost->default_lock);
 	scsi_assign_lock(shost, &shost->default_lock);
fs/ext3/dir.c +3 -0
index fbb0d4e..a34814d 100644
@@ -346,6 +346,9 @@ int ext3_htree_store_dirent(struct file 
 	new_fn = kmalloc(len, GFP_KERNEL);
 	if (!new_fn)
 		return -ENOMEM;
+	/* kmemleak cannot guess the object type because the block
+	 * size is different from the object size */
+	memleak_typeid(new_fn, struct fname);
 	memset(new_fn, 0, len);
 	new_fn->hash = hash;
 	new_fn->minor_hash = minor_hash;
ipc/util.c +6 -0
index 67b6d17..17cc294 100644
@@ -388,6 +388,9 @@ void* ipc_rcu_alloc(int size)
 	 */
 	if (rcu_use_vmalloc(size)) {
 		out = vmalloc(HDRLEN_VMALLOC + size);
+		/* the stored pointer is different from the address of
+		 * the allocated block because of padding */
+		memleak_padding(out, HDRLEN_VMALLOC, size);
 		if (out) {
 			out += HDRLEN_VMALLOC;
 			container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 1;
@@ -395,6 +398,9 @@ void* ipc_rcu_alloc(int size)
 		}
 	} else {
 		out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
+		/* the stored pointer is different from the address of
+		 * the allocated block because of padding */
+		memleak_padding(out, HDRLEN_KMALLOC, size);
 		if (out) {
 			out += HDRLEN_KMALLOC;
 			container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 0;
kernel/params.c +7 -1
index 91aea7a..b957b86 100644
@@ -547,6 +547,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);
@@ -556,8 +557,13 @@ static void __init kernel_param_sysfs_se
 	kobject_set_name(&mk->kobj, name);
 	kobject_register(&mk->kobj);
 
+	mp = param_sysfs_setup(mk, kparam, num_params, name_skip);
+	/* this structure is not freed but the pointer is
+	 * lost. However, there are other pointers to its members and
+	 * the object has to be kept */
+	memleak_not_leak(mp);
 	/* 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);
 	}
mm/slab.c +4 -0
index 42c90aa..b29662e 100644
@@ -3421,6 +3421,10 @@ void *__alloc_percpu(size_t size)
 		memset(pdata->ptrs[i], 0, size);
 	}
 
+	/* the code below changes the value of the returned pointer
+	 * and kmemleak cannot find the original value during
+	 * scanning. It is marked as not being a leak */
+	memleak_not_leak(pdata);
 	/* Catch derefs w/o wrappers */
 	return (void *)(~(unsigned long)pdata);
 
net/core/dev.c +6 -0
index d95e262..3c1bcac 100644
@@ -3211,6 +3211,12 @@ struct net_device *alloc_netdev(int size
 	dev = (struct net_device *)
 		(((long)p + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
 	dev->padded = (char *)dev - (char *)p;
+	/* kmemleak cannot guess the object type because the block
+	 * size is different from the object size. The stored pointer
+	 * is also different from the address of the allocated block
+	 * because of padding */
+	memleak_padding(p, dev->padded, alloc_size - dev->padded);
+	memleak_typeid(p, struct net_device);
 
 	if (sizeof_priv)
 		dev->priv = netdev_priv(dev);
net/core/skbuff.c +3 -0
index 022d889..7b511e6 100644
@@ -151,6 +151,9 @@ struct sk_buff *__alloc_skb(unsigned int
 
 	/* Get the HEAD */
 	skb = kmem_cache_alloc(cache, gfp_mask & ~__GFP_DMA);
+	/* the skbuff_fclone_cache contains objects larger than
+	 * "struct sk_buff" and kmemleak cannot guess the type */
+	memleak_typeid(skb, struct sk_buff);
 	if (!skb)
 		goto out;
 
net/ipv4/netfilter/ip_conntrack_core.c +5 -0
index aa45917..7e64fe0 100644
@@ -654,6 +654,11 @@ struct ip_conntrack *ip_conntrack_alloc(
 	}
 
 	conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
+	/* tuplehash_to_ctrack doesn't pass a constant argument to
+	 * container_of and therefore the conntrack->tuplehash[].list
+	 * aliases are ignored */
+	memleak_container(struct ip_conntrack, tuplehash[IP_CT_DIR_ORIGINAL]);
+	memleak_container(struct ip_conntrack, tuplehash[IP_CT_DIR_REPLY]);
 	if (!conntrack) {
 		DEBUGP("Can't allocate conntrack.\n");
 		return ERR_PTR(-ENOMEM);
net/sched/sch_generic.c +6 -0
index 0834c2e..7fa6aab 100644
@@ -437,6 +437,12 @@ struct Qdisc *qdisc_alloc(struct net_dev
 		goto errout;
 	sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p);
 	sch->padded = (char *) sch - (char *) p;
+	/* kmemleak cannot guess the object type because the block
+	 * size is different from the object size. The stored pointer
+	 * is also different from the address of the allocated block
+	 * because of padding */
+	memleak_padding(p, sch->padded, sizeof(struct Qdisc));
+	memleak_typeid(p, struct Qdisc);
 
 	INIT_LIST_HEAD(&sch->list);
 	skb_queue_head_init(&sch->q);
#8 lore raw

PATCH 8/10 Keep the __init functions after initialization

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

This patch adds the CONFIG_DEBUG_KEEP_INIT option which preserves the .init.text section after initialization. Memory leaks happening during this phase can be more easily tracked.

---

 include/linux/init.h |    4 ++++
 lib/Kconfig.debug    |   12 ++++++++++++
 2 files changed, 16 insertions(+), 0 deletions(-)
include/linux/init.h +4 -0
index 6667785..1590e9a 100644
@@ -40,7 +40,11 @@ #include <linux/compiler.h>
 
 /* These are for everybody (although not all archs will actually
    discard it in modules) */
+#ifdef CONFIG_DEBUG_KEEP_INIT
+#define __init
+#else
 #define __init		__attribute__ ((__section__ (".init.text")))
+#endif
 #define __initdata	__attribute__ ((__section__ (".init.data")))
 #define __exitdata	__attribute__ ((__section__(".exit.data")))
 #define __exit_call	__attribute_used__ __attribute__ ((__section__ (".exitcall.exit")))
lib/Kconfig.debug +12 -0
index 77ddb7d..c8ca3d6 100644
@@ -185,6 +185,18 @@ config DEBUG_MEMLEAK_REPORTS_NR
 	  reading the /sys/kernel/debug/memleak file could lead to
 	  some soft-locks.
 
+config DEBUG_KEEP_INIT
+	bool "Do not free the __init functions"
+	default n
+	depends on DEBUG_MEMLEAK
+	help
+	  This option moves the __init functions out of the .init.text
+	  section and therefore they are no longer freed after the
+	  kernel initialization. It is useful for identifying memory
+	  leaks happening during the kernel or modules initialization.
+
+	  If unsure, say N.
+
 config DEBUG_PREEMPT
 	bool "Debug preemptible kernel"
 	depends on DEBUG_KERNEL && PREEMPT && TRACE_IRQFLAGS_SUPPORT
#9 lore raw

PATCH 9/10 Simple testing for kmemleak

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

This patch only contains some very simple testing at the moment. Proper testing will be needed.

---

 lib/Kconfig.debug |   11 +++++++
 mm/Makefile       |    1 +
 mm/memleak-test.c |   89 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 101 insertions(+), 0 deletions(-)
lib/Kconfig.debug +11 -0
index c8ca3d6..c2ba98d 100644
@@ -197,6 +197,17 @@ config DEBUG_KEEP_INIT
 
 	  If unsure, say N.
 
+config DEBUG_MEMLEAK_TEST
+	tristate "Test the kernel memory leak detector"
+	default n
+	depends on DEBUG_MEMLEAK
+	help
+	  Say Y or M here to build the test harness for the kernel
+	  memory leak detector. At the moment, this option enables a
+	  module that explicitly leaks memory.
+
+	  If unsure, say N.
+
 config DEBUG_PREEMPT
 	bool "Debug preemptible kernel"
 	depends on DEBUG_KERNEL && PREEMPT && TRACE_IRQFLAGS_SUPPORT
mm/Makefile +1 -0
index edccbc0..fd46a73 100644
@@ -24,3 +24,4 @@ obj-$(CONFIG_MEMORY_HOTPLUG) += memory_h
 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 new file +89 -0

89 lines not on this page · raw message

#10 lore raw

PATCH 10/10 Update the MAINTAINERS file for kmemleak

In reply to Catalin Marinas

From: Catalin Marinas <[email protected]>

---

 MAINTAINERS |    6 ++++++
 1 files changed, 6 insertions(+), 0 deletions(-)
MAINTAINERS +6 -0
index e3e1515..6dccfbf 100644
@@ -1663,6 +1663,12 @@ L:	[email protected]
 W:	http://www.kerneljanitors.org/
 S:	Maintained
 
+KERNEL MEMORY LEAK DETECTOR
+P:	Catalin Marinas
+M:	[email protected]
+W:	http://www.procode.org/
+S:	Maintained
+
 KERNEL NFSD
 P:	Neil Brown
 M:	[email protected]
#11 lore raw
In reply to Catalin Marinas

Hi Catalin,

Quoted 7 lines · Catalin Marinas

This is a new version (0.9) of the kernel memory leak detector. See the Documentation/kmemleak.txt file for a more detailed description. The patches are downloadable from (the whole patch or the broken-out series):

http://homepage.ntlworld.com/cmarinas/kmemleak/patch-2.6.18-rc4-kmemleak-0.9.bz2
http://homepage.ntlworld.com/cmarinas/kmemleak/broken-out/patches-kmemleak-0.9.tar.bz2

Can you look at this?

=======================================================
[ INFO: possible circular locking dependency detected ]
-------------------------------------------------------
events/0/8 is trying to acquire lock:
(old_style_spin_init){++..}, at: [<c017674f>] memleak_free+0x95/0x157

but task is already holding lock:
(&parent->list_lock){++..}, at: [<c0174f29>] drain_array+0x49/0xad

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #1 (&parent->list_lock){++..}:

      [<c0140cc7>] check_prevs_add+0x4d/0xaf
      [<c01423c1>] __lock_acquire+0x7b1/0x814
      [<c01429bc>] lock_acquire+0x5e/0x7e
      [<c02f9f7a>] _spin_lock+0x23/0x2f
      [<c0174058>] cache_alloc_refill+0x76/0x1d2
      [<c0174559>] kmem_cache_alloc+0x73/0xce
      [<c01f0c8a>] radix_tree_node_alloc+0x1a/0x51
      [<c01f0e3f>] radix_tree_insert+0x51/0xfb
      [<c01761f6>] insert_alias+0x85/0xe8
      [<c01762a4>] memleak_insert_aliases+0x4b/0xa6
      [<c01773f7>] memleak_init+0x44/0xf5
      [<c0100ab0>] start_kernel+0x17e/0x1f9
      [<c0100210>] 0xc0100210
-> #0 (old_style_spin_init){++..}:
      [<c0140cc7>] check_prevs_add+0x4d/0xaf
      [<c01423c1>] __lock_acquire+0x7b1/0x814
      [<c01429bc>] lock_acquire+0x5e/0x7e
      [<c02f9f7a>] _spin_lock+0x23/0x2f
      [<c017674f>] memleak_free+0x95/0x157
      [<c0174a74>] kmem_cache_free+0x62/0xbc
      [<c0172fc8>] slab_destroy+0x48/0x4d
      [<c01743b8>] free_block+0xc9/0x101
      [<c0174f65>] drain_array+0x85/0xad
      [<c017500d>] cache_reap+0x80/0xfe
      [<c01394dd>] run_workqueue+0x88/0xc4
      [<c0139617>] worker_thread+0xfe/0x131
      [<c013c6e1>] kthread+0x82/0xaa
      [<c01044c9>] kernel_thread_helper+0x5/0xb

other info that might help us debug this:

2 locks held by events/0/8:
#0: (cache_chain_mutex){--..}, at: [<c0174f9d>] cache_reap+0x10/0xfe #1: (&parent->list_lock){++..}, at: [<c0174f29>] drain_array+0x49/0xad

stack backtrace:
[<c0106e59>] show_trace_log_lvl+0x58/0x14c
[<c0106f5a>] show_trace+0xd/0xf
[<c010702c>] dump_stack+0x17/0x19
[<c01405c0>] print_circular_bug_tail+0x59/0x62
[<c0140af1>] check_prev_add+0x2b/0x1b4
[<c0140cc7>] check_prevs_add+0x4d/0xaf
[<c01423c1>] __lock_acquire+0x7b1/0x814
[<c01429bc>] lock_acquire+0x5e/0x7e
[<c02f9f7a>] _spin_lock+0x23/0x2f
[<c017674f>] memleak_free+0x95/0x157
[<c0174a74>] kmem_cache_free+0x62/0xbc
[<c0172fc8>] slab_destroy+0x48/0x4d
[<c01743b8>] free_block+0xc9/0x101
[<c0174f65>] drain_array+0x85/0xad
[<c017500d>] cache_reap+0x80/0xfe
[<c01394dd>] run_workqueue+0x88/0xc4
[<c0139617>] worker_thread+0xfe/0x131
 [<c013c6e1>] kthread+0x82/0xaa
[<c01044c9>] kernel_thread_helper+0x5/0xb
DWARF2 unwinder stuck at kernel_thread_helper+0x5/0xb
Leftover inexact backtrace:
[<c0106f5a>] show_trace+0xd/0xf
[<c010702c>] dump_stack+0x17/0x19
[<c01405c0>] print_circular_bug_tail+0x59/0x62
[<c0140af1>] check_prev_add+0x2b/0x1b4
[<c0140cc7>] check_prevs_add+0x4d/0xaf
[<c01423c1>] __lock_acquire+0x7b1/0x814
[<c01429bc>] lock_acquire+0x5e/0x7e
[<c02f9f7a>] _spin_lock+0x23/0x2f
[<c017674f>] memleak_free+0x95/0x157
[<c0174a74>] kmem_cache_free+0x62/0xbc
[<c0172fc8>] slab_destroy+0x48/0x4d
[<c01743b8>] free_block+0xc9/0x101
[<c0174f65>] drain_array+0x85/0xad
[<c017500d>] cache_reap+0x80/0xfe
[<c01394dd>] run_workqueue+0x88/0xc4
[<c0139617>] worker_thread+0xfe/0x131
 [<c013c6e1>] kthread+0x82/0xaa
[<c01044c9>] kernel_thread_helper+0x5/0xb

0xc017674f is in memleak_free (/usr/src/linux-work3/mm/memleak.c:479).

474     /* Remove a pointer and its aliases from the pointer radix tree */
475     static inline void delete_pointer(unsigned long ptr)
476     {

477 struct memleak_pointer *pointer;
478
479 pointer = radix_tree_delete(&pointer_tree, ptr);
480 if (!pointer) {
481 dump_stack();
482 printk(KERN_WARNING "kmemleak: freeing unknown
pointer value 0x%08lx\n", ptr);
483 return;

0xc0174f29 is in drain_array (/usr/src/linux-work3/mm/slab.c:3739).
3734 return;
3735 if (ac->touched && !force) {
3736 ac->touched = 0;
3737 } else {
3738 spin_lock_irq(&l3->list_lock);
3739 if (ac->avail) {
3740 tofree = force ? ac->avail :
(ac->limit + 4) / 5;
3741 if (tofree > ac->avail)
3742 tofree = (ac->avail + 1) / 2;
3743 free_block(cachep, ac->entry, tofree, node);

0xc0174f9d is in cache_reap (/usr/src/linux-work3/mm/slab.c:3770).
3765 {

3766            struct kmem_cache *searchp;
3767            struct kmem_list3 *l3;
3768            int node = numa_node_id();

3769
3770 if (!mutex_trylock(&cache_chain_mutex)) {
3771 /* Give up. Setup the next iteration. */
3772 schedule_delayed_work(&__get_cpu_var(reap_work),
3773 REAPTIMEOUT_CPUC);
3774 return;

config file and dmesg http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/

Quoted 2 lines

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#12 lore raw

Hi Michal,

Quoted 2 lines · Michal Piotrowski

Can you look at this?

=======================================================

Quoted 1 line

[ INFO: possible circular locking dependency detected ]

Thanks for this. Did you get this with the previous version of kmemleak (0.8) or you just tried it now.

I'll have a look.

Signature · 1 line
Catalin
#13 lore raw
Quoted 10 lines · Catalin Marinas

Hi Michal,

Quoted 2 lines · Michal Piotrowski

Can you look at this?

=======================================================

Quoted 1 line

[ INFO: possible circular locking dependency detected ]

Thanks for this. Did you get this with the previous version of kmemleak (0.8) or you just tried it now.

It's kmemleak 0.9 issue. I have tested kmemleak 0.8 on 2.6.18-rc1and 2.6.18-rc2. I haven't seen this before.

Quoted 6 lines

I'll have a look.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#14 lore raw
Quoted 2 lines · Michal Piotrowski

It's kmemleak 0.9 issue. I have tested kmemleak 0.8 on 2.6.18-rc1and 2.6.18-rc2. I haven't seen this before.

it looks like it was caused by commit fc818301a8a39fedd7f0a71f878f29130c72193d where free_block() now calls slab_destroy() with l3->list_lock held.

The prio_tree use (which doesn't alloc memory) instead of the radix_tree is about 4 times slower when scanning the memory and I don't think I'll use it.

It leaves me with the options of either implementing my own memory allocator based on pages (including a simple hash table instead of radix tree) or fix the locking in kmemleak so that memory allocations happen without memleak_lock held. The latter is a bit complicated as well since any slab allocation causes a re-entrance into kmemleak.

Any other suggestions?

Thanks.

Signature · 1 line
Catalin
#15 lore raw

Hi Catalin,

Quoted 7 lines · Catalin Marinas
Quoted 2 lines · Michal Piotrowski

It's kmemleak 0.9 issue. I have tested kmemleak 0.8 on 2.6.18-rc1and 2.6.18-rc2. I haven't seen this before.

it looks like it was caused by commit fc818301a8a39fedd7f0a71f878f29130c72193d where free_block() now calls slab_destroy() with l3->list_lock held.

I'll revert this commit.

Quoted 5 lines

The prio_tree use (which doesn't alloc memory) instead of the radix_tree is about 4 times slower when scanning the memory and I don't think I'll use it.

Quoted 7 lines

It leaves me with the options of either implementing my own memory allocator based on pages (including a simple hash table instead of radix tree) or fix the locking in kmemleak so that memory allocations happen without memleak_lock held. The latter is a bit complicated as well since any slab allocation causes a re-entrance into kmemleak.

Any other suggestions?

Please talk with Christoph Lameter, he is working on Modular Slab.
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.1/0951.html
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.2/0030.html
Maybe he can help with this problem.

Quoted 6 lines

Thanks.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#16 lore raw
Quoted 4 lines · Michal Piotrowski

Hi Catalin,

Quoted 1 line · Catalin Marinas

On 13/08/06, Michal Piotrowski <[email protected]> wrote:

Quoted 16 lines
Quoted 6 lines
Quoted 2 lines

It's kmemleak 0.9 issue. I have tested kmemleak 0.8 on 2.6.18-rc1and 2.6.18-rc2. I haven't seen this before.

it looks like it was caused by commit fc818301a8a39fedd7f0a71f878f29130c72193d where free_block() now calls slab_destroy() with l3->list_lock held.

I'll revert this commit.

Quoted 7 lines

The prio_tree use (which doesn't alloc memory) instead of the radix_tree is about 4 times slower when scanning the memory and I don't think I'll use it.

It leaves me with the options of either implementing my own memory allocator based on pages

[MODSLAB 7/7] A slab allocator: Page Slab allocator
"The page slab is a specialized slab allocator that can only handle page order size object. It directly uses the page allocator to track the objects and can therefore avoid the overhead of the slabifier."
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.1/3023.html

Quoted 5 lines

(including a simple hash table instead of

Quoted 4 lines

radix tree) or fix the locking in kmemleak so that memory allocations happen without memleak_lock held. The latter is a bit complicated as well since any slab allocation causes a re-entrance into kmemleak.

Quoted 13 lines
Quoted 1 line

Any other suggestions?

Please talk with Christoph Lameter, he is working on Modular Slab.
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.1/0951.html
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.2/0030.html
Maybe he can help with this problem.

Quoted 6 lines

Thanks.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#17 lore raw
Quoted 10 lines · Michal Piotrowski
Quoted 3 lines · Catalin Marinas
Quoted 2 lines

It leaves me with the options of either implementing my own memory allocator based on pages

[MODSLAB 7/7] A slab allocator: Page Slab allocator
"The page slab is a specialized slab allocator that can only handle page order size object. It directly uses the page allocator to track the objects and can therefore avoid the overhead of the slabifier."
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.1/3023.html

But this one allocates page-order size objects. I usually have plenty of small objects in kmemleak.

Signature · 1 line
Catalin
#18 lore raw
In reply to Michal Piotrowski
Quoted 10 lines · Michal Piotrowski
Quoted 7 lines · Catalin Marinas
Quoted 2 lines · Michal Piotrowski

It's kmemleak 0.9 issue. I have tested kmemleak 0.8 on 2.6.18-rc1and 2.6.18-rc2. I haven't seen this before.

it looks like it was caused by commit fc818301a8a39fedd7f0a71f878f29130c72193d where free_block() now calls slab_destroy() with l3->list_lock held.

I'll revert this commit.

I'm not sure it's a good idea, it might have other implications in slab.c. I better fix kmemleak (I think currently you could get a deadlock only on SMP).

Quoted 4 lines

Please talk with Christoph Lameter, he is working on Modular Slab.
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.1/0951.html
http://www.ussg.iu.edu/hypermail/linux/kernel/0608.2/0030.html
Maybe he can help with this problem.

I haven't looked at these patches in detail but they look like making the slab allocator cleaner.

Anyway, I still need to revisit the locking in kmemleak and not rely on future changes to slab.c. At the moment I think I can avoid any kmemleak locks when allocating memory (by using radix_tree_preload with the radix trees). If this still fails, I'll think about writing my own, very simple, memory allocator and avoid the re-entrance problem.

Thanks.

Signature · 1 line
Catalin
#19 lore raw
Quoted 10 lines · Catalin Marinas
Quoted 9 lines · Michal Piotrowski
Quoted 7 lines · Catalin Marinas
Quoted 2 lines · Michal Piotrowski

It's kmemleak 0.9 issue. I have tested kmemleak 0.8 on 2.6.18-rc1and 2.6.18-rc2. I haven't seen this before.

it looks like it was caused by commit fc818301a8a39fedd7f0a71f878f29130c72193d where free_block() now calls slab_destroy() with l3->list_lock held.

Quoted 5 lines
Quoted 1 line

I'll revert this commit.

I'm not sure it's a good idea, it might have other implications in slab.c. I better fix kmemleak (I think currently you could get a deadlock only on SMP).

I attach a patch for kmemleak-0.9 which seems to work for me. It has a new locking mechanism in that memory allocations are performed without memleak_lock held. Let me know if you still get errors (I haven't fully tested it yet).

Thanks.

Signature · 1 line
Catalin
lib/Kconfig.debug +0 -0
mm/memleak.c +368 -162

893 lines not on this page · raw message

#20 lore raw

Hi Catalin,

Quoted 9 lines · Catalin Marinas
Quoted 3 lines · Catalin Marinas

I'm not sure it's a good idea, it might have other implications in slab.c. I better fix kmemleak (I think currently you could get a deadlock only on SMP).

I attach a patch for kmemleak-0.9 which seems to work for me. It has a new locking mechanism in that memory allocations are performed without memleak_lock held. Let me know if you still get errors (I haven't fully tested it yet).

Something doesn't work. It appears while udev start.

[<c0106cdc>] show_trace+0xd/0xf
[<c0106dae>] dump_stack+0x17/0x19
[<c016e9a0>] __kmalloc+0xf6/0x10a
[<c019a4bf>] load_elf_binary+0x5b/0xb46
[<c017d77a>] search_binary_handler+0xfc/0x2d4
[<c017daa5>] do_execve+0x153/0x1f0
[<c0104d5f>] sys_execve+0x29/0x7a
[<c0105f11>] sysenter_past_esp+0x5b/0x79

kmemleak: pointer 0xf5a39ae0:
trace:
c016e767: <kmemleak_cache_alloc>
c0127274: <dup_mm>
c0127427: <copy_mm>
c0128227: <copy_process>
c012893: <do_fork>
c0104d19: <sys_clone>

Kernel panic - not syncing: kmemleak: resizing pointer by alias 0xf5ad4a04

BUG: warning at
/usr/src/linux-work8/arch/i386/kernel/smp.c:547/smp_call_function()
[<c0106cdc>] show_trace+0xd/0xf
[<c0106dae>] dump_stack+0x17/0x19
[<c0116ac4>] smp_call_function+0x52/0xf9
[<c0116bad>] smp_send_stop+0x16/0x20
[<c0170c35>] panic+0x50/0xdf
[<c016e9a0>] __kmalloc+0xf6/0x10a
[<c019a4bf>] load_elf_binary+0x5b/0xb46
[<c017d77a>] search_binary_handler+0xfc/0x2d4
[<c017daa5>] do_execve+0x153/0x1f0
[<c0104d5f>] sys_execve+0x29/0x7a
[<c0105f11>] sysenter_past_esp+0x5b/0x79

http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/kml-config2

Quoted 8 lines

Thanks.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#21 lore raw
Quoted 1 line · Michal Piotrowski

Something doesn't work. It appears while udev start.

Could you try the attached patch? It has some improvements from the yesterday's one and also prints extra information when that error happens.

Thanks.

Signature · 1 line
Catalin
mm/memleak.c +386 -166
index 514280f..ffa4bdd 100644
@@ -16,6 +16,18 @@
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * Notes on locking: kmemleak needs to allocate/free memory for its
+ * own data structures - the memleak_pointer and the pointer and
+ * aliases radix trees. The memleak_free hook can be called from
+ * mm/slab.c with the list_lock held (i.e. when releasing of off-slab
+ * management structures) and it will ackquire the memleak_lock. To
+ * avoid deadlocks caused by locking dependency, the list_lock must
+ * not be acquired while memleak_lock is held. This is ensured by not
+ * allocating any memory while memleak_lock is held. The radix trees
+ * have to be preloaded before acquiring the lock so that the
+ * insertion won't allocate memory.
  */
 
 /* #define DEBUG */
@@ -37,6 +49,7 @@ #include <linux/seq_file.h>
 #include <linux/vmalloc.h>
 #include <linux/mutex.h>
 #include <linux/cpumask.h>
+#include <linux/spinlock.h>
 
 #include <asm/bitops.h>
 #include <asm/sections.h>
@@ -128,6 +141,7 @@ static inline int color_black(const stru
 
 /* Tree storing the pointer aliases indexed by size */
 static RADIX_TREE(alias_tree, GFP_ATOMIC);
+static DEFINE_RWLOCK(alias_tree_lock);
 /* Tree storing all the possible pointers, indexed by the pointer value */
 static RADIX_TREE(pointer_tree, GFP_ATOMIC);
 /* The list of all allocated pointers */
@@ -136,8 +150,8 @@ static LIST_HEAD(pointer_list);
 static LIST_HEAD(gray_list);
 
 static struct kmem_cache *pointer_cache;
-/* Used to avoid recursive call via the kmalloc/kfree functions */
-static spinlock_t memleak_lock = SPIN_LOCK_UNLOCKED;
+/* The main lock for protecting the pointer lists and radix trees */
+static DEFINE_SPINLOCK(memleak_lock);
 static cpumask_t memleak_cpu_mask = CPU_MASK_NONE;
 static DEFINE_MUTEX(memleak_mutex);
 static atomic_t memleak_initialized = ATOMIC_INIT(0);
@@ -171,7 +185,7 @@ static inline void dump_symbol_name(unsi
 }
 #endif
 
-#ifdef DEBUG
+#if 1
 static inline void dump_pointer_internals(struct memleak_pointer *pointer)
 {
 	struct memleak_alias *alias;
@@ -181,9 +195,12 @@ static inline void dump_pointer_internal
 	printk(KERN_NOTICE "  ref_count = %d\n", pointer->ref_count);
 	printk(KERN_NOTICE "  count = %d\n", pointer->count);
 	printk(KERN_NOTICE "  aliases:\n");
-	if (pointer->alias_list)
-		hlist_for_each_entry(alias, elem, pointer->alias_list, node)
+	if (pointer->alias_list) {
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, pointer->alias_list, node)
 			printk(KERN_NOTICE "    0x%lx\n", alias->offset);
+		rcu_read_unlock();
+	}
 }
 #else
 static inline void dump_pointer_internals(struct memleak_pointer *pointer)
@@ -213,45 +230,81 @@ static int insert_alias(unsigned long ty
 {
 	int ret = 0;
 	struct hlist_head *alias_list;
+	struct hlist_node *elem;
 	struct memleak_alias *alias;
+	unsigned long flags;
+	unsigned int cpu_id;
 
-	if (type_id == 0 || offset == 0 || offset >= ml_sizeof(type_id)) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (type_id == 0 || offset == 0 || offset >= ml_sizeof(type_id))
+		return -EINVAL;
+
+	/* no recursive re-entrance on the same CPU. We do not track
+	 * the alias tree allocations */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
+	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
+		BUG();
 
 	offset &= ~(BYTES_PER_WORD - 1);
 
+	read_lock(&alias_tree_lock);
 	alias_list = radix_tree_lookup(&alias_tree, type_id);
-	if (alias_list) {
-		struct hlist_node *elem;
+	read_unlock(&alias_tree_lock);
 
-		hlist_for_each_entry(alias, elem, alias_list, node) {
-			if (alias->offset == offset) {
-				ret = -EEXIST;
-				goto out;
-			}
-		}
-	} else {
+	if (!alias_list) {
+		/* no alias list for this type id. Allocate list_head
+		 * and preload the radix tree */
 		alias_list = kmalloc(sizeof(*alias_list), GFP_ATOMIC);
 		if (!alias_list)
-			panic("kmemleak: cannot allocate initial memory\n");
+			panic("kmemleak: cannot allocate alias_list\n");
 		INIT_HLIST_HEAD(alias_list);
 
-		ret = radix_tree_insert(&alias_tree, type_id, alias_list);
+		ret = radix_tree_preload(GFP_ATOMIC);
 		if (ret)
+			panic("kmemleak: cannot preload the aliases radix tree: %d\n", ret);
+
+		write_lock(&alias_tree_lock);
+		ret = radix_tree_insert(&alias_tree, type_id, alias_list);
+		write_unlock(&alias_tree_lock);
+
+		radix_tree_preload_end();
+
+		if (ret == -EEXIST) {
+			/* allocated in the meantime on a different CPU */
+			read_lock(&alias_tree_lock);
+			alias_list = radix_tree_lookup(&alias_tree, type_id);
+			read_unlock(&alias_tree_lock);
+		} else if (ret)
 			panic("kmemleak: cannot insert into the aliases radix tree: %d\n", ret);
 	}
 
+	BUG_ON(!alias_list);
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(alias, elem, alias_list, node) {
+		if (alias->offset == offset) {
+			rcu_read_unlock();
+			ret = -EEXIST;
+			goto out;
+		}
+	}
+	rcu_read_unlock();
+
 	alias = kmalloc(sizeof(*alias), GFP_ATOMIC);
 	if (!alias)
 		panic("kmemleak: cannot allocate initial memory\n");
 	INIT_HLIST_NODE(&alias->node);
 	alias->offset = offset;
 
-	hlist_add_head(&alias->node, alias_list);
+	write_lock(&alias_tree_lock);
+	hlist_add_head_rcu(&alias->node, alias_list);
+	write_unlock(&alias_tree_lock);
 
  out:
+	cpu_clear(cpu_id, memleak_cpu_mask);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
+
 	return ret;
 }
 
@@ -261,18 +314,12 @@ void memleak_insert_aliases(struct memle
 {
 	struct memleak_offset *ml_off;
 	int i = 0;
+#ifdef CONFIG_DEBUG_MEMLEAK_SECONDARY_ALIASES
 	unsigned long flags;
-	unsigned int cpu_id;
+#endif
 
 	pr_debug("%s(0x%p, 0x%p)\n", __FUNCTION__, ml_off_start, ml_off_end);
 
-	spin_lock_irqsave(&memleak_lock, flags);
-
-	/* do not track the kmemleak allocated pointers */
-	cpu_id = smp_processor_id();
-	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
-		BUG();
-
 	/* primary aliases - container_of(member) */
 	for (ml_off = ml_off_start; ml_off < ml_off_end; ml_off++)
 		if (!insert_alias(ml_off->type_id, ml_off->offset))
@@ -286,124 +333,213 @@ #ifdef CONFIG_DEBUG_MEMLEAK_SECONDARY_AL
 		struct memleak_alias *alias;
 		struct hlist_node *elem;
 
+		/* with imprecise type identification, it the member
+		 * id is the same as the outer structure id, just
+		 * ignore as any potential aliases are already in the
+		 * tree */
+		if (ml_off->member_type_id == ml_off->type_id)
+			continue;
+
+		read_lock_irqsave(&alias_tree_lock, flags);
 		alias_list = radix_tree_lookup(&alias_tree, ml_off->member_type_id);
+		read_unlock_irqrestore(&alias_tree_lock, flags);
 		if (!alias_list)
 			continue;
 
-		hlist_for_each_entry(alias, elem, alias_list, node)
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, alias_list, node)
 			if (!insert_alias(ml_off->type_id, ml_off->offset + alias->offset))
 				i++;
+		rcu_read_unlock();
 	}
 	pr_debug("kmemleak: found %d alias(es)\n", i);
 #endif
-
-	cpu_clear(cpu_id, memleak_cpu_mask);
-	spin_unlock_irqrestore(&memleak_lock, flags);
 }
 EXPORT_SYMBOL_GPL(memleak_insert_aliases);
 
+/* called with memleak_lock held and interrupts disabled */
 static inline struct memleak_pointer *get_cached_pointer(unsigned long ptr)
 {
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
 	if (!last_pointer || ptr != last_pointer->pointer)
 		last_pointer = radix_tree_lookup(&pointer_tree, ptr);
 	return last_pointer;
 }
 
-static void create_pointer_aliases(struct memleak_pointer *pointer)
+/* no need for atomic operations since memleak_lock is held and
+ * interrupts disabled */
+static inline void get_pointer(struct memleak_pointer *pointer)
 {
-	struct memleak_alias *alias;
-	struct hlist_node *elem;
-	unsigned long ptr = pointer->pointer;
-	int err;
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 
-	BUG_ON(pointer->flags & POINTER_ALIASES);
+	pointer->use_count++;
+}
 
-	if (pointer->offset) {
-		err = radix_tree_insert(&pointer_tree, ptr + pointer->offset, pointer);
-		if (err) {
-			dump_stack();
-			panic("kmemleak: cannot insert offset into the pointer radix tree: %d\n", err);
-		}
-	}
+/* called with memleak_lock held for pointer_list modification and
+ * memleak_cpu_mask set to avoid entering memleak_free (and deadlock)
+ */
+static void __put_pointer(struct memleak_pointer *pointer)
+{
+	struct hlist_node *elem, *tmp;
+	struct memleak_scan_area *area;
 
-	pointer->alias_list = radix_tree_lookup(&alias_tree, pointer->type_id);
-	if (pointer->alias_list) {
-		hlist_for_each_entry(alias, elem, pointer->alias_list, node) {
-			err = radix_tree_insert(&pointer_tree, ptr
-						+ pointer->offset + alias->offset,
-						pointer);
-			if (err) {
-				dump_stack();
-				panic("kmemleak: cannot insert alias into the pointer radix tree: %d\n", err);
-			}
-		}
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+	if (--pointer->use_count > 0)
+		return;
+
+	/* free the scanning areas */
+	hlist_for_each_entry_safe(area, elem, tmp, &pointer->area_list, node) {
+		hlist_del(elem);
+		kfree(area);
 	}
 
-	pointer->flags |= POINTER_ALIASES;
+	list_del(&pointer->pointer_list);
+	kmem_cache_free(pointer_cache, pointer);
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+}
+
+static void put_pointer(struct memleak_pointer *pointer)
+{
+	unsigned long flags;
+	unsigned int cpu_id;
+
+	/* no recursive re-entrance on the same CPU */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
+	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
+		BUG();
+
+	spin_lock(&memleak_lock);
+	__put_pointer(pointer);
+	spin_unlock(&memleak_lock);
+
+	cpu_clear(cpu_id, memleak_cpu_mask);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
 }
 
+/* called with memleak_lock held and interrupts disabled */
 static void delete_pointer_aliases(struct memleak_pointer *pointer)
 {
 	struct memleak_alias *alias;
 	struct hlist_node *elem;
-	unsigned long ptr = pointer->pointer;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 
 	BUG_ON(!(pointer->flags & POINTER_ALIASES));
 
 	if (pointer->offset)
-		radix_tree_delete(&pointer_tree, ptr + pointer->offset);
+		radix_tree_delete(&pointer_tree, pointer->pointer
+				  + pointer->offset);
 
 	if (pointer->alias_list) {
-		hlist_for_each_entry(alias, elem, pointer->alias_list, node)
-			radix_tree_delete(&pointer_tree,
-					  ptr + pointer->offset + alias->offset);
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, pointer->alias_list, node)
+			radix_tree_delete(&pointer_tree, pointer->pointer
+					  + pointer->offset + alias->offset);
+		rcu_read_unlock();
 		pointer->alias_list = NULL;
 	}
 
 	pointer->flags &= ~POINTER_ALIASES;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 }
 
-/* no need for atomic operations since memleak_lock is held anyway */
-static inline void get_pointer(struct memleak_pointer *pointer)
+/* called with memleak_lock held but may be released */
+static inline int insert_pointer_alias(struct memleak_pointer *pointer,
+				       unsigned long alias)
 {
-	pointer->use_count++;
+	int ret = 0;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+	spin_unlock(&memleak_lock);
+	ret = radix_tree_preload(GFP_ATOMIC);
+	spin_lock(&memleak_lock);
+	if (ret) {
+		printk(KERN_WARNING "kmemleak: cannot preload the pointer radix tree: %d\n", ret);
+		goto out;
+	}
+
+	ret = radix_tree_insert(&pointer_tree, alias, pointer);
+	if (ret) {
+		dump_stack();
+		panic("kmemleak: cannot insert alias into the pointer radix tree: %d\n", ret);
+	}
+
+	radix_tree_preload_end();
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+ out:
+	return ret;
 }
 
-/* called with memleak_lock held for pointer_list modification and
- * memleak_cpu_mask set to avoid entering memleak_free (and deadlock)
+/* called with memleak_lock held but it may be released in
+ * insert_pointer_alias(). The pointer structure is guaranteed not to
+ * be freed before this function returns (get/put_pointer)
  */
-static void __put_pointer(struct memleak_pointer *pointer)
+static inline void create_pointer_aliases(struct memleak_pointer *pointer)
 {
-	struct hlist_node *elem, *tmp;
-	struct memleak_scan_area *area;
+	struct memleak_alias *alias;
+	struct hlist_node *elem;
+	int err = 0;
 
-	if (--pointer->use_count > 0)
-		return;
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 
-	/* free the scanning areas */
-	hlist_for_each_entry_safe(area, elem, tmp, &pointer->area_list, node) {
-		hlist_del(elem);
-		kfree(area);
+	BUG_ON(pointer->flags & POINTER_ALIASES);
+
+	get_pointer(pointer);
+
+	if (pointer->offset) {
+		err = insert_pointer_alias(pointer, pointer->pointer
+					   + pointer->offset);
+		if (err)
+			goto out;
 	}
 
-	list_del(&pointer->pointer_list);
-	kmem_cache_free(pointer_cache, pointer);
-}
+	read_lock(&alias_tree_lock);
+	pointer->alias_list = radix_tree_lookup(&alias_tree, pointer->type_id);
+	read_unlock(&alias_tree_lock);
 
-static void put_pointer(struct memleak_pointer *pointer)
-{
-	unsigned long flags;
-	unsigned int cpu_id;
+	if (pointer->alias_list) {
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, pointer->alias_list, node) {
+			err = insert_pointer_alias(pointer, pointer->pointer
+						   + pointer->offset + alias->offset);
+			if (err) {
+				rcu_read_unlock();
+				goto out;
+			}
+		}
+		rcu_read_unlock();
+	}
 
-	spin_lock_irqsave(&memleak_lock, flags);
-	cpu_id = smp_processor_id();
-	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
-		BUG();
+ out:
+	pointer->flags |= POINTER_ALIASES;
+
+	/* check whether the memory block might have been freed during
+	 * the unlocked periods or an error occured */
+	if (err || !(pointer->flags & POINTER_ALLOCATED))
+		delete_pointer_aliases(pointer);
 
 	__put_pointer(pointer);
 
-	cpu_clear(cpu_id, memleak_cpu_mask);
-	spin_unlock_irqrestore(&memleak_lock, flags);
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 }
 
 /* Insert a pointer and its aliases into the pointer radix tree */
@@ -416,12 +552,12 @@ #ifdef CONFIG_FRAME_POINTER
 	void *frame;
 #endif
 
+	BUG_ON(!irqs_disabled());
+
 	pointer = kmem_cache_alloc(pointer_cache, SLAB_ATOMIC);
 	if (!pointer)
 		panic("kmemleak: cannot allocate a memleak_pointer structure\n");
 
-	last_pointer = pointer;
-
 	INIT_LIST_HEAD(&pointer->pointer_list);
 	INIT_LIST_HEAD(&pointer->gray_list);
 	INIT_HLIST_HEAD(&pointer->area_list);
@@ -455,6 +591,16 @@ #else
 	pointer->trace[0] = (unsigned long)__builtin_return_address(0);
 #endif
 
+	/* allocate here to avoid acquiring the list_lock in mm/slab.c
+	 * while memleak_lock is held */
+	err = radix_tree_preload(GFP_ATOMIC);
+	if (err) {
+		dump_stack();
+		panic("kmemleak: cannot preload the pointer radix tree: %d\n", err);
+	}
+
+	spin_lock(&memleak_lock);
+
 	err = radix_tree_insert(&pointer_tree, ptr, pointer);
 	if (err) {
 		dump_stack();
@@ -469,6 +615,12 @@ #endif
 
 	list_add_tail(&pointer->pointer_list, &pointer_list);
 	get_pointer(pointer);
+	last_pointer = pointer;
+
+	spin_unlock(&memleak_lock);
+	radix_tree_preload_end();
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Remove a pointer and its aliases from the pointer radix tree */
@@ -476,11 +628,15 @@ static inline void delete_pointer(unsign
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = radix_tree_delete(&pointer_tree, ptr);
 	if (!pointer) {
 		dump_stack();
 		printk(KERN_WARNING "kmemleak: freeing unknown pointer value 0x%08lx\n", ptr);
-		return;
+		goto out;
 	}
 	if (pointer->pointer != ptr) {
 		dump_stack();
@@ -490,6 +646,7 @@ static inline void delete_pointer(unsign
 
 	BUG_ON(!(pointer->flags & POINTER_ALLOCATED));
 
+	/* deleting the cached pointer */
 	if (last_pointer && ptr == last_pointer->pointer)
 		last_pointer = NULL;
 
@@ -506,6 +663,11 @@ #endif
 	pointer->flags &= ~POINTER_ALLOCATED;
 	pointer->pointer = 0;
 	__put_pointer(pointer);
+
+ out:
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Re-create the pointer aliases according to the new size/offset
@@ -515,6 +677,10 @@ static inline void unpad_pointer(unsigne
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -532,13 +698,18 @@ static inline void unpad_pointer(unsigne
 	}
 
 	if (offset == pointer->offset && size == pointer->size)
-		return;
+		goto out;
 
 	if (pointer->flags & POINTER_ALIASES)
 		delete_pointer_aliases(pointer);
 
 	pointer->offset = offset;
 	pointer->size = size;
+
+ out:
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Make a pointer permanently gray (false positive) */
@@ -546,6 +717,10 @@ static inline void make_gray_pointer(uns
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -558,6 +733,10 @@ static inline void make_gray_pointer(uns
 	}
 
 	pointer->ref_count = 0;
+
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Mark the pointer as black */
@@ -565,6 +744,10 @@ static inline void make_black_pointer(un
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -577,6 +760,10 @@ static inline void make_black_pointer(un
 	}
 
 	pointer->ref_count = -1;
+
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Add a scanning area to the pointer */
@@ -585,6 +772,18 @@ static inline void add_scan_area(unsigne
 	struct memleak_pointer *pointer;
 	struct memleak_scan_area *area;
 
+	BUG_ON(!irqs_disabled());
+
+	area = kmalloc(sizeof(*area), GFP_ATOMIC);
+	if (!area)
+		panic("kmemleak: cannot allocate a scan area\n");
+
+	INIT_HLIST_NODE(&area->node);
+	area->offset = offset;
+	area->length = length;
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -601,15 +800,11 @@ static inline void add_scan_area(unsigne
 		panic("kmemleak: scan area larger than block 0x%08lx\n", ptr);
 	}
 
-	area = kmalloc(sizeof(*area), GFP_ATOMIC);
-	if (!area)
-		panic("kmemleak: cannot allocate a scan area\n");
+	hlist_add_head(&area->node, &pointer->area_list);
 
-	INIT_HLIST_NODE(&area->node);
-	area->offset = offset;
-	area->length = length;
+	spin_unlock(&memleak_lock);
 
-	hlist_add_head(&area->node, &pointer->area_list);
+	BUG_ON(!irqs_disabled());
 }
 
 /* Re-create the pointer aliases according to the new type id */
@@ -617,6 +812,10 @@ static inline void change_type_id(unsign
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -634,12 +833,17 @@ static inline void change_type_id(unsign
 	}
 
 	if (!type_id || type_id == pointer->type_id)
-		return;
+		goto out;
 
 	if (pointer->flags & POINTER_ALIASES)
 		delete_pointer_aliases(pointer);
 
 	pointer->type_id = type_id;
+
+ out:
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Allocation function hook */
@@ -671,21 +875,20 @@ void memleak_alloc(const void *ptr, size
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_ALLOC;
-		pointer->pointer = ptr;
-		pointer->size = size;
-		pointer->ref_count = ref_count;
+			pointer->type = MEMLEAK_ALLOC;
+			pointer->pointer = ptr;
+			pointer->size = size;
+			pointer->ref_count = ref_count;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	create_pointer((unsigned long)ptr, size, ref_count);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -718,19 +921,18 @@ void memleak_free(const void *ptr)
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_FREE;
-		pointer->pointer = ptr;
+			pointer->type = MEMLEAK_FREE;
+			pointer->pointer = ptr;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	delete_pointer((unsigned long)ptr);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -764,21 +966,20 @@ void memleak_padding(const void *ptr, un
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_PADDING;
-		pointer->pointer = ptr;
-		pointer->offset = offset;
-		pointer->size = size;
+			pointer->type = MEMLEAK_PADDING;
+			pointer->pointer = ptr;
+			pointer->offset = offset;
+			pointer->size = size;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	unpad_pointer((unsigned long)ptr, offset, size);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -811,19 +1012,18 @@ void memleak_not_leak(const void *ptr)
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_NOT_LEAK;
-		pointer->pointer = ptr;
+			pointer->type = MEMLEAK_NOT_LEAK;
+			pointer->pointer = ptr;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	make_gray_pointer((unsigned long)ptr);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -856,19 +1056,18 @@ void memleak_ignore(const void *ptr)
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_IGNORE;
-		pointer->pointer = ptr;
+			pointer->type = MEMLEAK_IGNORE;
+			pointer->pointer = ptr;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	make_black_pointer((unsigned long)ptr);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -901,21 +1100,20 @@ void memleak_scan_area(const void *ptr, 
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_SCAN_AREA;
-		pointer->pointer = ptr;
-		pointer->offset = offset;
-		pointer->size = length;
+			pointer->type = MEMLEAK_SCAN_AREA;
+			pointer->pointer = ptr;
+			pointer->offset = offset;
+			pointer->size = length;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	add_scan_area((unsigned long)ptr, offset, length);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -948,20 +1146,19 @@ void memleak_typeid_raw(const void *ptr,
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_TYPEID;
-		pointer->pointer = ptr;
-		pointer->type_id = type_id;
+			pointer->type = MEMLEAK_TYPEID;
+			pointer->pointer = ptr;
+			pointer->type_id = type_id;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	change_type_id((unsigned long)ptr, type_id);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -973,7 +1170,7 @@ EXPORT_SYMBOL(memleak_typeid_raw);
 
 /* Scan a block of memory (exclusive range) for pointers and move
  * those found to the gray list. This function is called with
- * memleak_lock held
+ * memleak_lock held and interrupts disabled
  */
 static void __scan_block(void *_start, void *_end)
 {
@@ -982,6 +1179,9 @@ static void __scan_block(void *_start, v
 						      BYTES_PER_WORD);
 	unsigned long *end = _end;
 
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
 	for (ptr = start; ptr < end; ptr++) {
 		struct memleak_pointer *pointer =
 			radix_tree_lookup(&pointer_tree,
@@ -998,6 +1198,9 @@ static void __scan_block(void *_start, v
 			list_add_tail_rcu(&pointer->gray_list, &gray_list);
 		}
 	}
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 }
 
 static void scan_block(void *start, void *end)
@@ -1027,7 +1230,7 @@ static inline void scan_pointer(struct m
 	spin_lock_irqsave(&memleak_lock, flags);
 
 	/* freed pointer */
-	if (!pointer->pointer)
+	if (!pointer->flags & POINTER_ALLOCATED)
 		goto out;
 
 	if (hlist_empty(&pointer->area_list))
@@ -1056,14 +1259,21 @@ #endif
 	unsigned int cpu_id;
 
 	/* initialize pointers (make them white) and build the initial
-	 * gray list. Note that create_pointer_aliases might allocate
-	 * memory */
-	spin_lock_irqsave(&memleak_lock, flags);
-	cpu_id = smp_processor_id();
+	 * gray list. memleak_cpu_mask is set as we don't track the
+	 * kmemleak allocations */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
 	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
 		BUG();
 
-	list_for_each_entry(pointer, &pointer_list, pointer_list) {
+	/* use the _rcu variant since pointer_list can change */
+	rcu_read_lock();
+	/* memleak_lock needed to ensure that the pointer structure is
+	 * not freed on a different CPU (create_pointer_aliases may
+	 * release the lock) */
+	spin_lock(&memleak_lock);
+
+	list_for_each_entry_rcu(pointer, &pointer_list, pointer_list) {
 		/* lazy insertion of the pointer aliases into the radix tree */
 		if ((pointer->flags & POINTER_ALLOCATED)
 		    && !(pointer->flags & POINTER_ALIASES))
@@ -1071,13 +1281,19 @@ #endif
 
 		pointer->count = 0;
 		if (color_gray(pointer)) {
+			/* the pointer will be put back once it is
+			 * scanned via the gray_list */
 			get_pointer(pointer);
 			list_add_tail(&pointer->gray_list, &gray_list);
 		}
 	}
 
+	spin_unlock(&memleak_lock);
+	rcu_read_unlock();
+
 	cpu_clear(cpu_id, memleak_cpu_mask);
-	spin_unlock_irqrestore(&memleak_lock, flags);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
 
 	/* data/bss scanning */
 	scan_block(_sdata, _edata);
@@ -1262,6 +1478,10 @@ void __init memleak_init(void)
 
 	atomic_set(&memleak_initialized, 1);
 
+	if (preinit_pos >= PREINIT_POINTERS)
+		panic("kmemleak: preinit pointers buffer overflow: %d\n",
+		      preinit_pos);
+
 	/* execute the buffered memleak actions */
 	pr_debug("kmemleak: %d preinit actions\n", preinit_pos);
 	for (i = 0; i < preinit_pos; i++) {
#22 lore raw
Quoted 6 lines · Catalin Marinas
Quoted 1 line · Michal Piotrowski

Something doesn't work. It appears while udev start.

Could you try the attached patch? It has some improvements from the yesterday's one and also prints extra information when that error happens.

Problem fixed, thank. I'll do some tests.

Quoted 8 lines

Thanks.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#23 lore raw
Quoted 9 lines · Michal Piotrowski
Quoted 6 lines · Catalin Marinas
Quoted 1 line · Michal Piotrowski

Something doesn't work. It appears while udev start.

Could you try the attached patch? It has some improvements from the yesterday's one and also prints extra information when that error happens.

Problem fixed, thank. I'll do some tests.

Lockdep still detects this bug

=======================================================
[ INFO: possible circular locking dependency detected ]
-------------------------------------------------------
events/0/8 is trying to acquire lock:
(memleak_lock){++..}, at: [<c0176a11>] memleak_free+0x9e/0x16f

but task is already holding lock:
(&parent->list_lock){++..}, at: [<c0174f29>] drain_array+0x49/0xad

which lock already depends on the new lock.
etc.

Quoted 6 lines
Quoted 6 lines

Thanks.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#24 lore raw
Quoted 8 lines · Michal Piotrowski

Lockdep still detects this bug

=======================================================
[ INFO: possible circular locking dependency detected ]
-------------------------------------------------------
events/0/8 is trying to acquire lock:
(memleak_lock){++..}, at: [<c0176a11>] memleak_free+0x9e/0x16f

Quoted 5 lines

but task is already holding lock:
(&parent->list_lock){++..}, at: [<c0174f29>] drain_array+0x49/0xad

which lock already depends on the new lock.
etc.

Do you have the complete dmesg output? There should be a different path which I missed (I didn't get this yestarday but I haven't tried with the latest patch I sent to you today).

Signature · 1 line
Catalin
#25 lore raw
Quoted 12 lines · Catalin Marinas
Quoted 11 lines · Michal Piotrowski

Lockdep still detects this bug

=======================================================
[ INFO: possible circular locking dependency detected ]
-------------------------------------------------------
events/0/8 is trying to acquire lock:
(memleak_lock){++..}, at: [<c0176a11>] memleak_free+0x9e/0x16f

but task is already holding lock:
(&parent->list_lock){++..}, at: [<c0174f29>] drain_array+0x49/0xad

Quoted 6 lines
Quoted 2 lines

which lock already depends on the new lock.
etc.

Do you have the complete dmesg output? There should be a different path which I missed (I didn't get this yestarday but I haven't tried with the latest patch I sent to you today).

Here is dmesg output
http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/kml-dmesg2

Quoted 4 lines

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#26 lore raw
Quoted 10 lines · Michal Piotrowski
Quoted 6 lines · Catalin Marinas
Quoted 1 line · Michal Piotrowski

Lockdep still detects this bug

Do you have the complete dmesg output? There should be a different path which I missed (I didn't get this yestarday but I haven't tried with the latest patch I sent to you today).

Here is dmesg output
http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/kml-dmesg2

Thanks. It is strange - I was under the impression that calling radix_tree_preload() outside the memleak_lock holding would actually prevent radix_tree_insert() from allocating a node (and therefore call kmem_cache_alloc and acquire the list_lock). I'll look at the radix_tree code this weekend. I'm looking at implementing some kind of RCU mechanism in kmemleak to avoid future problems (might need changed to radix_tree as well).

Signature · 1 line
Catalin
#27 lore raw

On 18/08/06, Catalin Marinas <[email protected]> wrote:
[snip]

Quoted 1 line

to radix_tree as well).

cat /sys/kernel/debug/memleak + LTP + patching repo = oops?

BUG: unable to handle kernel paging request at virtual address 6b6b6b6b
printing eip:
c0177468
*pde = 00000000
Oops: 0000 [#1]
PREEMPT SMP
Modules linked in: jfs nls_base xfs reiserfs ext2 loop ipv6 w83627hf

hwmon_vid hwmon i2c_isa af_packet ip_conntrack_netbios_
ns ipt_REJECT xt_state ip_conntrack nfnetlink xt_tcpudp iptable_filter ip_tables x_tables cpufreq_userspace p4_clockmod spee dstep_lib binfmt_misc thermal processor fan container evdev snd_intel8x0 snd_ac97_codec snd_ac97_bus snd_seq_dummy snd_seq_o ss snd_seq_midi_event snd_seq snd_seq_device snd_pcm_oss sk98lin snd_mixer_oss snd_pcm skge snd_timer snd i2c_i801 intel_agp soundcore snd_page_alloc ide_cd agpgart cdrom rtc unix

CPU:    0
EIP:    0060:[<c0177468>]    Not tainted VLI
EFLAGS: 00010097   (2.6.18-rc4 #174)

EIP is at memleak_scan+0x58/0x54e
eax: 00000000 ebx: 00000000 ecx: 00000000 edx: 6b6b6b6b
esi: 6b6b6b67 edi: 00000000 ebp: de1aaf28 esp: de1aaf10
ds: 007b es: 007b ss: 0068
Process cat (pid: 6409, ti=de1aa000 task=e23ee1b0 task.ti=de1aa000)

Stack: 00000000 00000282 00000000 00000000 00000000 c035e220 de1aaf3c c017797e
      00001000 e1ae03d8 c035e220 de1aaf74 c0196654 00000000 00000000 00001000
      0804f000 f08e0c9c c0120ac5 00000001 00000000 00000000 00001000 f08e0c9c
Call Trace:
[<c017797e>] memleak_seq_start+0x20/0xaa
[<c0196654>] seq_read+0xc8/0x246
[<c017988b>] vfs_read+0xa9/0x151
[<c0179bab>] sys_read+0x3b/0x60
[<c0105f3d>] sysenter_past_esp+0x56/0x8d
[<b7f7c410>] 0xb7f7c410
[<c0106fe3>] show_stack_log_lvl+0x87/0x8f
[<c0107148>] show_registers+0x11a/0x183
[<c0107334>] die+0x10f/0x1da
[<c0120c69>] do_page_fault+0x3ce/0x4a4
[<c0106b41>] error_code+0x39/0x40
[<c017797e>] memleak_seq_start+0x20/0xaa
[<c0196654>] seq_read+0xc8/0x246
[<c017988b>] vfs_read+0xa9/0x151
[<c0179bab>] sys_read+0x3b/0x60
[<c0105f3d>] sysenter_past_esp+0x56/0x8d
Code: 19 c0 85 c0 74 08 0f 0b f3 04 fe bd 31 c0 b8 01 00 00 00 e8 cb

cd fa ff b8 a0 e1 35 c0 e8 e8 32 18 00 8b 15 8c e1 35 c
0 8d 72 fc <8b> 46 04 0f 18 00 90 81 fa 8c e1 35 c0 0f 84 0a 03 00 00 8b 06
EIP: [<c0177468>] memleak_scan+0x58/0x54e SS:ESP 0068:de1aaf10 <6>note: cat[6409] exited with preempt_count 3

BUG: sleeping function called from invalid context at
/usr/src/linux-work8/mm/slab.c:2904
in_atomic():1, irqs_disabled():1
[<c0106e59>] show_trace_log_lvl+0x58/0x14c
[<c0106f5a>] show_trace+0xd/0xf
[<c010702c>] dump_stack+0x17/0x19
[<c0126dc6>] __might_sleep+0x92/0x9a
[<c01745dc>] kmem_cache_zalloc+0x28/0xf5
[<c0159eee>] taskstats_exit_alloc+0x30/0x6e
[<c012c844>] do_exit+0x10e/0x46a
[<c01073f7>] die+0x1d2/0x1da
[<c0120c69>] do_page_fault+0x3ce/0x4a4
[<c0106b41>] error_code+0x39/0x40
[<c0177468>] memleak_scan+0x58/0x54e
[<c017797e>] memleak_seq_start+0x20/0xaa
[<c0196654>] seq_read+0xc8/0x246
[<c017988b>] vfs_read+0xa9/0x151
[<c0179bab>] sys_read+0x3b/0x60
[<c0105f3d>] sysenter_past_esp+0x56/0x8d
[<b7f7c410>] 0xb7f7c410
[<c0106f5a>] show_trace+0xd/0xf
[<c010702c>] dump_stack+0x17/0x19
[<c0126dc6>] __might_sleep+0x92/0x9a
[<c01745dc>] kmem_cache_zalloc+0x28/0xf5
[<c0159eee>] taskstats_exit_alloc+0x30/0x6e
[<c012c844>] do_exit+0x10e/0x46a
[<c01073f7>] die+0x1d2/0x1da
[<c0120c69>] do_page_fault+0x3ce/0x4a4
[<c0106b41>] error_code+0x39/0x40
[<c017797e>] memleak_seq_start+0x20/0xaa
[<c0196654>] seq_read+0xc8/0x246
[<c017988b>] vfs_read+0xa9/0x151
[<c0179bab>] sys_read+0x3b/0x60
[<c0105f3d>] sysenter_past_esp+0x56/0x8d
BUG: spinlock lockup on CPU#1, growfiles/6257, c035e1a0
[<c0106e59>] show_trace_log_lvl+0x58/0x14c
[<c0106f5a>] show_trace+0xd/0xf
[<c010702c>] dump_stack+0x17/0x19
[<c01fedbd>] __spin_lock_debug+0x86/0x93
[<c01fee28>] _raw_spin_lock+0x5e/0x73
[<c02fa771>] _spin_lock+0x2a/0x2f
[<c0176877>] memleak_alloc+0x17b/0x277
[<c01745a6>] kmem_cache_alloc+0xc0/0xce
[<c018bc1a>] locks_alloc_lock+0x12/0x14
[<c018c7ce>] __posix_lock_file_conf+0x64/0x439
[<c018cbb3>] posix_lock_file+0x10/0x12
[<c018d8c4>] fcntl_setlk+0x114/0x20e
[<c0189a94>] do_fcntl+0xf0/0x140
[<c0189bad>] sys_fcntl64+0x6e/0x82
[<c0105f3d>] sysenter_past_esp+0x56/0x8d
[<b7f10410>] 0xb7f10410
[<c0106f5a>] show_trace+0xd/0xf
[<c010702c>] dump_stack+0x17/0x19
[<c01fedbd>] __spin_lock_debug+0x86/0x93
[<c01fee28>] _raw_spin_lock+0x5e/0x73
[<c02fa771>] _spin_lock+0x2a/0x2f
[<c0176877>] memleak_alloc+0x17b/0x277
[<c01745a6>] kmem_cache_alloc+0xc0/0xce
[<c018bc1a>] locks_alloc_lock+0x12/0x14
[<c018c7ce>] __posix_lock_file_conf+0x64/0x439
[<c018cbb3>] posix_lock_file+0x10/0x12
[<c018d8c4>] fcntl_setlk+0x114/0x20e
[<c0189a94>] do_fcntl+0xf0/0x140
[<c0189bad>] sys_fcntl64+0x6e/0x82
[<c0105f3d>] sysenter_past_esp+0x56/0x8d

A few minutes later system has died. list *0xc0177468 in gdb shows nothing.

(gdb) l *0xc0177468
0xc0177468 is in memleak_scan (include2/asm/processor.h:1276). Line number 1271 out of range; include2/asm/processor.h has 746 lines.

(gdb) l *memleak_scan+0x58/0x54e
0xc0177410 is in memleak_scan (/usr/src/linux-work8/mm/memleak.c:1252).
1247 spin_unlock_irqrestore(&memleak_lock, flags);
1248 }
1249

1250    /* Scan the memory and print the orphan pointers */
1251    static void memleak_scan(void)
1252    {

1253 unsigned long flags;
1254 struct memleak_pointer *pointer, *tmp;
1255 #ifdef CONFIG_DEBUG_MEMLEAK_TASK_STACKS
1256 struct task_struct *task;

http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/kml-dmesg3

Quoted 4 lines

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#28 lore raw
Quoted 3 lines · Michal Piotrowski

cat /sys/kernel/debug/memleak + LTP + patching repo = oops?

BUG: unable to handle kernel paging request at virtual address 6b6b6b6b

I got this as well today on an 4-CPU ARM platform. I changed some list traversal to the RCU variants but forgot to change the list_del/add. I attach another patch which fixes this (haven't tested it extensively yet).

Thanks.

Signature · 1 line
Catalin
mm/memleak.c +408 -164
index 514280f..7d6c1f3 100644
@@ -16,6 +16,18 @@
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ *
+ * Notes on locking: kmemleak needs to allocate/free memory for its
+ * own data structures - the memleak_pointer and the pointer and
+ * aliases radix trees. The memleak_free hook can be called from
+ * mm/slab.c with the list_lock held (i.e. when releasing of off-slab
+ * management structures) and it will ackquire the memleak_lock. To
+ * avoid deadlocks caused by locking dependency, the list_lock must
+ * not be acquired while memleak_lock is held. This is ensured by not
+ * allocating any memory while memleak_lock is held. The radix trees
+ * have to be preloaded before acquiring the lock so that the
+ * insertion won't allocate memory.
  */
 
 /* #define DEBUG */
@@ -37,6 +49,8 @@ #include <linux/seq_file.h>
 #include <linux/vmalloc.h>
 #include <linux/mutex.h>
 #include <linux/cpumask.h>
+#include <linux/spinlock.h>
+#include <linux/rcupdate.h>
 
 #include <asm/bitops.h>
 #include <asm/sections.h>
@@ -75,6 +89,7 @@ struct memleak_pointer {
 	unsigned long flags;
 	struct list_head pointer_list;
 	struct list_head gray_list;
+	struct rcu_head rcu;
 	int use_count;
 	unsigned long pointer;
 	unsigned long offset;		/* padding */
@@ -128,6 +143,7 @@ static inline int color_black(const stru
 
 /* Tree storing the pointer aliases indexed by size */
 static RADIX_TREE(alias_tree, GFP_ATOMIC);
+static DEFINE_RWLOCK(alias_tree_lock);
 /* Tree storing all the possible pointers, indexed by the pointer value */
 static RADIX_TREE(pointer_tree, GFP_ATOMIC);
 /* The list of all allocated pointers */
@@ -136,8 +152,8 @@ static LIST_HEAD(pointer_list);
 static LIST_HEAD(gray_list);
 
 static struct kmem_cache *pointer_cache;
-/* Used to avoid recursive call via the kmalloc/kfree functions */
-static spinlock_t memleak_lock = SPIN_LOCK_UNLOCKED;
+/* The main lock for protecting the pointer lists and radix trees */
+static DEFINE_SPINLOCK(memleak_lock);
 static cpumask_t memleak_cpu_mask = CPU_MASK_NONE;
 static DEFINE_MUTEX(memleak_mutex);
 static atomic_t memleak_initialized = ATOMIC_INIT(0);
@@ -181,9 +197,12 @@ static inline void dump_pointer_internal
 	printk(KERN_NOTICE "  ref_count = %d\n", pointer->ref_count);
 	printk(KERN_NOTICE "  count = %d\n", pointer->count);
 	printk(KERN_NOTICE "  aliases:\n");
-	if (pointer->alias_list)
-		hlist_for_each_entry(alias, elem, pointer->alias_list, node)
+	if (pointer->alias_list) {
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, pointer->alias_list, node)
 			printk(KERN_NOTICE "    0x%lx\n", alias->offset);
+		rcu_read_unlock();
+	}
 }
 #else
 static inline void dump_pointer_internals(struct memleak_pointer *pointer)
@@ -213,45 +232,81 @@ static int insert_alias(unsigned long ty
 {
 	int ret = 0;
 	struct hlist_head *alias_list;
+	struct hlist_node *elem;
 	struct memleak_alias *alias;
+	unsigned long flags;
+	unsigned int cpu_id;
 
-	if (type_id == 0 || offset == 0 || offset >= ml_sizeof(type_id)) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (type_id == 0 || offset == 0 || offset >= ml_sizeof(type_id))
+		return -EINVAL;
+
+	/* no recursive re-entrance on the same CPU. We do not track
+	 * the alias tree allocations */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
+	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
+		BUG();
 
 	offset &= ~(BYTES_PER_WORD - 1);
 
+	read_lock(&alias_tree_lock);
 	alias_list = radix_tree_lookup(&alias_tree, type_id);
-	if (alias_list) {
-		struct hlist_node *elem;
+	read_unlock(&alias_tree_lock);
 
-		hlist_for_each_entry(alias, elem, alias_list, node) {
-			if (alias->offset == offset) {
-				ret = -EEXIST;
-				goto out;
-			}
-		}
-	} else {
+	if (!alias_list) {
+		/* no alias list for this type id. Allocate list_head
+		 * and preload the radix tree */
 		alias_list = kmalloc(sizeof(*alias_list), GFP_ATOMIC);
 		if (!alias_list)
-			panic("kmemleak: cannot allocate initial memory\n");
+			panic("kmemleak: cannot allocate alias_list\n");
 		INIT_HLIST_HEAD(alias_list);
 
-		ret = radix_tree_insert(&alias_tree, type_id, alias_list);
+		ret = radix_tree_preload(GFP_ATOMIC);
 		if (ret)
+			panic("kmemleak: cannot preload the aliases radix tree: %d\n", ret);
+
+		write_lock(&alias_tree_lock);
+		ret = radix_tree_insert(&alias_tree, type_id, alias_list);
+		write_unlock(&alias_tree_lock);
+
+		radix_tree_preload_end();
+
+		if (ret == -EEXIST) {
+			/* allocated in the meantime on a different CPU */
+			read_lock(&alias_tree_lock);
+			alias_list = radix_tree_lookup(&alias_tree, type_id);
+			read_unlock(&alias_tree_lock);
+		} else if (ret)
 			panic("kmemleak: cannot insert into the aliases radix tree: %d\n", ret);
 	}
 
+	BUG_ON(!alias_list);
+
+	rcu_read_lock();
+	hlist_for_each_entry_rcu(alias, elem, alias_list, node) {
+		if (alias->offset == offset) {
+			rcu_read_unlock();
+			ret = -EEXIST;
+			goto out;
+		}
+	}
+	rcu_read_unlock();
+
 	alias = kmalloc(sizeof(*alias), GFP_ATOMIC);
 	if (!alias)
 		panic("kmemleak: cannot allocate initial memory\n");
 	INIT_HLIST_NODE(&alias->node);
 	alias->offset = offset;
 
-	hlist_add_head(&alias->node, alias_list);
+	write_lock(&alias_tree_lock);
+	hlist_add_head_rcu(&alias->node, alias_list);
+	write_unlock(&alias_tree_lock);
 
  out:
+	cpu_clear(cpu_id, memleak_cpu_mask);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
+
 	return ret;
 }
 
@@ -261,18 +316,12 @@ void memleak_insert_aliases(struct memle
 {
 	struct memleak_offset *ml_off;
 	int i = 0;
+#ifdef CONFIG_DEBUG_MEMLEAK_SECONDARY_ALIASES
 	unsigned long flags;
-	unsigned int cpu_id;
+#endif
 
 	pr_debug("%s(0x%p, 0x%p)\n", __FUNCTION__, ml_off_start, ml_off_end);
 
-	spin_lock_irqsave(&memleak_lock, flags);
-
-	/* do not track the kmemleak allocated pointers */
-	cpu_id = smp_processor_id();
-	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
-		BUG();
-
 	/* primary aliases - container_of(member) */
 	for (ml_off = ml_off_start; ml_off < ml_off_end; ml_off++)
 		if (!insert_alias(ml_off->type_id, ml_off->offset))
@@ -286,87 +335,70 @@ #ifdef CONFIG_DEBUG_MEMLEAK_SECONDARY_AL
 		struct memleak_alias *alias;
 		struct hlist_node *elem;
 
+		/* with imprecise type identification, it the member
+		 * id is the same as the outer structure id, just
+		 * ignore as any potential aliases are already in the
+		 * tree */
+		if (ml_off->member_type_id == ml_off->type_id)
+			continue;
+
+		read_lock_irqsave(&alias_tree_lock, flags);
 		alias_list = radix_tree_lookup(&alias_tree, ml_off->member_type_id);
+		read_unlock_irqrestore(&alias_tree_lock, flags);
 		if (!alias_list)
 			continue;
 
-		hlist_for_each_entry(alias, elem, alias_list, node)
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, alias_list, node)
 			if (!insert_alias(ml_off->type_id, ml_off->offset + alias->offset))
 				i++;
+		rcu_read_unlock();
 	}
 	pr_debug("kmemleak: found %d alias(es)\n", i);
 #endif
-
-	cpu_clear(cpu_id, memleak_cpu_mask);
-	spin_unlock_irqrestore(&memleak_lock, flags);
 }
 EXPORT_SYMBOL_GPL(memleak_insert_aliases);
 
+/* called with memleak_lock held and interrupts disabled */
 static inline struct memleak_pointer *get_cached_pointer(unsigned long ptr)
 {
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
 	if (!last_pointer || ptr != last_pointer->pointer)
 		last_pointer = radix_tree_lookup(&pointer_tree, ptr);
 	return last_pointer;
 }
 
-static void create_pointer_aliases(struct memleak_pointer *pointer)
+/* no need for atomic operations since memleak_lock is held and
+ * interrupts disabled */
+static inline void get_pointer(struct memleak_pointer *pointer)
 {
-	struct memleak_alias *alias;
-	struct hlist_node *elem;
-	unsigned long ptr = pointer->pointer;
-	int err;
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 
-	BUG_ON(pointer->flags & POINTER_ALIASES);
-
-	if (pointer->offset) {
-		err = radix_tree_insert(&pointer_tree, ptr + pointer->offset, pointer);
-		if (err) {
-			dump_stack();
-			panic("kmemleak: cannot insert offset into the pointer radix tree: %d\n", err);
-		}
-	}
-
-	pointer->alias_list = radix_tree_lookup(&alias_tree, pointer->type_id);
-	if (pointer->alias_list) {
-		hlist_for_each_entry(alias, elem, pointer->alias_list, node) {
-			err = radix_tree_insert(&pointer_tree, ptr
-						+ pointer->offset + alias->offset,
-						pointer);
-			if (err) {
-				dump_stack();
-				panic("kmemleak: cannot insert alias into the pointer radix tree: %d\n", err);
-			}
-		}
-	}
-
-	pointer->flags |= POINTER_ALIASES;
+	pointer->use_count++;
 }
 
-static void delete_pointer_aliases(struct memleak_pointer *pointer)
+static void free_pointer_rcu(struct rcu_head *rcu)
 {
-	struct memleak_alias *alias;
-	struct hlist_node *elem;
-	unsigned long ptr = pointer->pointer;
-
-	BUG_ON(!(pointer->flags & POINTER_ALIASES));
-
-	if (pointer->offset)
-		radix_tree_delete(&pointer_tree, ptr + pointer->offset);
+	struct memleak_pointer *pointer = container_of(rcu,
+						       struct memleak_pointer,
+						       rcu);
+	unsigned long flags;
+	unsigned int cpu_id;
 
-	if (pointer->alias_list) {
-		hlist_for_each_entry(alias, elem, pointer->alias_list, node)
-			radix_tree_delete(&pointer_tree,
-					  ptr + pointer->offset + alias->offset);
-		pointer->alias_list = NULL;
-	}
+	/* no recursive re-entrance on the same CPU */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
+	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
+		BUG();
 
-	pointer->flags &= ~POINTER_ALIASES;
-}
+	kmem_cache_free(pointer_cache, pointer);
 
-/* no need for atomic operations since memleak_lock is held anyway */
-static inline void get_pointer(struct memleak_pointer *pointer)
-{
-	pointer->use_count++;
+	cpu_clear(cpu_id, memleak_cpu_mask);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
 }
 
 /* called with memleak_lock held for pointer_list modification and
@@ -377,6 +409,9 @@ static void __put_pointer(struct memleak
 	struct hlist_node *elem, *tmp;
 	struct memleak_scan_area *area;
 
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
 	if (--pointer->use_count > 0)
 		return;
 
@@ -386,8 +421,11 @@ static void __put_pointer(struct memleak
 		kfree(area);
 	}
 
-	list_del(&pointer->pointer_list);
-	kmem_cache_free(pointer_cache, pointer);
+	list_del_rcu(&pointer->pointer_list);
+	call_rcu(&pointer->rcu, free_pointer_rcu);
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 }
 
 static void put_pointer(struct memleak_pointer *pointer)
@@ -395,15 +433,136 @@ static void put_pointer(struct memleak_p
 	unsigned long flags;
 	unsigned int cpu_id;
 
-	spin_lock_irqsave(&memleak_lock, flags);
-	cpu_id = smp_processor_id();
+	/* no recursive re-entrance on the same CPU */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
 	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
 		BUG();
 
+	spin_lock(&memleak_lock);
 	__put_pointer(pointer);
+	spin_unlock(&memleak_lock);
 
 	cpu_clear(cpu_id, memleak_cpu_mask);
-	spin_unlock_irqrestore(&memleak_lock, flags);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
+}
+
+/* called with memleak_lock held and interrupts disabled */
+static void delete_pointer_aliases(struct memleak_pointer *pointer)
+{
+	struct memleak_alias *alias;
+	struct hlist_node *elem;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+	BUG_ON(!(pointer->flags & POINTER_ALIASES));
+
+	if (pointer->offset)
+		radix_tree_delete(&pointer_tree, pointer->pointer
+				  + pointer->offset);
+
+	if (pointer->alias_list) {
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, pointer->alias_list, node)
+			radix_tree_delete(&pointer_tree, pointer->pointer
+					  + pointer->offset + alias->offset);
+		rcu_read_unlock();
+		pointer->alias_list = NULL;
+	}
+
+	pointer->flags &= ~POINTER_ALIASES;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+}
+
+/* called with memleak_lock held but may be released */
+static inline int insert_pointer_alias(struct memleak_pointer *pointer,
+				       unsigned long alias)
+{
+	int ret = 0;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+	spin_unlock(&memleak_lock);
+	ret = radix_tree_preload(GFP_ATOMIC);
+	spin_lock(&memleak_lock);
+	if (ret) {
+		printk(KERN_WARNING "kmemleak: cannot preload the pointer radix tree: %d\n", ret);
+		goto out;
+	}
+
+	ret = radix_tree_insert(&pointer_tree, alias, pointer);
+	if (ret) {
+		dump_stack();
+		panic("kmemleak: cannot insert alias into the pointer radix tree: %d\n", ret);
+	}
+
+	radix_tree_preload_end();
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+ out:
+	return ret;
+}
+
+/* called with memleak_lock held but it may be released in
+ * insert_pointer_alias(). The pointer structure is guaranteed not to
+ * be freed before this function returns (get/put_pointer)
+ */
+static inline void create_pointer_aliases(struct memleak_pointer *pointer)
+{
+	struct memleak_alias *alias;
+	struct hlist_node *elem;
+	int err = 0;
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
+	BUG_ON(pointer->flags & POINTER_ALIASES);
+
+	get_pointer(pointer);
+
+	if (pointer->offset) {
+		err = insert_pointer_alias(pointer, pointer->pointer
+					   + pointer->offset);
+		if (err)
+			goto out;
+	}
+
+	read_lock(&alias_tree_lock);
+	pointer->alias_list = radix_tree_lookup(&alias_tree, pointer->type_id);
+	read_unlock(&alias_tree_lock);
+
+	if (pointer->alias_list) {
+		rcu_read_lock();
+		hlist_for_each_entry_rcu(alias, elem, pointer->alias_list, node) {
+			err = insert_pointer_alias(pointer, pointer->pointer
+						   + pointer->offset + alias->offset);
+			if (err) {
+				rcu_read_unlock();
+				goto out;
+			}
+		}
+		rcu_read_unlock();
+	}
+
+ out:
+	pointer->flags |= POINTER_ALIASES;
+
+	/* check whether the memory block might have been freed during
+	 * the unlocked periods or an error occured */
+	if (err || !(pointer->flags & POINTER_ALLOCATED))
+		delete_pointer_aliases(pointer);
+
+	__put_pointer(pointer);
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 }
 
 /* Insert a pointer and its aliases into the pointer radix tree */
@@ -416,15 +575,16 @@ #ifdef CONFIG_FRAME_POINTER
 	void *frame;
 #endif
 
+	BUG_ON(!irqs_disabled());
+
 	pointer = kmem_cache_alloc(pointer_cache, SLAB_ATOMIC);
 	if (!pointer)
 		panic("kmemleak: cannot allocate a memleak_pointer structure\n");
 
-	last_pointer = pointer;
-
 	INIT_LIST_HEAD(&pointer->pointer_list);
 	INIT_LIST_HEAD(&pointer->gray_list);
 	INIT_HLIST_HEAD(&pointer->area_list);
+	INIT_RCU_HEAD(&pointer->rcu);
 	pointer->flags = POINTER_ALLOCATED;
 	pointer->use_count = 0;
 	pointer->pointer = ptr;
@@ -455,6 +615,16 @@ #else
 	pointer->trace[0] = (unsigned long)__builtin_return_address(0);
 #endif
 
+	/* allocate here to avoid acquiring the list_lock in mm/slab.c
+	 * while memleak_lock is held */
+	err = radix_tree_preload(GFP_ATOMIC);
+	if (err) {
+		dump_stack();
+		panic("kmemleak: cannot preload the pointer radix tree: %d\n", err);
+	}
+
+	spin_lock(&memleak_lock);
+
 	err = radix_tree_insert(&pointer_tree, ptr, pointer);
 	if (err) {
 		dump_stack();
@@ -467,8 +637,14 @@ #endif
 		      ptr, err);
 	}
 
-	list_add_tail(&pointer->pointer_list, &pointer_list);
+	list_add_tail_rcu(&pointer->pointer_list, &pointer_list);
 	get_pointer(pointer);
+	last_pointer = pointer;
+
+	spin_unlock(&memleak_lock);
+	radix_tree_preload_end();
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Remove a pointer and its aliases from the pointer radix tree */
@@ -476,11 +652,15 @@ static inline void delete_pointer(unsign
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = radix_tree_delete(&pointer_tree, ptr);
 	if (!pointer) {
 		dump_stack();
 		printk(KERN_WARNING "kmemleak: freeing unknown pointer value 0x%08lx\n", ptr);
-		return;
+		goto out;
 	}
 	if (pointer->pointer != ptr) {
 		dump_stack();
@@ -490,6 +670,7 @@ static inline void delete_pointer(unsign
 
 	BUG_ON(!(pointer->flags & POINTER_ALLOCATED));
 
+	/* deleting the cached pointer */
 	if (last_pointer && ptr == last_pointer->pointer)
 		last_pointer = NULL;
 
@@ -506,6 +687,11 @@ #endif
 	pointer->flags &= ~POINTER_ALLOCATED;
 	pointer->pointer = 0;
 	__put_pointer(pointer);
+
+ out:
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Re-create the pointer aliases according to the new size/offset
@@ -515,6 +701,10 @@ static inline void unpad_pointer(unsigne
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -532,13 +722,18 @@ static inline void unpad_pointer(unsigne
 	}
 
 	if (offset == pointer->offset && size == pointer->size)
-		return;
+		goto out;
 
 	if (pointer->flags & POINTER_ALIASES)
 		delete_pointer_aliases(pointer);
 
 	pointer->offset = offset;
 	pointer->size = size;
+
+ out:
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Make a pointer permanently gray (false positive) */
@@ -546,6 +741,10 @@ static inline void make_gray_pointer(uns
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -558,6 +757,10 @@ static inline void make_gray_pointer(uns
 	}
 
 	pointer->ref_count = 0;
+
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Mark the pointer as black */
@@ -565,6 +768,10 @@ static inline void make_black_pointer(un
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -577,6 +784,10 @@ static inline void make_black_pointer(un
 	}
 
 	pointer->ref_count = -1;
+
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Add a scanning area to the pointer */
@@ -585,6 +796,18 @@ static inline void add_scan_area(unsigne
 	struct memleak_pointer *pointer;
 	struct memleak_scan_area *area;
 
+	BUG_ON(!irqs_disabled());
+
+	area = kmalloc(sizeof(*area), GFP_ATOMIC);
+	if (!area)
+		panic("kmemleak: cannot allocate a scan area\n");
+
+	INIT_HLIST_NODE(&area->node);
+	area->offset = offset;
+	area->length = length;
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -601,15 +824,11 @@ static inline void add_scan_area(unsigne
 		panic("kmemleak: scan area larger than block 0x%08lx\n", ptr);
 	}
 
-	area = kmalloc(sizeof(*area), GFP_ATOMIC);
-	if (!area)
-		panic("kmemleak: cannot allocate a scan area\n");
+	hlist_add_head(&area->node, &pointer->area_list);
 
-	INIT_HLIST_NODE(&area->node);
-	area->offset = offset;
-	area->length = length;
+	spin_unlock(&memleak_lock);
 
-	hlist_add_head(&area->node, &pointer->area_list);
+	BUG_ON(!irqs_disabled());
 }
 
 /* Re-create the pointer aliases according to the new type id */
@@ -617,6 +836,10 @@ static inline void change_type_id(unsign
 {
 	struct memleak_pointer *pointer;
 
+	BUG_ON(!irqs_disabled());
+
+	spin_lock(&memleak_lock);
+
 	pointer = get_cached_pointer(ptr);
 	if (!pointer) {
 		dump_stack();
@@ -634,12 +857,17 @@ static inline void change_type_id(unsign
 	}
 
 	if (!type_id || type_id == pointer->type_id)
-		return;
+		goto out;
 
 	if (pointer->flags & POINTER_ALIASES)
 		delete_pointer_aliases(pointer);
 
 	pointer->type_id = type_id;
+
+ out:
+	spin_unlock(&memleak_lock);
+
+	BUG_ON(!irqs_disabled());
 }
 
 /* Allocation function hook */
@@ -671,21 +899,20 @@ void memleak_alloc(const void *ptr, size
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_ALLOC;
-		pointer->pointer = ptr;
-		pointer->size = size;
-		pointer->ref_count = ref_count;
+			pointer->type = MEMLEAK_ALLOC;
+			pointer->pointer = ptr;
+			pointer->size = size;
+			pointer->ref_count = ref_count;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	create_pointer((unsigned long)ptr, size, ref_count);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -718,19 +945,18 @@ void memleak_free(const void *ptr)
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_FREE;
-		pointer->pointer = ptr;
+			pointer->type = MEMLEAK_FREE;
+			pointer->pointer = ptr;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	delete_pointer((unsigned long)ptr);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -764,21 +990,20 @@ void memleak_padding(const void *ptr, un
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_PADDING;
-		pointer->pointer = ptr;
-		pointer->offset = offset;
-		pointer->size = size;
+			pointer->type = MEMLEAK_PADDING;
+			pointer->pointer = ptr;
+			pointer->offset = offset;
+			pointer->size = size;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	unpad_pointer((unsigned long)ptr, offset, size);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -811,19 +1036,18 @@ void memleak_not_leak(const void *ptr)
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_NOT_LEAK;
-		pointer->pointer = ptr;
+			pointer->type = MEMLEAK_NOT_LEAK;
+			pointer->pointer = ptr;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	make_gray_pointer((unsigned long)ptr);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -856,19 +1080,18 @@ void memleak_ignore(const void *ptr)
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_IGNORE;
-		pointer->pointer = ptr;
+			pointer->type = MEMLEAK_IGNORE;
+			pointer->pointer = ptr;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	make_black_pointer((unsigned long)ptr);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -901,21 +1124,20 @@ void memleak_scan_area(const void *ptr, 
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_SCAN_AREA;
-		pointer->pointer = ptr;
-		pointer->offset = offset;
-		pointer->size = length;
+			pointer->type = MEMLEAK_SCAN_AREA;
+			pointer->pointer = ptr;
+			pointer->offset = offset;
+			pointer->size = length;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	add_scan_area((unsigned long)ptr, offset, length);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -948,20 +1170,19 @@ void memleak_typeid_raw(const void *ptr,
 
 		BUG_ON(cpu_id != 0);
 
-		if (preinit_pos >= PREINIT_POINTERS)
-			panic("kmemleak: preinit pointers buffer overflow\n");
-		pointer = &preinit_pointers[preinit_pos++];
+		if (preinit_pos < PREINIT_POINTERS) {
+			pointer = &preinit_pointers[preinit_pos];
 
-		pointer->type = MEMLEAK_TYPEID;
-		pointer->pointer = ptr;
-		pointer->type_id = type_id;
+			pointer->type = MEMLEAK_TYPEID;
+			pointer->pointer = ptr;
+			pointer->type_id = type_id;
+		}
+		preinit_pos++;
 
 		goto unmask;
 	}
 
-	spin_lock(&memleak_lock);
 	change_type_id((unsigned long)ptr, type_id);
-	spin_unlock(&memleak_lock);
 
  unmask:
 	cpu_clear(cpu_id, memleak_cpu_mask);
@@ -973,7 +1194,7 @@ EXPORT_SYMBOL(memleak_typeid_raw);
 
 /* Scan a block of memory (exclusive range) for pointers and move
  * those found to the gray list. This function is called with
- * memleak_lock held
+ * memleak_lock held and interrupts disabled
  */
 static void __scan_block(void *_start, void *_end)
 {
@@ -982,6 +1203,9 @@ static void __scan_block(void *_start, v
 						      BYTES_PER_WORD);
 	unsigned long *end = _end;
 
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
+
 	for (ptr = start; ptr < end; ptr++) {
 		struct memleak_pointer *pointer =
 			radix_tree_lookup(&pointer_tree,
@@ -998,6 +1222,9 @@ static void __scan_block(void *_start, v
 			list_add_tail_rcu(&pointer->gray_list, &gray_list);
 		}
 	}
+
+	BUG_ON(!irqs_disabled());
+	BUG_ON(!spin_is_locked(&memleak_lock));
 }
 
 static void scan_block(void *start, void *end)
@@ -1027,7 +1254,7 @@ static inline void scan_pointer(struct m
 	spin_lock_irqsave(&memleak_lock, flags);
 
 	/* freed pointer */
-	if (!pointer->pointer)
+	if (!pointer->flags & POINTER_ALLOCATED)
 		goto out;
 
 	if (hlist_empty(&pointer->area_list))
@@ -1056,14 +1283,21 @@ #endif
 	unsigned int cpu_id;
 
 	/* initialize pointers (make them white) and build the initial
-	 * gray list. Note that create_pointer_aliases might allocate
-	 * memory */
-	spin_lock_irqsave(&memleak_lock, flags);
-	cpu_id = smp_processor_id();
+	 * gray list. memleak_cpu_mask is set as we don't track the
+	 * kmemleak allocations */
+	local_irq_save(flags);
+	cpu_id = get_cpu();
 	if (cpu_test_and_set(cpu_id, memleak_cpu_mask))
 		BUG();
 
-	list_for_each_entry(pointer, &pointer_list, pointer_list) {
+	/* use the _rcu variant since pointer_list can change */
+	rcu_read_lock();
+	/* memleak_lock needed to ensure that the pointer structure is
+	 * not freed on a different CPU (create_pointer_aliases may
+	 * release the lock) */
+	spin_lock(&memleak_lock);
+
+	list_for_each_entry_rcu(pointer, &pointer_list, pointer_list) {
 		/* lazy insertion of the pointer aliases into the radix tree */
 		if ((pointer->flags & POINTER_ALLOCATED)
 		    && !(pointer->flags & POINTER_ALIASES))
@@ -1071,13 +1305,19 @@ #endif
 
 		pointer->count = 0;
 		if (color_gray(pointer)) {
+			/* the pointer will be put back once it is
+			 * scanned via the gray_list */
 			get_pointer(pointer);
 			list_add_tail(&pointer->gray_list, &gray_list);
 		}
 	}
 
+	spin_unlock(&memleak_lock);
+	rcu_read_unlock();
+
 	cpu_clear(cpu_id, memleak_cpu_mask);
-	spin_unlock_irqrestore(&memleak_lock, flags);
+	put_cpu_no_resched();
+	local_irq_restore(flags);
 
 	/* data/bss scanning */
 	scan_block(_sdata, _edata);
@@ -1262,6 +1502,10 @@ void __init memleak_init(void)
 
 	atomic_set(&memleak_initialized, 1);
 
+	if (preinit_pos >= PREINIT_POINTERS)
+		panic("kmemleak: preinit pointers buffer overflow: %d\n",
+		      preinit_pos);
+
 	/* execute the buffered memleak actions */
 	pr_debug("kmemleak: %d preinit actions\n", preinit_pos);
 	for (i = 0; i < preinit_pos; i++) {
#29 lore raw
Quoted 9 lines · Catalin Marinas
Quoted 3 lines · Michal Piotrowski

cat /sys/kernel/debug/memleak + LTP + patching repo = oops?

BUG: unable to handle kernel paging request at virtual address 6b6b6b6b

I got this as well today on an 4-CPU ARM platform. I changed some list traversal to the RCU variants but forgot to change the list_del/add. I attach another patch which fixes this (haven't tested it extensively yet).

Thanks.

I just added your "Fix memory leak in vc_resize/vc_allocate" patch to my series file.

orphan pointer 0xc6110000 (size 12288):
c017480e: <__kmalloc>
c024dda4: <vc_resize>
c020ed9c: <fbcon_startup>
c0251028: <register_con_driver>
c02511e0: <take_over_console>
c020e21e: <fbcon_takeover>
c0212b08: <fbcon_fb_registered>
c0212ce1: <fbcon_event_notify>
orphan pointer 0xf55b0000 (size 8208):
c017480e: <__kmalloc>
c0211bb8: <fbcon_set_font>
c0251b17: <con_font_set>
c0251c7b: <con_font_op>
c0249a97: <vt_ioctl>
c024432e: <tty_ioctl>
c0189fd1: <do_ioctl>
c018a269: <vfs_ioctl>

This is interesting. Is it too large?
orphan pointer 0xfe09f000 (size 4194304):
c016cb9c: <__vmalloc_node>
c016cbb2: <__vmalloc>
c016cbc7: <vmalloc>
fdd3bbe1: <txInit>
fdd23b73: <init_jfs_fs>
c0149fea: <sys_init_module>

A simple way to reproduce:

while true
do
sudo mount -o loop -t jfs /home/fs-farm/jfs.img /mnt/fs-farm/jfs/ sudo umount /mnt/fs-farm/jfs/
done

A large collection of false positives :)
http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/ml_collection/ml1.tar

Quoted 8 lines

Thanks.

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#30 lore raw
Quoted 13 lines · Michal Piotrowski

I just added your "Fix memory leak in vc_resize/vc_allocate" patch to my series file.

orphan pointer 0xc6110000 (size 12288):
c017480e: <__kmalloc>
c024dda4: <vc_resize>
c020ed9c: <fbcon_startup>
c0251028: <register_con_driver>
c02511e0: <take_over_console>
c020e21e: <fbcon_takeover>
c0212b08: <fbcon_fb_registered>
c0212ce1: <fbcon_event_notify>
orphan pointer 0xf55b0000 (size 8208):

Quoted 8 lines

c017480e: <__kmalloc>
c0211bb8: <fbcon_set_font>
c0251b17: <con_font_set>
c0251c7b: <con_font_op>
c0249a97: <vt_ioctl>
c024432e: <tty_ioctl>
c0189fd1: <do_ioctl>
c018a269: <vfs_ioctl>

The second one is probably a false positive as the stored pointer is different from the one returned by kmalloc (there is some padding). I'll add it to the kmemleak-false-positives patch.

The first one might be a real leak. Anotonino, any idea about this?

Thanks.

Signature · 1 line
Catalin
#31 lore raw
Quoted 23 lines · Catalin Marinas
Quoted 13 lines · Michal Piotrowski

I just added your "Fix memory leak in vc_resize/vc_allocate" patch to my series file.

orphan pointer 0xc6110000 (size 12288):
c017480e: <__kmalloc>
c024dda4: <vc_resize>
c020ed9c: <fbcon_startup>
c0251028: <register_con_driver>
c02511e0: <take_over_console>
c020e21e: <fbcon_takeover>
c0212b08: <fbcon_fb_registered>
c0212ce1: <fbcon_event_notify>
orphan pointer 0xf55b0000 (size 8208):

Quoted 8 lines

c017480e: <__kmalloc>
c0211bb8: <fbcon_set_font>
c0251b17: <con_font_set>
c0251c7b: <con_font_op>
c0249a97: <vt_ioctl>
c024432e: <tty_ioctl>
c0189fd1: <do_ioctl>
c018a269: <vfs_ioctl>

Quoted 3 lines

The second one is probably a false positive as the stored pointer is different from the one returned by kmalloc (there is some padding). I'll add it to the kmemleak-false-positives patch.

Yes, I think it's triggered by the padding:

	new_data += FONT_EXTRA_WORDS * sizeof(int);

The free'ing in fbcon_do_set_font() takes into account the padding:

	if (old_data && (--REFCOUNT(old_data) == 0))
		kfree(old_data - FONT_EXTRA_WORDS * sizeof(int));
Quoted 2 lines

The first one might be a real leak. Anotonino, any idea about this?

On boot, vc->vc_screenbuf is allocated by alloc_bootmem() (vc->kmalloced == 0), so yes, there's a leak in there . But I don't think we have a way to deallocate this type of memory, so we just let it go.

Tony

#32 lore raw
Quoted 12 lines · Antonino A. Daplas
Quoted 10 lines · Catalin Marinas
Quoted 9 lines · Michal Piotrowski

orphan pointer 0xc6110000 (size 12288):
c017480e: <__kmalloc>
c024dda4: <vc_resize>
c020ed9c: <fbcon_startup>
c0251028: <register_con_driver>
c02511e0: <take_over_console>
c020e21e: <fbcon_takeover>
c0212b08: <fbcon_fb_registered>
c0212ce1: <fbcon_event_notify>

Quoted 3 lines

On boot, vc->vc_screenbuf is allocated by alloc_bootmem() (vc->kmalloced == 0), so yes, there's a leak in there . But I don't think we have a way to deallocate this type of memory, so we just let it go.

It's not the leak caused by alloc_bootmem as kmemleak doesn't track this kind of allocations. The block allocated via kmalloc in vc_resize() is reported as unreferenced. However, I think that's a false positive because it looks like the vc_screenbuf pointer is stored in vc_cons[].d->vc_screenbuf which is allocated by alloc_bootmem in con_init() and kmemleak doesn't track the alloc_bootmem blocks. I'll fix kmemleak.

Thanks.

Signature · 1 line
Catalin
#33 lore raw
In reply to Michal Piotrowski
Quoted 8 lines · Michal Piotrowski

This is interesting. Is it too large?
orphan pointer 0xfe09f000 (size 4194304):
c016cb9c: <__vmalloc_node>
c016cbb2: <__vmalloc>
c016cbc7: <vmalloc>
fdd3bbe1: <txInit>
fdd23b73: <init_jfs_fs>
c0149fea: <sys_init_module>

Looking at the code, I don't think it is too large. Anyway, it is just a temporary false positive as it seems to only be present in some of the files in your tarball.

Quoted 2 lines

A large collection of false positives :)
http://www.stardust.webpages.pl/files/o_bugs/kmemleak-0.9/ml_collection/ml1.tar

Thanks, but some of them look like real leaks :-) - delayacct_tsk_init
- I'll post a separate e-mail for this.

Have you seen any crashes or lockdep reports with the latest kmemleak patches?

Signature · 1 line
Catalin
#34 lore raw

On 19/08/06, Catalin Marinas <[email protected]> wrote:
[snip]

Quoted 1 line

Have you seen any crashes or lockdep reports with the latest kmemleak patches?

12 hours of uptime - without any problems :).

Quoted 4 lines

--
Catalin

Regards,
Michal

Signature · 3 lines
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
#35 lore raw
In reply to Catalin Marinas
Quoted 3 lines · Catalin Marinas

it looks like it was caused by commit fc818301a8a39fedd7f0a71f878f29130c72193d where free_block() now calls slab_destroy() with l3->list_lock held.

[...]

Quoted 5 lines

It leaves me with the options of either implementing my own memory allocator based on pages (including a simple hash table instead of radix tree) or fix the locking in kmemleak so that memory allocations happen without memleak_lock held. The latter is a bit complicated as well since any slab allocation causes a re-entrance into kmemleak.

FYI, I couldn't fix the locking dependency issues without modifying the radix-tree implementation since even if you preload the tree outside the memleak lock, it still tries to allocate memory in radix_tree_insert.

I'll instead use a hash table (together with hlist) where I can control the memory allocations and make more use of RCU. In my few initial tests, the memory search with hash tables is about the same speed (if not slightly faster) as the radix-tree implementation.

I will post a new kmemleak version by the end of this week.

Signature · 1 line
Catalin
#36 lore raw
In reply to Michal Piotrowski
Quoted 36 lines · Michal Piotrowski

Can you look at this?
=======================================================
[ INFO: possible circular locking dependency detected ]
-------------------------------------------------------
events/0/8 is trying to acquire lock:
(old_style_spin_init){++..}, at: [<c017674f>] memleak_free+0x95/0x157

but task is already holding lock:
(&parent->list_lock){++..}, at: [<c0174f29>] drain_array+0x49/0xad

which lock already depends on the new lock.

the existing dependency chain (in reverse order) is:

-> #1 (&parent->list_lock){++..}:

       [<c0140cc7>] check_prevs_add+0x4d/0xaf
       [<c01423c1>] __lock_acquire+0x7b1/0x814
       [<c01429bc>] lock_acquire+0x5e/0x7e
       [<c02f9f7a>] _spin_lock+0x23/0x2f
       [<c0174058>] cache_alloc_refill+0x76/0x1d2
       [<c0174559>] kmem_cache_alloc+0x73/0xce
       [<c01f0c8a>] radix_tree_node_alloc+0x1a/0x51
       [<c01f0e3f>] radix_tree_insert+0x51/0xfb
       [<c01761f6>] insert_alias+0x85/0xe8
       [<c01762a4>] memleak_insert_aliases+0x4b/0xa6
       [<c01773f7>] memleak_init+0x44/0xf5
       [<c0100ab0>] start_kernel+0x17e/0x1f9
       [<c0100210>] 0xc0100210
-> #0 (old_style_spin_init){++..}:
       [<c0140cc7>] check_prevs_add+0x4d/0xaf
       [<c01423c1>] __lock_acquire+0x7b1/0x814
       [<c01429bc>] lock_acquire+0x5e/0x7e
       [<c02f9f7a>] _spin_lock+0x23/0x2f
       [<c017674f>] memleak_free+0x95/0x157
       [<c0174a74>] kmem_cache_free+0x62/0xbc
Quoted 8 lines
       [<c0172fc8>] slab_destroy+0x48/0x4d
       [<c01743b8>] free_block+0xc9/0x101
       [<c0174f65>] drain_array+0x85/0xad
       [<c017500d>] cache_reap+0x80/0xfe
       [<c01394dd>] run_workqueue+0x88/0xc4
       [<c0139617>] worker_thread+0xfe/0x131
       [<c013c6e1>] kthread+0x82/0xaa
       [<c01044c9>] kernel_thread_helper+0x5/0xb

I don't think I fully understand the slab locking, maybe some other kernel guys could help with this, but lockdep is probably right (the lock could happen on SMP systems).

It looks like lockdep complains that memleak_lock is acquired while list_lock is held and there is a possibility that (on a different CPU) memleak_lock was acquired first, followed by list_lock acquiring and therefore a deadlock.

The kmemleak+slab locking is a bit complicated because memleak itself needs to allocate memory and avoid recursive calls to it (the pointer_cache and the radix_tree allocations). The kmemleak-related allocations are not tracked by kmemleak.

I see the following solutions:

1. acquire the memleak_lock at the beginning of an alloc/free function and release it when finished while allowing recursive/nested calls (and only call the memleak_* functions during the outermost lock). This would mean ignoring the off-slab management allocations as these would lead to deadlock because of the recursive call into kmemleak. This locking should be placed around cache_reap() as well (actually, around all the entry points in the mm/slab.c file).

2. do an independent (simple) memory management in kmemleak and probably replace radix_tree with prio_tree as the latter doesn't seem to require allocations.

The first option is simple to implement but it has the disadvantage of serializing the slab calls on SMP and also not tracking the mm/slab.c allocations. The second one would provide full coverage of the kernel slab allocations but it is probably more difficult to implement.

Any thoughts/suggestions on this?

Thanks.

Signature · 1 line
Catalin
#37 lore raw
Quoted 7 lines · Catalin Marinas

The kmemleak+slab locking is a bit complicated because memleak itself needs to allocate memory and avoid recursive calls to it (the pointer_cache and the radix_tree allocations). The kmemleak-related allocations are not tracked by kmemleak.

I see the following solutions:

Quoted 7 lines

1. acquire the memleak_lock at the beginning of an alloc/free function and release it when finished while allowing recursive/nested calls (and only call the memleak_* functions during the outermost lock). This would mean ignoring the off-slab management allocations as these would lead to deadlock because of the recursive call into kmemleak. This locking should be placed around cache_reap() as well (actually, around all the entry points in the mm/slab.c file).

This would actually work because the slab allocation functions may sleep.

The simple solution would be to eliminate the memory allocations when memleak_lock is held, maybe by replacing the radix tree with a prio search tree (not sure what the performance impact will be). I'll try to go this route and see how it works.

Signature · 1 line
Catalin
#38 lore raw
Quoted 16 lines · Catalin Marinas
Quoted 7 lines · Catalin Marinas

The kmemleak+slab locking is a bit complicated because memleak itself needs to allocate memory and avoid recursive calls to it (the pointer_cache and the radix_tree allocations). The kmemleak-related allocations are not tracked by kmemleak.

I see the following solutions:

Quoted 7 lines

1. acquire the memleak_lock at the beginning of an alloc/free function and release it when finished while allowing recursive/nested calls (and only call the memleak_* functions during the outermost lock). This would mean ignoring the off-slab management allocations as these would lead to deadlock because of the recursive call into kmemleak. This locking should be placed around cache_reap() as well (actually, around all the entry points in the mm/slab.c file).

Quoted 1 line

This would actually work because the slab allocation functions may sleep.

I meant "would *not*" above.

Signature · 1 line
Catalin