* [PATCH 2.6.17-rc5 4/8] Modules support for kmemleak
2006-06-03 8:10 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.4 Catalin Marinas
@ 2006-06-03 8:11 ` Catalin Marinas
0 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-03 8:11 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
This patch handles the kmemleak operations needed for modules loading so
that memory allocations from inside a module are properly tracked.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
kernel/module.c | 32 ++++++++++++++++++++++++++++++++
1 files changed, 32 insertions(+), 0 deletions(-)
diff --git a/kernel/module.c b/kernel/module.c
index bbe0486..01bdf4c 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -1413,6 +1413,9 @@ static struct module *load_module(void _
exportindex, modindex, obsparmindex, infoindex, gplindex,
crcindex, gplcrcindex, versindex, pcpuindex, gplfutureindex,
gplfuturecrcindex;
+#ifdef CONFIG_DEBUG_MEMLEAK
+ unsigned int dataindex, bssindex, mloffindex;
+#endif
struct module *mod;
long err = 0;
void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
@@ -1510,6 +1513,11 @@ #ifdef CONFIG_KALLSYMS
sechdrs[symindex].sh_flags |= SHF_ALLOC;
sechdrs[strindex].sh_flags |= SHF_ALLOC;
#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
/* Check module struct version now, before we try to use module. */
if (!check_modstruct_version(sechdrs, versindex, mod)) {
@@ -1569,6 +1577,7 @@ #endif
/* Do the allocs. */
ptr = module_alloc(mod->core_size);
+ memleak_debug_not_leak(ptr);
if (!ptr) {
err = -ENOMEM;
goto free_percpu;
@@ -1577,6 +1586,7 @@ #endif
mod->module_core = ptr;
ptr = module_alloc(mod->init_size);
+ memleak_debug_ignore(ptr);
if (!ptr && mod->init_size) {
err = -ENOMEM;
goto free_core;
@@ -1608,6 +1618,28 @@ #endif
/* Module has been moved. */
mod = (void *)sechdrs[modindex].sh_addr;
+#ifdef CONFIG_DEBUG_MEMLEAK
+ if (mloffindex)
+ memleak_debug_insert_aliases((void *)sechdrs[mloffindex].sh_addr,
+ (void *)sechdrs[mloffindex].sh_addr
+ + sechdrs[mloffindex].sh_size);
+
+ /* only scan the sections containing data */
+ memleak_debug_scan_area(mod->module_core,
+ (unsigned long)mod - (unsigned long)mod->module_core,
+ sizeof(struct module));
+ if (dataindex)
+ memleak_debug_scan_area(mod->module_core,
+ sechdrs[dataindex].sh_addr
+ - (unsigned long)mod->module_core,
+ sechdrs[dataindex].sh_size);
+ if (bssindex)
+ memleak_debug_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);
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
@ 2006-06-04 21:56 Catalin Marinas
2006-06-04 21:59 ` [PATCH 2.6.17-rc5 1/8] Base support for kmemleak Catalin Marinas
` (8 more replies)
0 siblings, 9 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 21:56 UTC (permalink / raw)
To: linux-kernel
This is a new version (0.5) of the kernel memory leak detector. It is
mainly a bug-fix release after testing it with a wider range of kernel
modules. See the Documentation/kmemleak.txt file for a more detailed
description. The patches are downloadable from (the bundled patch or
the series):
http://homepage.ntlworld.com/cmarinas/kmemleak/patch-2.6.17-rc5-kmemleak-0.5.bz2
http://homepage.ntlworld.com/cmarinas/kmemleak/patches-kmemleak-0.5.tar.bz2
What's new in this version:
- fixed panic caused by some secondary offsets in modules
- removed the panic when not all aliases are found for freed pointers
as the aliases list can be modified by modules insertion
- following Pavel's suggestion, shortened the function names by
removing "_debug"
To do:
- more testing
- test Ingo's suggestion on task stacks scanning
- NUMA support
- (support for ioremap tracking)
Note that gcc4 cannot compile the kernels with this patch because of a
bug with __builtin_constant_p (always returning true).
Any bug reports or suggestions are more than welcome as I can only
test it with a limited number of kernel configurations.
Thanks.
--
Catalin
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 1/8] Base support for kmemleak
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
@ 2006-06-04 21:59 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 2/8] Some documentation " Catalin Marinas
` (7 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 21:59 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
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.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
include/linux/kernel.h | 7
include/linux/memleak.h | 79 ++++
init/main.c | 3
lib/Kconfig.debug | 43 ++
mm/Makefile | 2
mm/memleak.c | 1018 +++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 1149 insertions(+), 3 deletions(-)
diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index f4fc576..9155457 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -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>
@@ -284,9 +285,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.
diff --git a/include/linux/memleak.h b/include/linux/memleak.h
new file mode 100644
index 0000000..79010fc
--- /dev/null
+++ b/include/linux/memleak.h
@@ -0,0 +1,79 @@
+/*
+ * include/linux/memleak.h
+ *
+ * Copyright (C) 2006 ARM Limited
+ * Written by Catalin Marinas <catalin.marinas@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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
+ */
+
+#ifndef __MEMLEAK_H
+#define __MEMLEAK_H
+
+#include <linux/stddef.h>
+
+#ifdef CONFIG_DEBUG_MEMLEAK
+
+struct memleak_offset {
+ unsigned long offset;
+ unsigned long size;
+ unsigned long member_size;
+};
+
+/* if offsetof(type, member) is not a constant known at compile time,
+ * just use 0 instead since we cannot add it to the
+ * .init.memleak_offsets section
+ */
+#define memleak_offsetof(type, member) \
+ (__builtin_constant_p(offsetof(type, member)) ? \
+ offsetof(type, member) : 0)
+
+#define DECLARE_MEMLEAK_OFFSET(name, type, member) \
+ static const struct memleak_offset \
+ __attribute__ ((__section__ (".init.memleak_offsets"))) \
+ __attribute_used__ __memleak_offset__##name = { \
+ memleak_offsetof(type, member), \
+ sizeof(type), \
+ sizeof(((type *)0)->member) \
+ }
+
+extern void memleak_init(void);
+extern void memleak_alloc(const void *ptr, size_t size, int ref_count);
+extern void memleak_free(const void *ptr);
+extern void memleak_resize(const void *ptr, size_t size);
+extern void memleak_not_leak(const void *ptr);
+extern void memleak_ignore(const void *ptr);
+extern void memleak_scan_area(const void *ptr, unsigned long offset, size_t length);
+extern void memleak_insert_aliases(struct memleak_offset *ml_off_start,
+ struct memleak_offset *ml_off_end);
+
+#define memleak_erase(ptr) do { (ptr) = NULL; } while (0)
+
+#else
+
+#define DECLARE_MEMLEAK_OFFSET(name, type, member)
+
+#define memleak_init()
+#define memleak_alloc(ptr, size, ref_count)
+#define memleak_free(ptr)
+#define memleak_resize(ptr, size)
+#define memleak_not_leak(ptr)
+#define memleak_ignore(ptr)
+#define memleak_scan_area(ptr, offset, length)
+#define memleak_insert_aliases(ml_off_start, ml_off_end)
+#define memleak_erase(ptr)
+
+#endif /* CONFIG_DEBUG_MEMLEAK */
+
+#endif /* __MEMLEAK_H */
diff --git a/init/main.c b/init/main.c
index f715b9b..986487c 100644
--- a/init/main.c
+++ b/init/main.c
@@ -513,6 +513,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)
@@ -533,7 +535,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();
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index ccb0c1f..7c3bca3 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -89,6 +89,49 @@ config DEBUG_SLAB_LEAK
bool "Memory leak debugging"
depends on DEBUG_SLAB
+menuconfig DEBUG_MEMLEAK
+ bool "Kernel memory leak detector"
+ default n
+ depends on EXPERIMENTAL && DEBUG_SLAB
+ depends on !NUMA
+ select DEBUG_FS
+ help
+ Say Y here if you want to enable the memory leak
+ detector. The memory allocation/freeing is traced in a way
+ similar to the Boehm's conservative garbage collector, the
+ difference being that the orphan pointers are not freed but
+ only shown in /sys/kernel/debug/memleak. Enabling this
+ feature will introduce an overhead to memory allocations.
+
+config DEBUG_MEMLEAK_TRACE_LENGTH
+ int "KMemLeak stack trace length"
+ default 4
+ depends on DEBUG_MEMLEAK && FRAME_POINTER
+ help
+ This option sets the length of the stack trace for the
+ allocated pointers tracked by kmemleak.
+
+config DEBUG_MEMLEAK_PREINIT_POINTERS
+ int "KMemLeak pre-init actions buffer size"
+ default 2048
+ depends on DEBUG_MEMLEAK
+ help
+ This is the buffer for storing the memory allocation/freeing
+ calls before kmemleak is fully initialized. Each element in
+ the buffer takes 20 bytes on a 32 bit architecture. This
+ buffer will be freed once the system initialization is
+ completed.
+
+config DEBUG_MEMLEAK_SECONDARY_ALIASES
+ bool "Create secondary level pointer aliases"
+ default y
+ depends on DEBUG_MEMLEAK
+ help
+ This option creates aliases for container_of(container_of(member))
+ access to pointers. Disabling this option reduces the chances of
+ false negatives but it can slightly increase the number of false
+ positives.
+
config DEBUG_PREEMPT
bool "Debug preemptible kernel"
depends on DEBUG_KERNEL && PREEMPT
diff --git a/mm/Makefile b/mm/Makefile
index 0b8f73f..d487d96 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -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
diff --git a/mm/memleak.c b/mm/memleak.c
new file mode 100644
index 0000000..0f8e2a9
--- /dev/null
+++ b/mm/memleak.c
@@ -0,0 +1,1018 @@
+/*
+ * mm/memleak.c
+ *
+ * Copyright (C) 2006 ARM Limited
+ * Written by Catalin Marinas <catalin.marinas@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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
+ */
+
+/* #define DEBUG */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/radix-tree.h>
+#include <linux/gfp.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/kallsyms.h>
+#include <linux/mman.h>
+#include <linux/nodemask.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+#include <linux/cpumask.h>
+
+#include <asm/bitops.h>
+#include <asm/sections.h>
+#include <asm/percpu.h>
+#include <asm/processor.h>
+#include <asm/thread_info.h>
+
+#include <linux/memleak.h>
+
+#ifdef CONFIG_FRAME_POINTER
+#define MAX_TRACE CONFIG_DEBUG_MEMLEAK_TRACE_LENGTH
+#else
+#define MAX_TRACE 1
+#endif
+
+#define PREINIT_POINTERS CONFIG_DEBUG_MEMLEAK_PREINIT_POINTERS
+#define BYTES_PER_WORD sizeof(void *)
+
+/* define this if you want the task stacks to be scanned (with an
+ * increased chance of false negatives) */
+/* #define TASK_STACK_SCAN */
+
+extern struct memleak_offset __memleak_offsets_start[];
+extern struct memleak_offset __memleak_offsets_end[];
+
+struct memleak_alias {
+ struct hlist_node node;
+ unsigned long offset;
+};
+
+struct memleak_scan_area {
+ struct hlist_node node;
+ unsigned long offset;
+ size_t length;
+};
+
+struct memleak_pointer {
+ struct list_head pointer_list;
+ struct list_head gray_list;
+ unsigned long pointer;
+ size_t size;
+ int ref_count; /* the minimum encounters of the value */
+ int count; /* the ecounters of the value */
+ struct hlist_head *alias_list;
+ struct hlist_head area_list; /* areas to be scanned (or empty for all) */
+ unsigned long trace[MAX_TRACE];
+};
+
+typedef enum {
+ MEMLEAK_ALLOC,
+ MEMLEAK_FREE,
+ MEMLEAK_RESIZE,
+ MEMLEAK_NOT_LEAK,
+ MEMLEAK_IGNORE,
+ MEMLEAK_SCAN_AREA
+} memleak_action_t;
+
+struct memleak_preinit_pointer {
+ memleak_action_t type;
+ const void *pointer;
+ unsigned long offset;
+ size_t size;
+ int ref_count;
+};
+
+/* Pointer colors, encoded with count and ref_count:
+ * - white - orphan block, i.e. not enough references to it (ref_count >= 1)
+ * - gray - referred at least once and therefore non-orphan (ref_count == 0)
+ * - black - ignore; it doesn't contain references (text section) (ref_count == -1)
+ */
+#define COLOR_WHITE(pointer) ((pointer)->count != -1 && (pointer)->count < (pointer)->ref_count)
+#define COLOR_GRAY(pointer) ((pointer)->ref_count != -1 && (pointer)->count >= (pointer)->ref_count)
+#define COLOR_BLACK(pointer) ((pointer)->ref_count == -1)
+
+/* Tree storing the pointer aliases indexed by size */
+static RADIX_TREE(alias_tree, GFP_KERNEL);
+/* Tree storing all the possible pointers, indexed by the pointer value */
+static RADIX_TREE(pointer_tree, GFP_ATOMIC);
+/* The list of all allocated pointers */
+static LIST_HEAD(pointer_list);
+/* The list of the gray pointers */
+static LIST_HEAD(gray_list);
+
+static kmem_cache_t *pointer_cache;
+/* Used to avoid recursive call via the kmalloc/kfree functions */
+static spinlock_t memleak_lock = SPIN_LOCK_UNLOCKED;
+static cpumask_t memleak_cpu_mask = CPU_MASK_NONE;
+static DEFINE_MUTEX(memleak_mutex);
+static int memleak_initialized = 0;
+static int __initdata preinit_pos = 0;
+static struct memleak_preinit_pointer __initdata preinit_pointers[PREINIT_POINTERS];
+/* last allocated pointer (optimization); protected by memleak_lock */
+static struct memleak_pointer *last_pointer = NULL;
+
+static void dump_pointer_info(struct memleak_pointer *pointer)
+{
+#ifdef CONFIG_KALLSYMS
+ char namebuf[KSYM_NAME_LEN + 1] = "";
+ char *modname;
+ unsigned long symsize;
+ unsigned long offset = 0;
+#endif
+#ifdef DEBUG
+ struct memleak_alias *alias;
+ struct hlist_node *elem;
+#endif
+ int i;
+
+ printk(KERN_NOTICE "kmemleak: pointer 0x%08lx:\n", pointer->pointer);
+#ifdef DEBUG
+ printk(KERN_NOTICE " size = %d\n", pointer->size);
+ 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)
+ printk(KERN_NOTICE " 0x%lx\n", alias->offset);
+ printk(KERN_NOTICE " trace:\n");
+#endif
+ for (i = 0; i < MAX_TRACE; i++) {
+ unsigned long trace = pointer->trace[i];
+
+ if (!trace)
+ break;
+#ifdef CONFIG_KALLSYMS
+ kallsyms_lookup(trace, &symsize, &offset, &modname, namebuf);
+ printk(KERN_NOTICE " %lx: <%s>\n", trace, namebuf);
+#else
+ printk(KERN_NOTICE " %lx\n", trace);
+#endif
+ }
+}
+
+/* Insert an element into the aliases radix tree.
+ * Return 0 on success.
+ */
+static int insert_alias(unsigned long size, unsigned long offset)
+{
+ int ret = 0;
+ struct hlist_head *alias_list;
+ struct memleak_alias *alias;
+
+ if (size == 0 || offset == 0 || offset >= size) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ offset &= ~(BYTES_PER_WORD - 1);
+
+ alias_list = radix_tree_lookup(&alias_tree, size);
+ if (alias_list) {
+ struct hlist_node *elem;
+
+ hlist_for_each_entry(alias, elem, alias_list, node) {
+ if (alias->offset == offset) {
+ ret = -EEXIST;
+ goto out;
+ }
+ }
+ } else {
+ alias_list = kmalloc(sizeof(*alias_list), GFP_KERNEL);
+ if (!alias_list)
+ panic("kmemleak: cannot allocate initial memory\n");
+ INIT_HLIST_HEAD(alias_list);
+
+ ret = radix_tree_insert(&alias_tree, size, alias_list);
+ if (ret)
+ panic("kmemleak: cannot insert into the aliases radix tree: %d\n", ret);
+ }
+
+ alias = kmalloc(sizeof(*alias), GFP_KERNEL);
+ if (!alias)
+ panic("kmemleak: cannot allocate initial memory\n");
+ INIT_HLIST_NODE(&alias->node);
+ alias->offset = offset;
+
+ hlist_add_head(&alias->node, alias_list);
+
+ out:
+ return ret;
+}
+
+/* Insert pointer aliases the from the given array */
+void memleak_insert_aliases(struct memleak_offset *ml_off_start,
+ struct memleak_offset *ml_off_end)
+{
+ struct memleak_offset *ml_off;
+ int i = 0;
+ unsigned long flags;
+
+ ml_off_end -= sizeof(struct memleak_offset) - 1;
+
+ spin_lock_irqsave(&memleak_lock, flags);
+
+ /* primary aliases - container_of(member) */
+ for (ml_off = ml_off_start; ml_off < ml_off_end; ml_off++)
+ if (!insert_alias(ml_off->size, ml_off->offset))
+ i++;
+ pr_debug("kmemleak: found %d primary alias(es)\n", i);
+
+ /* secondary aliases - container_of(container_of(member)) */
+#ifdef CONFIG_DEBUG_MEMLEAK_SECONDARY_ALIASES
+ for (ml_off = ml_off_start; ml_off < ml_off_end; ml_off++) {
+ struct hlist_head *alias_list;
+ struct memleak_alias *alias;
+ struct hlist_node *elem;
+
+ alias_list = radix_tree_lookup(&alias_tree, ml_off->member_size);
+ if (!alias_list)
+ continue;
+
+ hlist_for_each_entry(alias, elem, alias_list, node)
+ if (!insert_alias(ml_off->size, ml_off->offset + alias->offset))
+ i++;
+ }
+ pr_debug("kmemleak: found %d alias(es)\n", i);
+#endif
+
+ spin_unlock_irqrestore(&memleak_lock, flags);
+}
+EXPORT_SYMBOL_GPL(memleak_insert_aliases);
+
+static inline struct memleak_pointer *get_cached_pointer(unsigned long ptr)
+{
+ if (!last_pointer || ptr != last_pointer->pointer)
+ last_pointer = radix_tree_lookup(&pointer_tree, ptr);
+ return last_pointer;
+}
+
+/* Insert a pointer and its aliases into the pointer radix tree */
+static inline void insert_pointer(unsigned long ptr, size_t size, int ref_count)
+{
+ struct memleak_alias *alias;
+ struct hlist_node *elem;
+ struct memleak_pointer *pointer;
+ int err;
+#ifdef CONFIG_FRAME_POINTER
+ int i;
+ void *frame;
+#endif
+
+ 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);
+ pointer->pointer = ptr;
+ pointer->size = size;
+ pointer->ref_count = ref_count;
+ pointer->count = -1;
+ pointer->alias_list = radix_tree_lookup(&alias_tree, size);
+
+#ifdef CONFIG_FRAME_POINTER
+ frame = __builtin_frame_address(0);
+ for (i = 0; i < MAX_TRACE; i++) {
+ void *stack = task_stack_page(current);
+
+ if (frame < stack || frame > stack + THREAD_SIZE - BYTES_PER_WORD) {
+ pointer->trace[i] = 0;
+ continue;
+ }
+
+ pointer->trace[i] = arch_call_address(frame);
+ frame = arch_prev_frame(frame);
+ /* we don't need the return to do_exit() */
+ if (kstack_end(frame))
+ pointer->trace[i] = 0;
+ }
+#else
+ pointer->trace[0] = (unsigned long)__builtin_return_address(0);
+#endif
+
+ err = radix_tree_insert(&pointer_tree, ptr, pointer);
+ if (err) {
+ dump_stack();
+ panic("kmemleak: cannot insert into the pointer radix tree: %d\n", err);
+ }
+
+ if (pointer->alias_list) {
+ hlist_for_each_entry(alias, elem, pointer->alias_list, node) {
+ if (alias->offset >= size)
+ BUG();
+
+ err = radix_tree_insert(&pointer_tree, ptr + alias->offset, pointer);
+ if (err) {
+ dump_stack();
+ panic("kmemleak: cannot insert alias into the pointer radix tree: %d\n", err);
+ }
+ }
+ }
+
+ list_add_tail(&pointer->pointer_list, &pointer_list);
+}
+
+/* Remove a pointer and its aliases from the pointer radix tree */
+static inline void delete_pointer(unsigned long ptr)
+{
+ struct memleak_alias *alias;
+ struct hlist_node *elem, *tmp;
+ struct memleak_pointer *pointer;
+ struct memleak_scan_area *area;
+
+ pointer = radix_tree_delete(&pointer_tree, ptr);
+ if (!pointer) {
+ dump_stack();
+ printk(KERN_WARNING "kmemleak: freeing unknown pointer value 0x%08lx\n", ptr);
+ return;
+ }
+ if (pointer->pointer != ptr) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ panic("kmemleak: freeing pointer by alias 0x%08lx\n", ptr);
+ }
+
+ if (last_pointer && ptr == last_pointer->pointer)
+ last_pointer = NULL;
+
+#ifdef DEBUG
+ if (COLOR_WHITE(pointer)) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ printk(KERN_WARNING "kmemleak: freeing orphan pointer 0x%08lx\n", ptr);
+ }
+#endif
+
+ if (pointer->alias_list)
+ hlist_for_each_entry(alias, elem, pointer->alias_list, node)
+ radix_tree_delete(&pointer_tree, ptr + alias->offset);
+
+ hlist_for_each_entry_safe(area, elem, tmp, &pointer->area_list, node) {
+ hlist_del(elem);
+ kfree(area);
+ }
+
+ list_del(&pointer->pointer_list);
+ kmem_cache_free(pointer_cache, pointer);
+}
+
+/* Re-create the pointer aliases according to the new size information */
+static inline void resize_pointer(unsigned long ptr, size_t size)
+{
+ struct memleak_alias *alias;
+ struct hlist_node *elem;
+ struct memleak_pointer *pointer;
+ int err;
+
+ pointer = get_cached_pointer(ptr);
+ if (!pointer) {
+ dump_stack();
+ panic("kmemleak: resizing unknown pointer value 0x%08lx\n", ptr);
+ }
+ if (pointer->pointer != ptr) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ panic("kmemleak: resizing pointer by alias 0x%08lx\n", ptr);
+ }
+
+ /* remove old aliases */
+ if (pointer->alias_list)
+ hlist_for_each_entry(alias, elem, pointer->alias_list, node)
+ radix_tree_delete(&pointer_tree, ptr + alias->offset);
+
+ /* add the new aliases. We don't update the pointer->size
+ * because the real block size should be scanned */
+ pointer->alias_list = radix_tree_lookup(&alias_tree, size);
+ if (pointer->alias_list) {
+ hlist_for_each_entry(alias, elem, pointer->alias_list, node) {
+ if (alias->offset >= size)
+ BUG();
+
+ err = radix_tree_insert(&pointer_tree, ptr + alias->offset, pointer);
+ if (err) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ panic("kmemleak: cannot insert alias into the pointer radix tree: %d\n", err);
+ }
+ }
+ }
+}
+
+/* Make a pointer permanently gray (false positive) */
+static inline void make_gray_pointer(unsigned long ptr)
+{
+ struct memleak_pointer *pointer;
+
+ pointer = get_cached_pointer(ptr);
+ if (!pointer) {
+ dump_stack();
+ panic("kmemleak: graying unknown pointer value 0x%08lx\n", ptr);
+ }
+ if (pointer->pointer != ptr) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ panic("kmemleak: graying pointer by alias 0x%08lx\n", ptr);
+ }
+
+ pointer->ref_count = 0;
+}
+
+/* Mark the pointer as black */
+static inline void make_black_pointer(unsigned long ptr)
+{
+ struct memleak_pointer *pointer;
+
+ pointer = get_cached_pointer(ptr);
+ if (!pointer) {
+ dump_stack();
+ panic("kmemleak: blacking unknown pointer value 0x%08lx\n", ptr);
+ }
+ if (pointer->pointer != ptr) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ panic("kmemleak: blacking pointer by alias 0x%08lx\n", ptr);
+ }
+
+ pointer->ref_count = -1;
+}
+
+/* Add a scanning area to the pointer */
+static inline void add_scan_area(unsigned long ptr, unsigned long offset, size_t length)
+{
+ struct memleak_pointer *pointer;
+ struct memleak_scan_area *area;
+
+ pointer = get_cached_pointer(ptr);
+ if (!pointer) {
+ dump_stack();
+ panic("kmemleak: adding scan area to unknown pointer value 0x%08lx\n", ptr);
+ }
+ if (pointer->pointer != ptr) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ panic("kmemleak: adding scan area to pointer by alias 0x%08lx\n", ptr);
+ }
+ if (offset + length > pointer->size) {
+ dump_stack();
+ dump_pointer_info(pointer);
+ 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");
+
+ INIT_HLIST_NODE(&area->node);
+ area->offset = offset;
+ area->length = length;
+
+ hlist_add_head(&area->node, &pointer->area_list);
+}
+
+/* Allocation function hook */
+void memleak_alloc(const void *ptr, size_t size, int ref_count)
+{
+ unsigned long flags;
+ unsigned int cpu_id = smp_processor_id();
+
+ if (!ptr)
+ return;
+
+ local_irq_save(flags);
+
+ if (!memleak_initialized) {
+ /* no need for SMP locking since this block is
+ * executed before the other CPUs are started */
+ struct memleak_preinit_pointer *pointer;
+
+ BUG_ON(cpu_id != 0);
+
+ if (preinit_pos >= PREINIT_POINTERS)
+ panic("kmemleak: preinit pointers buffer overflow\n");
+ pointer = &preinit_pointers[preinit_pos++];
+
+ pointer->type = MEMLEAK_ALLOC;
+ pointer->pointer = ptr;
+ pointer->size = size;
+ pointer->ref_count = ref_count;
+
+ goto out;
+ }
+
+ /* avoid recursive calls. After disabling the interrupts, the
+ * only calls to this function on the same CPU should be from
+ * kmemleak itself and we ignore them. Calls from other CPU's
+ * would wait on the spin_lock.
+ */
+ if (!cpu_test_and_set(cpu_id, memleak_cpu_mask)) {
+ pr_debug("%s(0x%p, %d, %d)\n", __FUNCTION__, ptr, size, ref_count);
+
+ spin_lock(&memleak_lock);
+ insert_pointer((unsigned long)ptr, size, ref_count);
+ spin_unlock(&memleak_lock);
+
+ cpu_clear(cpu_id, memleak_cpu_mask);
+ }
+
+ out:
+ local_irq_restore(flags);
+}
+EXPORT_SYMBOL_GPL(memleak_alloc);
+
+/* Freeing function hook */
+void memleak_free(const void *ptr)
+{
+ unsigned long flags;
+ unsigned int cpu_id = smp_processor_id();
+
+ if (!ptr)
+ return;
+
+ local_irq_save(flags);
+
+ if (!memleak_initialized) {
+ struct memleak_preinit_pointer *pointer;
+
+ BUG_ON(cpu_id != 0);
+
+ if (preinit_pos >= PREINIT_POINTERS)
+ panic("kmemleak: preinit pointers buffer overflow\n");
+ pointer = &preinit_pointers[preinit_pos++];
+
+ pointer->type = MEMLEAK_FREE;
+ pointer->pointer = ptr;
+
+ goto out;
+ }
+
+ /* avoid recursive calls. See memleak_alloc() for an explanation */
+ if (!cpu_test_and_set(cpu_id, memleak_cpu_mask)) {
+ pr_debug("%s(0x%p)\n", __FUNCTION__, ptr);
+
+ spin_lock(&memleak_lock);
+ delete_pointer((unsigned long)ptr);
+ spin_unlock(&memleak_lock);
+
+ cpu_clear(cpu_id, memleak_cpu_mask);
+ }
+
+ out:
+ local_irq_restore(flags);
+}
+EXPORT_SYMBOL_GPL(memleak_free);
+
+/* Change the size information of an allocated memory block */
+void memleak_resize(const void *ptr, size_t size)
+{
+ unsigned long flags;
+ unsigned int cpu_id = smp_processor_id();
+
+ if (!ptr)
+ return;
+
+ local_irq_save(flags);
+
+ if (!memleak_initialized) {
+ struct memleak_preinit_pointer *pointer;
+
+ BUG_ON(cpu_id != 0);
+
+ if (preinit_pos >= PREINIT_POINTERS)
+ panic("kmemleak: preinit pointers buffer overflow\n");
+ pointer = &preinit_pointers[preinit_pos++];
+
+ pointer->type = MEMLEAK_RESIZE;
+ pointer->pointer = ptr;
+ pointer->size = size;
+
+ goto out;
+ }
+
+ /* avoid recursive calls. See memleak_alloc() for an explanation */
+ if (!cpu_test_and_set(cpu_id, memleak_cpu_mask)) {
+ pr_debug("%s(0x%p, %d)\n", __FUNCTION__, ptr, size);
+
+ spin_lock(&memleak_lock);
+ resize_pointer((unsigned long)ptr, size);
+ spin_unlock(&memleak_lock);
+
+ cpu_clear(cpu_id, memleak_cpu_mask);
+ }
+
+ out:
+ local_irq_restore(flags);
+}
+EXPORT_SYMBOL(memleak_resize);
+
+/* Mark a pointer as a false positive */
+void memleak_not_leak(const void *ptr)
+{
+ unsigned long flags;
+ unsigned int cpu_id = smp_processor_id();
+
+ if (!ptr)
+ return;
+
+ local_irq_save(flags);
+
+ if (!memleak_initialized) {
+ struct memleak_preinit_pointer *pointer;
+
+ BUG_ON(cpu_id != 0);
+
+ if (preinit_pos >= PREINIT_POINTERS)
+ panic("kmemleak: preinit pointers buffer overflow\n");
+ pointer = &preinit_pointers[preinit_pos++];
+
+ pointer->type = MEMLEAK_NOT_LEAK;
+ pointer->pointer = ptr;
+
+ goto out;
+ }
+
+ /* avoid recursive calls. See memleak_alloc() for an explanation */
+ if (!cpu_test_and_set(cpu_id, memleak_cpu_mask)) {
+ pr_debug("%s(0x%p)\n", __FUNCTION__, ptr);
+
+ spin_lock(&memleak_lock);
+ make_gray_pointer((unsigned long)ptr);
+ spin_unlock(&memleak_lock);
+
+ cpu_clear(cpu_id, memleak_cpu_mask);
+ }
+
+ out:
+ local_irq_restore(flags);
+}
+EXPORT_SYMBOL(memleak_not_leak);
+
+/* Ignore this memory block */
+void memleak_ignore(const void *ptr)
+{
+ unsigned long flags;
+ unsigned int cpu_id = smp_processor_id();
+
+ if (!ptr)
+ return;
+
+ local_irq_save(flags);
+
+ if (!memleak_initialized) {
+ struct memleak_preinit_pointer *pointer;
+
+ BUG_ON(cpu_id != 0);
+
+ if (preinit_pos >= PREINIT_POINTERS)
+ panic("kmemleak: preinit pointers buffer overflow\n");
+ pointer = &preinit_pointers[preinit_pos++];
+
+ pointer->type = MEMLEAK_IGNORE;
+ pointer->pointer = ptr;
+
+ goto out;
+ }
+
+ /* avoid recursive calls. See memleak_alloc() for an explanation */
+ if (!cpu_test_and_set(cpu_id, memleak_cpu_mask)) {
+ pr_debug("%s(0x%p)\n", __FUNCTION__, ptr);
+
+ spin_lock(&memleak_lock);
+ make_black_pointer((unsigned long)ptr);
+ spin_unlock(&memleak_lock);
+
+ cpu_clear(cpu_id, memleak_cpu_mask);
+ }
+
+ out:
+ local_irq_restore(flags);
+}
+EXPORT_SYMBOL(memleak_ignore);
+
+/* Add a scanning area to a pointer */
+void memleak_scan_area(const void *ptr, unsigned long offset, size_t length)
+{
+ unsigned long flags;
+ unsigned int cpu_id = smp_processor_id();
+
+ if (!ptr)
+ return;
+
+ local_irq_save(flags);
+
+ if (!memleak_initialized) {
+ struct memleak_preinit_pointer *pointer;
+
+ BUG_ON(cpu_id != 0);
+
+ if (preinit_pos >= PREINIT_POINTERS)
+ panic("kmemleak: preinit pointers buffer overflow\n");
+ pointer = &preinit_pointers[preinit_pos++];
+
+ pointer->type = MEMLEAK_SCAN_AREA;
+ pointer->pointer = ptr;
+ pointer->offset = offset;
+ pointer->size = length;
+
+ goto out;
+ }
+
+ /* avoid recursive calls. See memleak_alloc() for an explanation */
+ if (!cpu_test_and_set(cpu_id, memleak_cpu_mask)) {
+ pr_debug("%s(0x%p)\n", __FUNCTION__, ptr);
+
+ spin_lock(&memleak_lock);
+ add_scan_area((unsigned long)ptr, offset, length);
+ spin_unlock(&memleak_lock);
+
+ cpu_clear(cpu_id, memleak_cpu_mask);
+ }
+
+ out:
+ local_irq_restore(flags);
+}
+EXPORT_SYMBOL(memleak_scan_area);
+
+/* Scan a block of memory (exclusive range) for pointers and move
+ * those found to the gray list
+ */
+static void memleak_scan_block(void *_start, void *_end)
+{
+ unsigned long *ptr;
+ unsigned long *start = (unsigned long *)ALIGN((unsigned long)_start,
+ BYTES_PER_WORD);
+ unsigned long *end = _end;
+
+ for (ptr = start; ptr < end; ptr++) {
+ struct memleak_pointer *pointer =
+ radix_tree_lookup(&pointer_tree,
+ (*ptr) & ~(BYTES_PER_WORD - 1));
+ if (!pointer)
+ continue;
+ if (!COLOR_WHITE(pointer))
+ continue;
+
+ pointer->count++;
+ /* this can happen during the grey_list traversal */
+ if (COLOR_GRAY(pointer))
+ list_add_tail_rcu(&pointer->gray_list, &gray_list);
+ }
+}
+
+/* Scan a memory block represented by a memleak_pointer */
+static inline void memleak_scan_pointer(struct memleak_pointer *pointer)
+{
+ struct memleak_scan_area *area;
+ struct hlist_node *elem;
+
+ if (hlist_empty(&pointer->area_list))
+ memleak_scan_block((void *)pointer->pointer,
+ (void *)(pointer->pointer + pointer->size));
+ else
+ hlist_for_each_entry(area, elem, &pointer->area_list, node) {
+ unsigned long ptr = pointer->pointer + area->offset;
+
+ memleak_scan_block((void *)ptr, (void *)(ptr + area->length));
+ }
+}
+
+/* Scan the memory and print the orphan pointers */
+static void memleak_scan(void)
+{
+ unsigned long flags;
+ struct memleak_pointer *pointer;
+#ifdef TASK_STACK_SCAN
+ struct task_struct *task;
+#endif
+ int i;
+
+ spin_lock_irqsave(&memleak_lock, flags);
+
+ list_for_each_entry(pointer, &pointer_list, pointer_list) {
+ pointer->count = 0;
+ if (COLOR_GRAY(pointer))
+ list_add_tail(&pointer->gray_list, &gray_list);
+ }
+
+ /* data/bss scanning */
+ memleak_scan_block(_sdata, _edata);
+ memleak_scan_block(__bss_start, __bss_stop);
+
+#ifdef CONFIG_SMP
+ /* per-cpu scanning */
+ for (i = 0; i < NR_CPUS; i++)
+ memleak_scan_block(__per_cpu_offset[i] + __per_cpu_start,
+ __per_cpu_offset[i] + __per_cpu_end);
+#endif
+
+ /* mem_map scanning */
+ for_each_online_node(i) {
+ struct page *page, *end;
+
+ page = NODE_MEM_MAP(i);
+ end = page + NODE_DATA(i)->node_spanned_pages;
+
+ memleak_scan_block(page, end);
+ }
+
+#ifdef TASK_STACK_SCAN
+ for_each_process(task)
+ memleak_scan_block(task_stack_page(task),
+ task_stack_page(task) + THREAD_SIZE);
+#endif
+
+ /* gray_list scanning */
+ rcu_read_lock();
+ list_for_each_entry_rcu(pointer, &gray_list, gray_list) {
+ memleak_scan_pointer(pointer);
+ list_del_rcu(&pointer->gray_list);
+ }
+ rcu_read_unlock();
+
+ spin_unlock_irqrestore(&memleak_lock, flags);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void *memleak_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ struct memleak_pointer *pointer;
+ loff_t n = *pos;
+
+ mutex_lock(&memleak_mutex);
+
+ if (!n)
+ memleak_scan();
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(pointer, &pointer_list, pointer_list) {
+ if (!n--)
+ return pointer;
+ }
+ rcu_read_unlock();
+
+ return NULL;
+}
+
+static void *memleak_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct list_head *n = ((struct memleak_pointer *)v)->pointer_list.next;
+
+ ++(*pos);
+
+ if (n != &pointer_list)
+ return list_entry(n, struct memleak_pointer, pointer_list);
+ return NULL;
+}
+
+static void memleak_seq_stop(struct seq_file *seq, void *v)
+{
+ mutex_unlock(&memleak_mutex);
+}
+
+static int memleak_seq_show(struct seq_file *seq, void *v)
+{
+ const struct memleak_pointer *pointer = v;
+#ifdef CONFIG_KALLSYMS
+ char namebuf[KSYM_NAME_LEN + 1] = "";
+ char *modname;
+ unsigned long symsize;
+ unsigned long offset = 0;
+#endif
+ int i;
+
+ if (!COLOR_WHITE(pointer))
+ return 0;
+
+ seq_printf(seq, "orphan pointer 0x%08lx (size %d):\n",
+ pointer->pointer, pointer->size);
+
+ for (i = 0; i < MAX_TRACE; i++) {
+ unsigned long trace = pointer->trace[i];
+ if (!trace)
+ break;
+
+#ifdef CONFIG_KALLSYMS
+ kallsyms_lookup(trace, &symsize, &offset, &modname, namebuf);
+ seq_printf(seq, " %lx: <%s>\n", trace, namebuf);
+#else
+ seq_printf(seq, " %lx\n", trace);
+#endif
+ }
+
+ return 0;
+}
+
+static struct seq_operations memleak_seq_ops = {
+ .start = memleak_seq_start,
+ .next = memleak_seq_next,
+ .stop = memleak_seq_stop,
+ .show = memleak_seq_show,
+};
+
+static int memleak_seq_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &memleak_seq_ops);
+}
+
+static struct file_operations memleak_fops = {
+ .owner = THIS_MODULE,
+ .open = memleak_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+#endif /* CONFIG_DEBUG_FS */
+
+/* KMemLeak initialization. Set up the radix tree for the pointer aliases */
+void __init memleak_init(void)
+{
+ int i = 0;
+ unsigned long flags;
+
+ pointer_cache = kmem_cache_create("pointer_cache", sizeof(struct memleak_pointer),
+ 0, SLAB_PANIC, NULL, NULL);
+ if (!pointer_cache)
+ panic("kmemleak: cannot create the pointer cache\n");
+
+ memleak_insert_aliases(__memleak_offsets_start, __memleak_offsets_end);
+
+ /* no need to hold the spinlock as SMP is not initialized yet
+ * and memleak_initialized is 0 */
+ local_irq_save(flags);
+
+ memleak_initialized = 1;
+
+ /* execute the buffered memleak actions */
+ pr_debug("kmemleak: %d preinit actions\n", preinit_pos);
+ for (i = 0; i < preinit_pos; i++) {
+ struct memleak_preinit_pointer *pointer = &preinit_pointers[i];
+
+ switch (pointer->type) {
+ case MEMLEAK_ALLOC:
+ memleak_alloc(pointer->pointer, pointer->size,
+ pointer->ref_count);
+ break;
+ case MEMLEAK_FREE:
+ memleak_free(pointer->pointer);
+ break;
+ case MEMLEAK_RESIZE:
+ memleak_resize(pointer->pointer, pointer->size);
+ break;
+ case MEMLEAK_NOT_LEAK:
+ memleak_not_leak(pointer->pointer);
+ break;
+ case MEMLEAK_IGNORE:
+ memleak_ignore(pointer->pointer);
+ break;
+ case MEMLEAK_SCAN_AREA:
+ memleak_scan_area(pointer->pointer,
+ pointer->offset, pointer->size);
+ break;
+ default:
+ BUG();
+ }
+ }
+
+ local_irq_restore(flags);
+
+ printk(KERN_INFO "Kernel memory leak detector initialized\n");
+}
+
+/* Late initialization function */
+int __init memleak_late_init(void)
+{
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dentry;
+
+ dentry = debugfs_create_file("memleak", S_IRUGO, NULL, NULL,
+ &memleak_fops);
+ if (!dentry)
+ return -ENOMEM;
+#endif
+ pr_debug("kmemleak: late initialization completed\n");
+
+ return 0;
+}
+late_initcall(memleak_late_init);
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 2/8] Some documentation for kmemleak
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
2006-06-04 21:59 ` [PATCH 2.6.17-rc5 1/8] Base support for kmemleak Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 3/8] Add the memory allocation/freeing hooks " Catalin Marinas
` (6 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
Documentation/kmemleak.txt | 92 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 92 insertions(+), 0 deletions(-)
diff --git a/Documentation/kmemleak.txt b/Documentation/kmemleak.txt
new file mode 100644
index 0000000..7072325
--- /dev/null
+++ b/Documentation/kmemleak.txt
@@ -0,0 +1,92 @@
+Kernel Memory Leak Detector
+===========================
+
+
+Introduction
+------------
+
+Kmemleak provides a way of detecting possible kernel memory leaks in a
+way similar to a tracing garbage collector
+(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tracing_garbage_collectors),
+with the difference that the orphan pointers are not freed but only
+reported via /sys/kernel/debug/memleak. A similar method is used by
+the Valgrind tool (memcheck --leak-check) to detect the memory leaks
+in user-space applications.
+
+
+Basic Algorithm
+---------------
+
+The memory allocations via kmalloc, vmalloc, kmem_cache_alloc and
+friends are tracked and the pointers, together with additional
+information like size and stack trace, are stored in a radix tree. The
+corresponding freeing function calls are tracked and the pointers
+removed from the radix tree.
+
+An allocated block of memory is considered orphan if a pointer to its
+start address or to an alias (pointer aliases are explained later)
+cannot be found by scanning the memory (including saved
+registers). This means that there might be no way for the kernel to
+pass the address of the allocated block to a freeing function and
+therefore the block is considered a leak.
+
+The scanning algorithm steps:
+
+ 1. mark all pointers as white (remaining white pointers will later
+ be considered orphan)
+ 2. scan the memory starting with the data section and stacks,
+ checking the values against the addresses stored in the radix
+ tree. If a white pointer is found, it is added to the grey list
+ 3. scan the grey pointers for matching addresses (some white
+ pointers can become grey and added at the end of the grey list)
+ until the grey set is finished
+ 4. the remaining white pointers are considered orphan and reported
+ via /sys/kernel/debug/memleak
+
+
+Improvements
+------------
+
+Because the Linux kernel calculates many pointers at run-time via the
+container_of macro (see the lists implementation), a lot of false
+positives would be reported. This tool re-writes the container_of
+macro so that the offset and size information is stored in the
+.init.memleak_offsets section. The memleak_init() function creates a
+radix tree with corresponding offsets for every encountered block
+size. The memory allocations hook stores the pointer address together
+with its aliases based on the size of the allocated block.
+
+While one level of offsets should be enough for most cases, two levels
+are considered, i.e. container_of(container_of(...)) (one false
+positive is the "struct socket_alloc" allocation in the
+sock_alloc_inode() function).
+
+Some allocated memory blocks have pointers stored in the kernel's
+internal data structures and they cannot be detected as orphans. To
+avoid this, kmemleak can also store the number of values equal to the
+pointer (or aliases) that need to be found so that the block is not
+considered a leak. One example is __vmalloc().
+
+
+Limitations and Drawbacks
+-------------------------
+
+The biggest drawback is the reduced performance of memory allocation
+and freeing. To avoid other penalties, the memory scanning is only
+performed when the /sys/kernel/debug/memleak file is read. Anyway,
+this tool is intended for debugging purposes where the performance
+might not be the most important requirement.
+
+The tool can report false positives. These are cases where an
+allocated block doesn't need to be freed (some cases in the init_call
+functions), the pointer is calculated by other methods than the
+container_of macro or the pointer is stored in a location not scanned
+by kmemleak. If the "member" argument in the offsetof(type, member)
+call is not constant, kmemleak considers the offset as zero since it
+cannot be determined at compilation time (as a side node, it seems
+that gcc-4.0 doesn't compile these offsetof constructs either).
+
+Page allocations and ioremap are not tracked. NUMA architectures are
+not supported yet.
+
+Only the ARM and i386 architectures are currently supported.
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 3/8] Add the memory allocation/freeing hooks for kmemleak
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
2006-06-04 21:59 ` [PATCH 2.6.17-rc5 1/8] Base support for kmemleak Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 2/8] Some documentation " Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 4/8] Modules support " Catalin Marinas
` (5 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
This patch adds the callbacks to memleak_(alloc|free) functions from
kmalloc/kfree, kmem_cache_(alloc|free), vmalloc/vfree etc.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
drivers/base/platform.c | 1 +
include/linux/slab.h | 4 ++++
mm/page_alloc.c | 2 ++
mm/slab.c | 22 ++++++++++++++++++++--
mm/vmalloc.c | 24 ++++++++++++++++++++++--
5 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index 83f5c59..ee5986a 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -166,6 +166,7 @@ struct platform_device *platform_device_
struct platform_object *pa;
pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
+ memleak_resize(pa, sizeof(struct platform_object));
if (pa) {
strcpy(pa->name, name);
pa->pdev.name = pa->name;
diff --git a/include/linux/slab.h b/include/linux/slab.h
index 2d985d5..aa37216 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -89,6 +89,7 @@ #endif
static inline void *kmalloc(size_t size, gfp_t flags)
{
+#ifndef CONFIG_DEBUG_MEMLEAK
if (__builtin_constant_p(size)) {
int i = 0;
#define CACHE(x) \
@@ -107,6 +108,7 @@ found:
malloc_sizes[i].cs_dmacachep :
malloc_sizes[i].cs_cachep, flags);
}
+#endif
return __kmalloc(size, flags);
}
@@ -114,6 +116,7 @@ extern void *__kzalloc(size_t, gfp_t);
static inline void *kzalloc(size_t size, gfp_t flags)
{
+#ifndef CONFIG_DEBUG_MEMLEAK
if (__builtin_constant_p(size)) {
int i = 0;
#define CACHE(x) \
@@ -132,6 +135,7 @@ found:
malloc_sizes[i].cs_dmacachep :
malloc_sizes[i].cs_cachep, flags);
}
+#endif
return __kzalloc(size, flags);
}
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 253a450..4a65aa9 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -2800,6 +2800,8 @@ void *__init alloc_large_system_hash(con
if (_hash_mask)
*_hash_mask = (1 << log2qty) - 1;
+ memleak_alloc(table, size, 1);
+
return table;
}
diff --git a/mm/slab.c b/mm/slab.c
index f1b644e..0d38f74 100644
--- a/mm/slab.c
+++ b/mm/slab.c
@@ -2878,6 +2878,7 @@ #endif
STATS_INC_ALLOCMISS(cachep);
objp = cache_alloc_refill(cachep, flags);
}
+ memleak_erase(ac->entry[ac->avail]);
return objp;
}
@@ -3143,7 +3144,11 @@ #endif
*/
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
- return __cache_alloc(cachep, flags, __builtin_return_address(0));
+ void *ptr = __cache_alloc(cachep, flags, __builtin_return_address(0));
+
+ memleak_alloc(ptr, cachep->obj_size, 1);
+
+ return ptr;
}
EXPORT_SYMBOL(kmem_cache_alloc);
@@ -3158,6 +3163,9 @@ EXPORT_SYMBOL(kmem_cache_alloc);
void *kmem_cache_zalloc(struct kmem_cache *cache, gfp_t flags)
{
void *ret = __cache_alloc(cache, flags, __builtin_return_address(0));
+
+ memleak_alloc(ret, cache->obj_size, 1);
+
if (ret)
memset(ret, 0, obj_size(cache));
return ret;
@@ -3279,6 +3287,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_.
@@ -3288,7 +3297,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_alloc(ptr, size, 1);
+
+ return ptr;
}
@@ -3372,6 +3385,9 @@ void kmem_cache_free(struct kmem_cache *
unsigned long flags;
local_irq_save(flags);
+
+ memleak_free(objp);
+
__cache_free(cachep, objp);
local_irq_restore(flags);
}
@@ -3395,6 +3411,8 @@ void kfree(const void *objp)
return;
local_irq_save(flags);
kfree_debugcheck(objp);
+ memleak_free(objp);
+
c = virt_to_cache(objp);
mutex_debug_check_no_locks_freed(objp, obj_size(c));
__cache_free(c, (void *)objp);
diff --git a/mm/vmalloc.c b/mm/vmalloc.c
index c0504f1..b7a9db3 100644
--- a/mm/vmalloc.c
+++ b/mm/vmalloc.c
@@ -349,6 +349,9 @@ void __vunmap(void *addr, int deallocate
void vfree(void *addr)
{
BUG_ON(in_interrupt());
+
+ memleak_free(addr);
+
__vunmap(addr, 1);
}
EXPORT_SYMBOL(vfree);
@@ -447,7 +450,14 @@ fail:
void *__vmalloc_area(struct vm_struct *area, gfp_t gfp_mask, pgprot_t prot)
{
- return __vmalloc_area_node(area, gfp_mask, prot, -1);
+ void *addr = __vmalloc_area_node(area, gfp_mask, prot, -1);
+
+ /* this needs ref_count = 2 since vm_struct also contains a
+ pointer to this address. The guard page is also subtracted
+ from the size */
+ memleak_alloc(addr, area->size - PAGE_SIZE, 2);
+
+ return addr;
}
/**
@@ -466,6 +476,10 @@ void *__vmalloc_node(unsigned long size,
int node)
{
struct vm_struct *area;
+ void *addr;
+#ifdef CONFIG_DEBUG_MEMLEAK
+ unsigned long real_size = size;
+#endif
size = PAGE_ALIGN(size);
if (!size || (size >> PAGE_SHIFT) > num_physpages)
@@ -475,7 +489,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);
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 4/8] Modules support for kmemleak
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
` (2 preceding siblings ...)
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 3/8] Add the memory allocation/freeing hooks " Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 5/8] Add kmemleak support for i386 Catalin Marinas
` (4 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
This patch handles the kmemleak operations needed for modules loading so
that memory allocations from inside a module are properly tracked.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
kernel/module.c | 32 ++++++++++++++++++++++++++++++++
1 files changed, 32 insertions(+), 0 deletions(-)
diff --git a/kernel/module.c b/kernel/module.c
index bbe0486..2b66374 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -1413,6 +1413,9 @@ static struct module *load_module(void _
exportindex, modindex, obsparmindex, infoindex, gplindex,
crcindex, gplcrcindex, versindex, pcpuindex, gplfutureindex,
gplfuturecrcindex;
+#ifdef CONFIG_DEBUG_MEMLEAK
+ unsigned int dataindex, bssindex, mloffindex;
+#endif
struct module *mod;
long err = 0;
void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
@@ -1510,6 +1513,11 @@ #ifdef CONFIG_KALLSYMS
sechdrs[symindex].sh_flags |= SHF_ALLOC;
sechdrs[strindex].sh_flags |= SHF_ALLOC;
#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
/* Check module struct version now, before we try to use module. */
if (!check_modstruct_version(sechdrs, versindex, mod)) {
@@ -1569,6 +1577,7 @@ #endif
/* Do the allocs. */
ptr = module_alloc(mod->core_size);
+ memleak_not_leak(ptr);
if (!ptr) {
err = -ENOMEM;
goto free_percpu;
@@ -1577,6 +1586,7 @@ #endif
mod->module_core = ptr;
ptr = module_alloc(mod->init_size);
+ memleak_ignore(ptr);
if (!ptr && mod->init_size) {
err = -ENOMEM;
goto free_core;
@@ -1608,6 +1618,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);
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 5/8] Add kmemleak support for i386
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
` (3 preceding siblings ...)
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 4/8] Modules support " Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 6/8] Add kmemleak support for ARM Catalin Marinas
` (3 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
This patch modifies the vmlinux.lds.S script and adds the backtrace support
for i386 to be used with kmemleak.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
arch/i386/kernel/vmlinux.lds.S | 4 ++++
include/asm-i386/processor.h | 12 ++++++++++++
2 files changed, 16 insertions(+), 0 deletions(-)
diff --git a/arch/i386/kernel/vmlinux.lds.S b/arch/i386/kernel/vmlinux.lds.S
index 8831303..370480e 100644
--- a/arch/i386/kernel/vmlinux.lds.S
+++ b/arch/i386/kernel/vmlinux.lds.S
@@ -38,6 +38,7 @@ SECTIONS
RODATA
/* writeable */
+ _sdata = .; /* Start of data section */
.data : AT(ADDR(.data) - LOAD_OFFSET) { /* Data */
*(.data)
CONSTRUCTORS
@@ -140,6 +141,9 @@ SECTIONS
__per_cpu_start = .;
.data.percpu : AT(ADDR(.data.percpu) - LOAD_OFFSET) { *(.data.percpu) }
__per_cpu_end = .;
+ __memleak_offsets_start = .;
+ .init.memleak_offsets : AT(ADDR(.init.memleak_offsets) - LOAD_OFFSET) { *(.init.memleak_offsets) }
+ __memleak_offsets_end = .;
. = ALIGN(4096);
__init_end = .;
/* freed after init ends here */
diff --git a/include/asm-i386/processor.h b/include/asm-i386/processor.h
index 805f0dc..9b6568a 100644
--- a/include/asm-i386/processor.h
+++ b/include/asm-i386/processor.h
@@ -743,4 +743,16 @@ #else
#define mcheck_init(c) do {} while(0)
#endif
+#ifdef CONFIG_FRAME_POINTER
+static inline unsigned long arch_call_address(void *frame)
+{
+ return *(unsigned long *) (frame + 4);
+}
+
+static inline void *arch_prev_frame(void *frame)
+{
+ return *(void **) frame;
+}
+#endif
+
#endif /* __ASM_I386_PROCESSOR_H */
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 6/8] Add kmemleak support for ARM
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
` (4 preceding siblings ...)
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 5/8] Add kmemleak support for i386 Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 7/8] Remove some of the kmemleak false positives Catalin Marinas
` (2 subsequent siblings)
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
This patch modifies the vmlinux.lds.S script and adds the backtrace support
for ARM to be used with kmemleak.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
arch/arm/kernel/vmlinux.lds.S | 7 +++++++
include/asm-arm/processor.h | 12 ++++++++++++
2 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/arch/arm/kernel/vmlinux.lds.S b/arch/arm/kernel/vmlinux.lds.S
index 2b254e8..c6f038c 100644
--- a/arch/arm/kernel/vmlinux.lds.S
+++ b/arch/arm/kernel/vmlinux.lds.S
@@ -68,6 +68,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)
@@ -110,6 +115,7 @@ #endif
.data : AT(__data_loc) {
__data_start = .; /* address in memory */
+ _sdata = .;
/*
* first, the init task union, aligned
@@ -158,6 +164,7 @@ #endif
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
+ __bss_stop = .;
_end = .;
}
/* Stabs debugging sections. */
diff --git a/include/asm-arm/processor.h b/include/asm-arm/processor.h
index 04f4d34..feaf017 100644
--- a/include/asm-arm/processor.h
+++ b/include/asm-arm/processor.h
@@ -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 */
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 7/8] Remove some of the kmemleak false positives
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
` (5 preceding siblings ...)
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 6/8] Add kmemleak support for ARM Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 8/8] Simple testing for kmemleak Catalin Marinas
2006-06-05 21:52 ` [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Michal Piotrowski
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
There are allocations for which the main pointer cannot be found but they
are not memory leaks. This patch fixes some of them.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
arch/i386/kernel/setup.c | 1 +
ipc/util.c | 2 ++
kernel/params.c | 7 +++++--
mm/slab.c | 1 +
4 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/arch/i386/kernel/setup.c b/arch/i386/kernel/setup.c
index 846e163..7c18c88 100644
--- a/arch/i386/kernel/setup.c
+++ b/arch/i386/kernel/setup.c
@@ -1323,6 +1323,7 @@ legacy_init_iomem_resources(struct resou
if (e820.map[i].addr + e820.map[i].size > 0x100000000ULL)
continue;
res = kzalloc(sizeof(struct resource), GFP_ATOMIC);
+ memleak_not_leak(res);
switch (e820.map[i].type) {
case E820_RAM: res->name = "System RAM"; break;
case E820_ACPI: res->name = "ACPI Tables"; break;
diff --git a/ipc/util.c b/ipc/util.c
index 8193299..dcf3e2d 100644
--- a/ipc/util.c
+++ b/ipc/util.c
@@ -389,6 +389,7 @@ void* ipc_rcu_alloc(int size)
*/
if (rcu_use_vmalloc(size)) {
out = vmalloc(HDRLEN_VMALLOC + size);
+ memleak_not_leak(out);
if (out) {
out += HDRLEN_VMALLOC;
container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 1;
@@ -396,6 +397,7 @@ void* ipc_rcu_alloc(int size)
}
} else {
out = kmalloc(HDRLEN_KMALLOC + size, GFP_KERNEL);
+ memleak_not_leak(out);
if (out) {
out += HDRLEN_KMALLOC;
container_of(out, struct ipc_rcu_hdr, data)->is_vmalloc = 0;
diff --git a/kernel/params.c b/kernel/params.c
index af43ecd..a30beaf 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -548,6 +548,7 @@ static void __init kernel_param_sysfs_se
unsigned int name_skip)
{
struct module_kobject *mk;
+ struct module_param_attrs *mp;
mk = kzalloc(sizeof(struct module_kobject), GFP_KERNEL);
BUG_ON(!mk);
@@ -557,11 +558,13 @@ static void __init kernel_param_sysfs_se
kobject_set_name(&mk->kobj, name);
kobject_register(&mk->kobj);
+ mp = param_sysfs_setup(mk, kparam, num_params, name_skip);
/* no need to keep the kobject if no parameter is exported */
- if (!param_sysfs_setup(mk, kparam, num_params, name_skip)) {
+ if (!mp) {
kobject_unregister(&mk->kobj);
kfree(mk);
- }
+ } else
+ memleak_not_leak(mp);
}
/*
diff --git a/mm/slab.c b/mm/slab.c
index 0d38f74..395e7bb 100644
--- a/mm/slab.c
+++ b/mm/slab.c
@@ -3357,6 +3357,7 @@ void *__alloc_percpu(size_t size)
memset(pdata->ptrs[i], 0, size);
}
+ memleak_not_leak(pdata);
/* Catch derefs w/o wrappers */
return (void *)(~(unsigned long)pdata);
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 2.6.17-rc5 8/8] Simple testing for kmemleak
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
` (6 preceding siblings ...)
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 7/8] Remove some of the kmemleak false positives Catalin Marinas
@ 2006-06-04 22:00 ` Catalin Marinas
2006-06-05 21:52 ` [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Michal Piotrowski
8 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-04 22:00 UTC (permalink / raw)
To: linux-kernel
From: Catalin Marinas <catalin.marinas@arm.com>
This patch only contains some very simple testing at the moment. Proper
testing will be needed.
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
---
lib/Kconfig.debug | 9 ++++++
mm/Makefile | 1 +
mm/memleak-test.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 93 insertions(+), 0 deletions(-)
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 7c3bca3..0a7c001 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -132,6 +132,15 @@ config DEBUG_MEMLEAK_SECONDARY_ALIASES
false negatives but it can slightly increase the number of false
positives.
+config DEBUG_MEMLEAK_TEST
+ tristate "Test the kernel memory leak detector"
+ default n
+ depends on DEBUG_MEMLEAK
+ help
+ Say Y here to build the test harness for the kernel memory
+ leak detector. At the moment, this option enables a module
+ that explicitly leaks memory.
+
config DEBUG_PREEMPT
bool "Debug preemptible kernel"
depends on DEBUG_KERNEL && PREEMPT
diff --git a/mm/Makefile b/mm/Makefile
index d487d96..aef1bd8 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -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
diff --git a/mm/memleak-test.c b/mm/memleak-test.c
new file mode 100644
index 0000000..4061f99
--- /dev/null
+++ b/mm/memleak-test.c
@@ -0,0 +1,83 @@
+/*
+ * mm/memleak-test.c
+ *
+ * Copyright (C) 2006 ARM Limited
+ * Written by Catalin Marinas <catalin.marinas@arm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/list.h>
+
+#include <linux/memleak.h>
+
+struct test_node {
+ long header[25];
+ struct list_head list;
+ long footer[25];
+};
+
+static LIST_HEAD(test_list);
+
+/* Some very simple testing. This function needs to be extended for
+ * proper testing */
+static int __init memleak_test_init(void)
+{
+ struct test_node *elem;
+ int i;
+
+ printk(KERN_INFO "KMemLeak testing\n");
+
+ /* make some orphan pointers */
+ kmalloc(32, GFP_KERNEL);
+ kmalloc(32, GFP_KERNEL);
+#ifndef CONFIG_MODULES
+ kmem_cache_alloc(files_cachep, GFP_KERNEL);
+ kmem_cache_alloc(files_cachep, GFP_KERNEL);
+#endif
+ vmalloc(64);
+ vmalloc(64);
+
+ /* add elements to a list. They should only appear as orphan
+ * after the module is removed */
+ for (i = 0; i < 10; i++) {
+ elem = kmalloc(sizeof(*elem), GFP_KERNEL);
+ if (!elem)
+ return -ENOMEM;
+ memset(elem, 0, sizeof(*elem));
+ INIT_LIST_HEAD(&elem->list);
+
+ list_add_tail(&elem->list, &test_list);
+ }
+
+ return 0;
+}
+module_init(memleak_test_init);
+
+static void __exit memleak_test_exit(void)
+{
+ struct test_node *elem, *tmp;
+
+ /* remove the list elements without actually freeing the memory */
+ list_for_each_entry_safe(elem, tmp, &test_list, list)
+ list_del(&elem->list);
+}
+module_exit(memleak_test_exit);
+
+MODULE_LICENSE("GPL");
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
` (7 preceding siblings ...)
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 8/8] Simple testing for kmemleak Catalin Marinas
@ 2006-06-05 21:52 ` Michal Piotrowski
2006-06-06 8:25 ` Catalin Marinas
` (2 more replies)
8 siblings, 3 replies; 17+ messages in thread
From: Michal Piotrowski @ 2006-06-05 21:52 UTC (permalink / raw)
To: Catalin Marinas; +Cc: linux-kernel
On 04/06/06, Catalin Marinas <catalin.marinas@gmail.com> wrote:
> This is a new version (0.5) of the kernel memory leak detector. It is
> mainly a bug-fix release after testing it with a wider range of kernel
> modules. See the Documentation/kmemleak.txt file for a more detailed
> description. The patches are downloadable from (the bundled patch or
> the series):
>
> http://homepage.ntlworld.com/cmarinas/kmemleak/patch-2.6.17-rc5-kmemleak-0.5.bz2
> http://homepage.ntlworld.com/cmarinas/kmemleak/patches-kmemleak-0.5.tar.bz2
>
System hangs while starting udev.
Here is config
http://www.stardust.webpages.pl/files/kml/kml-config
Here is "Kernel Bug : The Movie 2" ;)
http://www.stardust.webpages.pl/files/crap/kbtm2.avi
$ gcc-3.4 --version
gcc-3.4 (GCC) 3.4.6
Regards,
Michal
--
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-05 21:52 ` [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Michal Piotrowski
@ 2006-06-06 8:25 ` Catalin Marinas
2006-06-07 1:08 ` Keith Owens
2006-06-06 8:47 ` Catalin Marinas
2006-06-06 10:46 ` Catalin Marinas
2 siblings, 1 reply; 17+ messages in thread
From: Catalin Marinas @ 2006-06-06 8:25 UTC (permalink / raw)
To: Michal Piotrowski; +Cc: linux-kernel
On 05/06/06, Michal Piotrowski <michal.k.k.piotrowski@gmail.com> wrote:
> System hangs while starting udev.
>
> Here is config
> http://www.stardust.webpages.pl/files/kml/kml-config
>
> Here is "Kernel Bug : The Movie 2" ;)
> http://www.stardust.webpages.pl/files/crap/kbtm2.avi
Could you put some screenshots instead? I can't play this file (tried
totem, realplayer, windows media player).
Thanks.
--
Catalin
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-05 21:52 ` [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Michal Piotrowski
2006-06-06 8:25 ` Catalin Marinas
@ 2006-06-06 8:47 ` Catalin Marinas
2006-06-06 10:46 ` Catalin Marinas
2 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-06 8:47 UTC (permalink / raw)
To: Michal Piotrowski; +Cc: linux-kernel
On 05/06/06, Michal Piotrowski <michal.k.k.piotrowski@gmail.com> wrote:
> System hangs while starting udev.
>
> Here is config
> http://www.stardust.webpages.pl/files/kml/kml-config
>
> Here is "Kernel Bug : The Movie 2" ;)
> http://www.stardust.webpages.pl/files/crap/kbtm2.avi
I mananged to play it but there isn't anything obvious. Are there any
messages from kmemleak (it's pretty hard to read the screen in this
movie)?
Could you try with SMP disabled? The locking mechanism in kmemleak
should be OK but I might have missed something.
--
Catalin
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-05 21:52 ` [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Michal Piotrowski
2006-06-06 8:25 ` Catalin Marinas
2006-06-06 8:47 ` Catalin Marinas
@ 2006-06-06 10:46 ` Catalin Marinas
2006-06-06 15:50 ` Michal Piotrowski
2 siblings, 1 reply; 17+ messages in thread
From: Catalin Marinas @ 2006-06-06 10:46 UTC (permalink / raw)
To: Michal Piotrowski; +Cc: linux-kernel
On 05/06/06, Michal Piotrowski <michal.k.k.piotrowski@gmail.com> wrote:
> System hangs while starting udev.
>
> Here is config
> http://www.stardust.webpages.pl/files/kml/kml-config
I managed to reproduce the problem - the kernel can deadlock on SMP if
the module being loaded contains the .init.memleak_aliases section.
I'll fix it and post another patch later today.
Thanks for testing.
--
Catalin
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-06 10:46 ` Catalin Marinas
@ 2006-06-06 15:50 ` Michal Piotrowski
2006-06-06 15:59 ` Catalin Marinas
0 siblings, 1 reply; 17+ messages in thread
From: Michal Piotrowski @ 2006-06-06 15:50 UTC (permalink / raw)
To: Catalin Marinas; +Cc: linux-kernel
Hi Catalian,
On 06/06/06, Catalin Marinas <catalin.marinas@gmail.com> wrote:
> On 05/06/06, Michal Piotrowski <michal.k.k.piotrowski@gmail.com> wrote:
> > System hangs while starting udev.
> >
> > Here is config
> > http://www.stardust.webpages.pl/files/kml/kml-config
>
> I managed to reproduce the problem - the kernel can deadlock on SMP if
> the module being loaded contains the .init.memleak_aliases section.
> I'll fix it and post another patch later today.
>
I have disabled SMP and system still hangs, but now I know when :)
Here is bug http://www.stardust.webpages.pl/files/kml/bug1.jpg
Here is config http://www.stardust.webpages.pl/files/kml/kml-config2
I think that Ingo's "Ignore loglevel on printks" patch is very useful.
> --
> Catalin
>
Regards,
Michal
--
Michal K. K. Piotrowski
LTG - Linux Testers Group
(http://www.stardust.webpages.pl/ltg/wiki/)
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-06 15:50 ` Michal Piotrowski
@ 2006-06-06 15:59 ` Catalin Marinas
0 siblings, 0 replies; 17+ messages in thread
From: Catalin Marinas @ 2006-06-06 15:59 UTC (permalink / raw)
To: Michal Piotrowski; +Cc: linux-kernel
On 06/06/06, Michal Piotrowski <michal.k.k.piotrowski@gmail.com> wrote:
> I have disabled SMP and system still hangs, but now I know when :)
>
> Here is bug http://www.stardust.webpages.pl/files/kml/bug1.jpg
Yes, that's the deadlock I got as well. memleak_insert_aliases holds
the spinlock but it also calls kmalloc which calls the memleak_alloc
hook which deadlocks.
Hopefully, I fixed it know and tested it on ARM. I'll give it a try a
bit later on x86 and post a new patch.
Regards.
--
Catalin
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5
2006-06-06 8:25 ` Catalin Marinas
@ 2006-06-07 1:08 ` Keith Owens
0 siblings, 0 replies; 17+ messages in thread
From: Keith Owens @ 2006-06-07 1:08 UTC (permalink / raw)
To: Catalin Marinas; +Cc: Michal Piotrowski, linux-kernel
"Catalin Marinas" (on Tue, 6 Jun 2006 09:25:53 +0100) wrote:
>On 05/06/06, Michal Piotrowski <michal.k.k.piotrowski@gmail.com> wrote:
>> System hangs while starting udev.
>>
>> Here is config
>> http://www.stardust.webpages.pl/files/kml/kml-config
>>
>> Here is "Kernel Bug : The Movie 2" ;)
>> http://www.stardust.webpages.pl/files/crap/kbtm2.avi
>
>Could you put some screenshots instead? I can't play this file (tried
>totem, realplayer, windows media player).
[g]mplayer handles it fine. But the screen quality is so bad that you
cannot read any numbers.
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2006-06-07 1:08 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-06-04 21:56 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Catalin Marinas
2006-06-04 21:59 ` [PATCH 2.6.17-rc5 1/8] Base support for kmemleak Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 2/8] Some documentation " Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 3/8] Add the memory allocation/freeing hooks " Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 4/8] Modules support " Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 5/8] Add kmemleak support for i386 Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 6/8] Add kmemleak support for ARM Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 7/8] Remove some of the kmemleak false positives Catalin Marinas
2006-06-04 22:00 ` [PATCH 2.6.17-rc5 8/8] Simple testing for kmemleak Catalin Marinas
2006-06-05 21:52 ` [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.5 Michal Piotrowski
2006-06-06 8:25 ` Catalin Marinas
2006-06-07 1:08 ` Keith Owens
2006-06-06 8:47 ` Catalin Marinas
2006-06-06 10:46 ` Catalin Marinas
2006-06-06 15:50 ` Michal Piotrowski
2006-06-06 15:59 ` Catalin Marinas
-- strict thread matches above, loose matches on Subject: below --
2006-06-03 8:10 [PATCH 2.6.17-rc5 0/8] Kernel memory leak detector 0.4 Catalin Marinas
2006-06-03 8:11 ` [PATCH 2.6.17-rc5 4/8] Modules support for kmemleak Catalin Marinas
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox