diff options
author | hunt <hunt> | 2008-01-15 16:57:20 +0000 |
---|---|---|
committer | hunt <hunt> | 2008-01-15 16:57:20 +0000 |
commit | 68c2e2a316c7a22d058041dcba205b1e3309477d (patch) | |
tree | 39fca2263ea173fee015777ba8ed96f124e642dd /runtime/alloc.c | |
parent | d94d159d9524a9b705c2704ca15b5014a24c923e (diff) | |
download | systemtap-steved-68c2e2a316c7a22d058041dcba205b1e3309477d.tar.gz systemtap-steved-68c2e2a316c7a22d058041dcba205b1e3309477d.tar.xz systemtap-steved-68c2e2a316c7a22d058041dcba205b1e3309477d.zip |
Add support for memory allocation tracking.
Diffstat (limited to 'runtime/alloc.c')
-rw-r--r-- | runtime/alloc.c | 335 |
1 files changed, 318 insertions, 17 deletions
diff --git a/runtime/alloc.c b/runtime/alloc.c index 8f2a7451..71b592a7 100644 --- a/runtime/alloc.c +++ b/runtime/alloc.c @@ -1,6 +1,6 @@ /* -*- linux-c -*- * Memory allocation functions - * Copyright (C) 2005, 2006, 2007 Red Hat Inc. + * Copyright (C) 2005-2008 Red Hat Inc. * * This file is part of systemtap, and is free software. You can * redistribute it and/or modify it under the terms of the GNU General @@ -11,55 +11,356 @@ #ifndef _ALLOC_C_ #define _ALLOC_C_ -/* counters of how much memory has been allocated */ -static int _stp_allocated_memory = 0; static int _stp_allocated_net_memory = 0; - #define STP_ALLOC_FLAGS (GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN) +#define DEBUG_MEM +/* + * If DEBUG_MEM is defined (stap -DDEBUG_MEM ...) then full memory + * tracking is used. Each allocation is recorded and matched with + * a free. Also a fence is set around the allocated memory so overflows + * and underflows can be detected. Errors are written to the system log + * with printk. + * + * NOTE: if youy system is slow or your script makes a very large number + * of allocations, you may get a warning in the system log: + * BUG: soft lockup - CPU#1 stuck for 11s! [staprun:28269] + * This is an expected side-effect of the overhead of tracking, especially + * with a simple linked list of allocations. Optimization + * would be nice, but DEBUG_MEM is only for testing. + */ + +#ifdef DEBUG_MEM + +static spinlock_t _stp_mem_lock = SPIN_LOCK_UNLOCKED; +static int _stp_allocated_memory = 0; + +#define MEM_MAGIC 0xc11cf77f +#define MEM_FENCE_SIZE 32 + +enum _stp_memtype { MEM_KMALLOC, MEM_VMALLOC, MEM_PERCPU }; + +typedef struct { + enum _stp_memtype type; + char *alloc; + char *free; +} _stp_malloc_type; + +static const _stp_malloc_type const _stp_malloc_types[] = { + {MEM_KMALLOC, "kmalloc", "kfree"}, + {MEM_VMALLOC, "vmalloc", "vfree"}, + {MEM_PERCPU, "alloc_percpu", "free_percpu"} +}; + +struct _stp_mem_entry { + struct list_head list; + int32_t magic; + enum _stp_memtype type; + size_t len; + void *addr; +}; + +#define MEM_DEBUG_SIZE (2*MEM_FENCE_SIZE+sizeof(struct _stp_mem_entry)) + +static LIST_HEAD(_stp_mem_list); + +void _stp_check_mem_fence (char *addr, int size) +{ + char *ptr; + int i; + + ptr = addr - MEM_FENCE_SIZE; + while (ptr < addr) { + if (*ptr != 0x55) { + printk("SYSTEMTAP ERROR: Memory fence corrupted before allocated memory\n"); + printk("at addr %p. (Allocation starts at %p)", ptr, addr); + return; + } + ptr++; + } + ptr = addr + size; + while (ptr < addr + size + MEM_FENCE_SIZE) { + if (*ptr != 0x55) { + printk("SYSTEMTAP ERROR: Memory fence corrupted after allocated memory\n"); + printk("at addr %p. (Allocation ends at %p)", ptr, addr + size - 1); + return; + } + ptr++; + } +} + +void *_stp_mem_debug_setup(void *addr, size_t size, enum _stp_memtype type) +{ + struct list_head *p; + struct _stp_mem_entry *m; + memset(addr, 0x55, MEM_FENCE_SIZE); + addr += MEM_FENCE_SIZE; + memset(addr + size, 0x55, MEM_FENCE_SIZE); + p = (struct list_head *)(addr + size + MEM_FENCE_SIZE); + m = (struct _stp_mem_entry *)p; + m->magic = MEM_MAGIC; + m->type = type; + m->len = size; + m->addr = addr; + spin_lock(&_stp_mem_lock); + list_add(p, &_stp_mem_list); + spin_unlock(&_stp_mem_lock); + return addr; +} + +/* Percpu allocations don't have the fence. Implementing it is problematic. */ +void _stp_mem_debug_percpu(struct _stp_mem_entry *m, void *addr, size_t size) +{ + struct list_head *p = (struct list_head *)m; + m->magic = MEM_MAGIC; + m->type = MEM_PERCPU; + m->len = size; + m->addr = addr; + spin_lock(&_stp_mem_lock); + list_add(p, &_stp_mem_list); + spin_unlock(&_stp_mem_lock); +} + +void _stp_mem_debug_free(void *addr, enum _stp_memtype type) +{ + int found = 0; + struct list_head *p, *tmp; + struct _stp_mem_entry *m = NULL; + + spin_lock(&_stp_mem_lock); + list_for_each_safe(p, tmp, &_stp_mem_list) { + m = list_entry(p, struct _stp_mem_entry, list); + if (m->addr == addr) { + list_del(p); + found = 1; + break; + } + } + spin_unlock(&_stp_mem_lock); + if (!found) { + printk("SYSTEMTAP ERROR: Free of unallocated memory %p type=%s\n", + addr, _stp_malloc_types[type].free); + return; + } + if (m->magic != MEM_MAGIC) { + printk("SYSTEMTAP ERROR: Memory at %p corrupted!!\n", addr); + return; + } + if (m->type != type) { + printk("SYSTEMTAP ERROR: Memory allocated with %s and freed with %s\n", + _stp_malloc_types[m->type].alloc, + _stp_malloc_types[type].free); + } + + switch (m->type) { + case MEM_KMALLOC: + _stp_check_mem_fence(addr, m->len); + kfree(addr - MEM_FENCE_SIZE); + break; + case MEM_PERCPU: + free_percpu(addr); + kfree(p); + break; + case MEM_VMALLOC: + _stp_check_mem_fence(addr, m->len); + vfree(addr - MEM_FENCE_SIZE); + break; + default: + printk("SYSTEMTAP ERROR: Attempted to free memory at addr %p len=%d with unknown allocation type.\n", addr, (int)m->len); + } + + return; +} +#endif + static void *_stp_kmalloc(size_t size) { - void *ret = kmalloc(size, STP_ALLOC_FLAGS); - if (ret) +#ifdef DEBUG_MEM + void *ret = kmalloc(size + MEM_DEBUG_SIZE, STP_ALLOC_FLAGS); + if (likely(ret)) { + ret = _stp_mem_debug_setup(ret, size, MEM_KMALLOC); _stp_allocated_memory += size; + } return ret; +#else + return kmalloc(size, STP_ALLOC_FLAGS); +#endif } static void *_stp_kzalloc(size_t size) #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) { - void *ret = kmalloc(size, STP_ALLOC_FLAGS); - if (ret) { +#ifdef DEBUG_MEM + void *ret = kmalloc(size + MEM_DEBUG_SIZE, STP_ALLOC_FLAGS); + if (likely(ret)) { + ret = _stp_mem_debug_setup(ret, size, MEM_KMALLOC); memset (ret, 0, size); _stp_allocated_memory += size; } +#else + void *ret = kmalloc(size, STP_ALLOC_FLAGS); + if (likely(ret)) + memset (ret, 0, size); +#endif /* DEBUG_MEM */ return ret; } -#else +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) */ { - void *ret = kzalloc(size, STP_ALLOC_FLAGS); - if (ret) +#ifdef DEBUG_MEM + void *ret = kzalloc(size + MEM_DEBUG_SIZE, STP_ALLOC_FLAGS); + if (likely(ret)) { + ret = _stp_mem_debug_setup(ret, size, MEM_KMALLOC); _stp_allocated_memory += size; + } return ret; -} +#else + return kzalloc(size, STP_ALLOC_FLAGS); #endif +} +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) */ static void *_stp_vmalloc(unsigned long size) { - void *ret = __vmalloc(size, STP_ALLOC_FLAGS, PAGE_KERNEL); - if (ret) +#ifdef DEBUG_MEM + void *ret = __vmalloc(size + MEM_DEBUG_SIZE, STP_ALLOC_FLAGS, PAGE_KERNEL); + if (likely(ret)) { + ret = _stp_mem_debug_setup(ret, size, MEM_VMALLOC); _stp_allocated_memory += size; + } return ret; +#else + return __vmalloc(size, STP_ALLOC_FLAGS, PAGE_KERNEL); +#endif + } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) -#define _stp_alloc_percpu(size) __alloc_percpu(size, 8) +static void *_stp_alloc_percpu(size_t size) +{ +#ifdef DEBUG_MEM + void *ret = __alloc_percpu(size, 8); + if (likely(ret)) { + struct _stp_mem_entry *m = kmalloc(sizeof(struct _stp_mem_entry), STP_ALLOC_FLAGS); + if (unlikely(m == NULL)) { + free_percpu(ret); + return NULL; + } + _stp_mem_debug_percpu(m, ret, size); + _stp_allocated_memory += size * num_online_cpus(); + } + return ret; #else -#define _stp_alloc_percpu(size) __alloc_percpu(size) + return __alloc_percpu(size, 8); #endif +} +#else +static void *_stp_alloc_percpu(size_t size) +{ +#ifdef DEBUG_MEM + void *ret = __alloc_percpu(size); + if (likely(ret)) { + struct _stp_mem_entry *m = kmalloc(sizeof(struct _stp_mem_entry), STP_ALLOC_FLAGS); + if (unlikely(m == NULL)) { + free_percpu(ret); + return NULL; + } + _stp_mem_debug_percpu(m, ret, size); + _stp_allocated_memory += size * num_online_cpus(); + } + return ret; +#else + return __alloc_percpu(size); +#endif +} +#endif /* LINUX_VERSION_CODE */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,12) -#define kmalloc_node(size,flags,node) kmalloc(size,flags) +#define _stp_kmalloc_node(size,node) _stp_kmalloc(size) +#else +static void *_stp_kmalloc_node(size_t size, int node) +{ +#ifdef DEBUG_MEM + void *ret = kmalloc_node(size + MEM_DEBUG_SIZE, STP_ALLOC_FLAGS, node); + if (likely(ret)) { + ret = _stp_mem_debug_setup(ret, size, MEM_KMALLOC); + _stp_allocated_memory += size; + } + return ret; +#else + return kmalloc_node(size, STP_ALLOC_FLAGS, node); +#endif +} #endif /* LINUX_VERSION_CODE */ +void _stp_kfree(void *addr) +{ +#ifdef DEBUG_MEM + _stp_mem_debug_free(addr, MEM_KMALLOC); +#else + kfree(addr); +#endif +} + +void _stp_vfree(void *addr) +{ +#ifdef DEBUG_MEM + _stp_mem_debug_free(addr, MEM_VMALLOC); +#else + vfree(addr); +#endif +} + +void _stp_free_percpu(void *addr) +{ +#ifdef DEBUG_MEM + _stp_mem_debug_free(addr, MEM_PERCPU); +#else + free_percpu(addr); +#endif +} + +void _stp_mem_debug_done(void) +{ +#ifdef DEBUG_MEM + struct list_head *p, *tmp; + struct _stp_mem_entry *m; + + spin_lock(&_stp_mem_lock); + list_for_each_safe(p, tmp, &_stp_mem_list) { + m = list_entry(p, struct _stp_mem_entry, list); + list_del(p); + + printk("SYSTEMTAP ERROR: Memory %p len=%d allocation type: %s. Not freed.\n", + m->addr, (int)m->len, _stp_malloc_types[m->type].alloc); + + if (m->magic != MEM_MAGIC) { + printk("SYSTEMTAP ERROR: Memory at %p len=%d corrupted!!\n", m->addr, (int)m->len); + /* Don't free. Too dangerous */ + goto done; + } + + switch (m->type) { + case MEM_KMALLOC: + _stp_check_mem_fence(m->addr, m->len); + kfree(m->addr - MEM_FENCE_SIZE); + break; + case MEM_PERCPU: + free_percpu(m->addr); + kfree(p); + break; + case MEM_VMALLOC: + _stp_check_mem_fence(m->addr, m->len); + vfree(m->addr - MEM_FENCE_SIZE); + break; + default: + printk("SYSTEMTAP ERROR: Attempted to free memory at addr %p len=%d with unknown allocation type.\n", m->addr, (int)m->len); + } + } +done: + spin_unlock(&_stp_mem_lock); + + return; + +#endif +} #endif /* _ALLOC_C_ */ |