diff options
Diffstat (limited to 'sys/kern/kern_malloc_debug.c')
-rw-r--r-- | sys/kern/kern_malloc_debug.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/sys/kern/kern_malloc_debug.c b/sys/kern/kern_malloc_debug.c new file mode 100644 index 00000000000..2289c3083a0 --- /dev/null +++ b/sys/kern/kern_malloc_debug.c @@ -0,0 +1,308 @@ +/* $OpenBSD: kern_malloc_debug.c,v 1.1 2000/06/06 20:18:20 art Exp $ */ + +/* + * Copyright (c) 1999, 2000 Artur Grabowski <art@openbsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This really belongs in kern/kern_malloc.c, but it was too much pollution. + */ + +/* + * It's only possible to debug one type/size at a time. The question is + * if this is a limitation or a feature. We never want to run this as the + * default malloc because we'll run out of memory really fast. Adding + * more types will also add to the complexity of the code. + * + * This is really is simple. Every malloc() allocates two virtual pages, + * the second page is left unmapped, and the the value returned is aligned + * so that it ends at (or very close to) the page boundary to catch overflows. + * Every free() changes the protection of the first page to VM_PROT_NONE so + * that we can catch any dangling writes to it. + * To minimize the risk of writes to recycled chunks we keep an LRU of latest + * freed chunks. The length of it is controlled by MALLOC_DEBUG_CHUNKS. + * + * Don't expect any performance. + * + * TODO: + * - support for size >= PAGE_SIZE + * - add support to the fault handler to give better diagnostics if we fail. + */ + +#include <sys/param.h> +#include <sys/proc.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/systm.h> + +#include <vm/vm.h> +#include <vm/vm_kern.h> +#include <uvm/uvm.h> +#include <uvm/uvm_page.h> + +/* + * malloc_deb_type and malloc_deb_size define the type and size of + * memory to be debugged. Use 0 for a wildcard. + * + * Although those are variables, it's a really bad idea to change the type + * if any memory chunks of this type are used. It's ok to change the size + * in runtime. + */ +int malloc_deb_type = M_MBUF; +int malloc_deb_size = 128; + +/* + * MALLOC_DEBUG_CHUNKS is the number of memory chunks we require on the + * freelist before we reuse them. + */ +#define MALLOC_DEBUG_CHUNKS 16 + +/* returns 0 if normal malloc/free should be used */ +int debug_malloc __P((unsigned long, int, int, void **)); +int debug_free __P((void *, int)); +void debug_malloc_init __P((void)); + +void malloc_deb_allocate_free __P((int)); +void debug_malloc_print __P((void)); + +struct malloc_deb_entry { + TAILQ_ENTRY(malloc_deb_entry) md_list; + vaddr_t md_va; + paddr_t md_pa; + size_t md_size; + int md_type; +}; + +TAILQ_HEAD(,malloc_deb_entry) malloc_deb_free; +TAILQ_HEAD(,malloc_deb_entry) malloc_deb_used; + +int malloc_deb_allocs; +int malloc_deb_frees; +int malloc_deb_pages; +int malloc_deb_chunks_on_freelist; + +#ifndef M_DEBUG +#define M_DEBUG M_TEMP +#endif + +int +debug_malloc(size, type, flags, addr) + unsigned long size; + int type, flags; + void **addr; +{ + struct malloc_deb_entry *md = NULL; + int s; + int wait = flags & M_NOWAIT; + + if ((type != malloc_deb_type && malloc_deb_type != 0) || + (size != malloc_deb_size && malloc_deb_size != 0) || + type == M_DEBUG) + return 0; + + /* XXX - fix later */ + if (size > PAGE_SIZE) + return 0; + + s = splimp(); + if (malloc_deb_chunks_on_freelist < MALLOC_DEBUG_CHUNKS) + malloc_deb_allocate_free(wait); + + md = TAILQ_FIRST(&malloc_deb_free); + if (md == NULL) { + splx(s); + return 0; + } + TAILQ_REMOVE(&malloc_deb_free, md, md_list); + malloc_deb_chunks_on_freelist--; + + TAILQ_INSERT_HEAD(&malloc_deb_used, md, md_list); + malloc_deb_allocs++; + splx(s); + + +#ifdef PMAP_NEW + pmap_kenter_pa(md->md_va, md->md_pa, VM_PROT_ALL); +#else + pmap_enter(pmap_kernel(), md->md_va, md->md_pa, VM_PROT_ALL, TRUE, + VM_PROT_READ|VM_PROT_WRITE); +#endif + + md->md_size = size; + md->md_type = type; + + /* + * Align the returned addr so that it ends where the first page + * ends. roundup to get decent alignment. + */ + *addr = (void *)(md->md_va + PAGE_SIZE - roundup(size, sizeof(long))); + return 1; +} + +int +debug_free(addr, type) + void *addr; + int type; +{ + struct malloc_deb_entry *md; + int s; + vaddr_t va; + + if ((type != malloc_deb_type && malloc_deb_type != 0) || + type == M_DEBUG) + return 0; + + /* + * trunc_page to get the address of the page. + */ + va = trunc_page((vaddr_t)addr); + + s = splimp(); + TAILQ_FOREACH(md, &malloc_deb_used, md_list) + if (md->md_va == va) + break; + + /* + * If we are not responsible for this entry, let the normal free + * handle it + */ + if (md == NULL) { + /* + * sanity check. Check for multiple frees. + */ + TAILQ_FOREACH(md, &malloc_deb_free, md_list) + if (md->md_va == va) + panic("debug_free: already free"); + splx(s); + return 0; + } + + malloc_deb_frees++; + TAILQ_REMOVE(&malloc_deb_used, md, md_list); + + TAILQ_INSERT_TAIL(&malloc_deb_free, md, md_list); + malloc_deb_chunks_on_freelist++; + /* + * unmap the page. + */ +#ifdef PMAP_NEW + pmap_kremove(md->md_va, PAGE_SIZE); +#else + pmap_remove(pmap_kernel(), md->md_va, md->md_va + PAGE_SIZE); +#endif + splx(s); + + return 1; +} + +void +debug_malloc_init() +{ + TAILQ_INIT(&malloc_deb_free); + TAILQ_INIT(&malloc_deb_used); + + malloc_deb_allocs = 0; + malloc_deb_frees = 0; + malloc_deb_pages = 0; + malloc_deb_chunks_on_freelist = 0; +} + +/* + * Add one chunk to the freelist. + * + * called at splimp. + */ +void +malloc_deb_allocate_free(wait) + int wait; +{ + vaddr_t va, offset; + struct vm_page *pg; + struct malloc_deb_entry *md; + + md = malloc(sizeof(struct malloc_deb_entry), M_DEBUG, + wait ? M_WAITOK : M_NOWAIT); + if (md == NULL) + return; + + va = uvm_km_kmemalloc(kmem_map, uvmexp.kmem_object, + PAGE_SIZE * 2, + UVM_KMF_VALLOC | (wait ? UVM_KMF_NOWAIT : 0)); + if (va == 0) { + free(md, M_DEBUG); + return; + } + + offset = va - vm_map_min(kernel_map); + do { + simple_lock(&uvmexp.kmem_object->vmobjlock); + pg = uvm_pagealloc(uvmexp.kmem_object, offset, NULL, 0); + if (pg) { + pg->flags &= ~PG_BUSY; /* new page */ + UVM_PAGE_OWN(pg, NULL); + } + simple_unlock(&uvmexp.kmem_object->vmobjlock); + + if (pg) + break; + + if (wait == 0) { + uvm_unmap(kmem_map, va, va + PAGE_SIZE * 2); + free(md, M_DEBUG); + return; + } + uvm_wait("debug_malloc"); + } while (1); + + md->md_va = va; + md->md_pa = VM_PAGE_TO_PHYS(pg); + + malloc_deb_pages++; + TAILQ_INSERT_HEAD(&malloc_deb_free, md, md_list); + malloc_deb_chunks_on_freelist++; +} + +void +debug_malloc_print() +{ + struct malloc_deb_entry *md; + + printf("allocs: %d\n", malloc_deb_allocs); + printf("frees: %d\n", malloc_deb_frees); + printf("pages used: %d\n", malloc_deb_pages); + printf("chunks on freelist: %d\n", malloc_deb_chunks_on_freelist); + + printf("\taddr:\tsize:\n"); + printf("free chunks:\n"); + TAILQ_FOREACH(md, &malloc_deb_free, md_list) + printf("\t0x%x\t0x%x\t%d\n", md->md_va, md->md_size, md->md_type); + printf("used chunks:\n"); + TAILQ_FOREACH(md, &malloc_deb_used, md_list) + printf("\t0x%x\t0x%x\t%d\n", md->md_va, md->md_size, md->md_type); +} + + |