From 658aa18ade2a53a12bcb95ce782497e07abc2930 Mon Sep 17 00:00:00 2001 From: Tomoki Shirasawa Date: Tue, 7 Oct 2014 15:45:16 +0900 Subject: [PATCH] add memory debug (kmalloc/kfree) * support "memdebug" mckernel option * check buffer overrun when memory free and next mcexec run * check double free * check memory leak when next mcexec run --- arch/x86/kernel/include/cas.h | 46 ++++++ kernel/host.c | 11 ++ kernel/include/cls.h | 3 +- kernel/include/kmalloc.h | 15 +- kernel/mem.c | 281 +++++++++++++++++++++++++++++++++- 5 files changed, 348 insertions(+), 8 deletions(-) create mode 100644 arch/x86/kernel/include/cas.h diff --git a/arch/x86/kernel/include/cas.h b/arch/x86/kernel/include/cas.h new file mode 100644 index 00000000..f5dcc9f9 --- /dev/null +++ b/arch/x86/kernel/include/cas.h @@ -0,0 +1,46 @@ +/** + * \file arch/x86/kernel/include/cas.h + * License details are found in the file LICENSE. + * \brief + * compare and swap + * \author Tomoki Shirasawa \par + * Copyright (C) 2012 - 2013 Hitachi, Ltd. + */ +/* + * HISTORY: + */ + +#ifndef __HEADER_X86_COMMON_CAS_H +#define __HEADER_X86_COMMON_CAS_H +// return 0:fail, 1:success +static inline int +compare_and_swap(void *addr, unsigned long olddata, unsigned long newdata) +{ + unsigned long before; + + asm volatile ( + "lock; cmpxchgq %2,%1" + : "=a" (before), "+m" (*(unsigned long *)addr) + : "q" (newdata), "0" (olddata) + : "cc"); + return before == olddata; +} + +#if 0 // cmpxchg16b was not support on k1om +// return 0:fail, 1:success +static inline int +compare_and_swap16(void *addr, void *olddata, void *newdata) +{ + char rc; + + asm volatile ( + "lock; cmpxchg16b %0; setz %1" + : "=m" (*(long *)addr), "=q" (rc) + : "m" (*(long *)addr), + "d" (((long *)olddata)[0]), "a" (((long *)olddata)[1]), + "c" (((long *)newdata)[0]), "b" (((long *)newdata)[1]) + : "memory"); + return rc; +} +#endif +#endif /*__HEADER_X86_COMMON_CAS_H*/ diff --git a/kernel/host.c b/kernel/host.c index 7061d055..29a6e64b 100644 --- a/kernel/host.c +++ b/kernel/host.c @@ -500,6 +500,9 @@ extern unsigned long do_kill(int, int, int, struct siginfo *); extern void settid(struct process *proc, int mode, int newcpuid, int oldcpuid); extern void process_procfs_request(unsigned long rarg); +extern int memcheckall(); +extern int freecheck(int runcount); +extern int runcount; static int syscall_packet_handler(struct ihk_ikc_channel_desc *c, void *__packet, void *ihk_os) @@ -524,6 +527,14 @@ static int syscall_packet_handler(struct ihk_ikc_channel_desc *c, return 0; case SCD_MSG_PREPARE_PROCESS: + + if (find_command_line("memdebug")) { + memcheckall(); + if (runcount) + freecheck(runcount); + runcount++; + } + if((rc = process_msg_prepare_process(packet->arg)) == 0){ pckt.msg = SCD_MSG_PREPARE_PROCESS_ACKED; pckt.err = 0; diff --git a/kernel/include/cls.h b/kernel/include/cls.h index 35ab360b..bf70be90 100644 --- a/kernel/include/cls.h +++ b/kernel/include/cls.h @@ -20,8 +20,9 @@ */ struct malloc_header { - struct malloc_header *next; + unsigned int check; unsigned int cpu_id; + struct malloc_header *next; unsigned long size; }; diff --git a/kernel/include/kmalloc.h b/kernel/include/kmalloc.h index 2015cc20..c7d4b800 100644 --- a/kernel/include/kmalloc.h +++ b/kernel/include/kmalloc.h @@ -15,7 +15,18 @@ #include -void *kmalloc(int size, enum ihk_mc_ap_flag flag); -void kfree(void *ptr); +#define kmalloc(size, flag) _kmalloc(size, flag, __FILE__, __LINE__) +#define kfree(ptr) _kfree(ptr, __FILE__, __LINE__) +#define memcheck(ptr, msg) _memcheck(ptr, msg, __FILE__, __LINE__, 0) +void *_kmalloc(int size, enum ihk_mc_ap_flag flag, char *file, int line); +void _kfree(void *ptr, char *file, int line); +void *__kmalloc(int size, enum ihk_mc_ap_flag flag); +void __kfree(void *ptr); +void *___kmalloc(int size, enum ihk_mc_ap_flag flag); +void ___kfree(void *ptr); + +int _memcheck(void *ptr, char *msg, char *file, int line, int free); +int memcheckall(); +int freecheck(int runcount); #endif diff --git a/kernel/mem.c b/kernel/mem.c index 5aea9dac..eff9ccdb 100644 --- a/kernel/mem.c +++ b/kernel/mem.c @@ -35,6 +35,8 @@ #include #include #include +#include +#include //#define DEBUG_PRINT_MEM @@ -522,10 +524,18 @@ static void page_init(void) return; } +static char *memdebug = NULL; + void register_kmalloc(void) { - allocator.alloc = kmalloc; - allocator.free = kfree; + if(memdebug){ + allocator.alloc = __kmalloc; + allocator.free = __kfree; + } + else{ + allocator.alloc = ___kmalloc; + allocator.free = ___kfree; + } } static struct ihk_page_allocator_desc *vmap_allocator; @@ -640,26 +650,281 @@ void mem_init(void) virtual_allocator_init(); } +struct location { + struct location *next; + int line; + int cnt; + char file[0]; +}; + +struct alloc { + struct alloc *next; + struct malloc_header *p; + struct location *loc; + int size; + int runcount; +}; + +#define HASHNUM 129 + +static struct alloc *allochash[HASHNUM]; +static struct location *lochash[HASHNUM]; +static ihk_spinlock_t alloclock; +int runcount; +static unsigned char *page; +static int space; + +static void *dalloc(unsigned long size) +{ + void *r; + static int pos = 0; + unsigned long irqstate; + + irqstate = ihk_mc_spinlock_lock(&alloclock); + size = (size + 7) & 0xfffffffffffffff8L; + if (pos + size > space) { + page = allocate_pages(1, IHK_MC_AP_NOWAIT); + space = 4096; + pos = 0; + } + r = page + pos; + pos += size; + ihk_mc_spinlock_unlock(&alloclock, irqstate); + + return r; +} + +void *_kmalloc(int size, enum ihk_mc_ap_flag flag, char *file, int line) +{ + char *r = ___kmalloc(size, flag); + struct malloc_header *h; + unsigned long hash; + char *t; + struct location *lp; + struct alloc *ap; + unsigned long alcsize; + unsigned long chksize; + + if (!memdebug) + return r; + + if (!r) + return r; + + h = ((struct malloc_header *)r) - 1; + alcsize = h->size * sizeof(struct malloc_header); + chksize = alcsize - size; + memset(r + size, '\x5a', chksize); + + for (hash = 0, t = file; *t; t++) { + hash <<= 1; + hash += *t; + } + hash += line; + hash %= HASHNUM; + for (lp = lochash[hash]; lp; lp = lp->next) + if (lp->line == line && + !strcmp(lp->file, file)) + break; + if (!lp) { + lp = dalloc(sizeof(struct location) + strlen(file) + 1); + memset(lp, '\0', sizeof(struct location)); + lp->line = line; + strcpy(lp->file, file); + do { + lp->next = lochash[hash]; + } while (!compare_and_swap(lochash + hash, (unsigned long)lp->next, (unsigned long)lp)); + } + + hash = (unsigned long)h % HASHNUM; + do { + for (ap = allochash[hash]; ap; ap = ap->next) + if (!ap->p) + break; + } while (ap && !compare_and_swap(&ap->p, 0UL, (unsigned long)h)); + if (!ap) { + ap = dalloc(sizeof(struct alloc)); + memset(ap, '\0', sizeof(struct alloc)); + ap->p = h; + do { + ap->next = allochash[hash]; + } while (!compare_and_swap(allochash + hash, (unsigned long)ap->next, (unsigned long)ap)); + } + + ap->loc = lp; + ap->size = size; + ap->runcount = runcount; + + return r; +} + +int _memcheck(void *ptr, char *msg, char *file, int line, int flags) +{ + struct malloc_header *h = ((struct malloc_header *)ptr) - 1; + struct malloc_header *next; + unsigned long hash = (unsigned long)h % HASHNUM; + struct alloc *ap; + static unsigned long check = 0x5a5a5a5a5a5a5a5aUL; + unsigned long alcsize; + unsigned long chksize; + + + if (h->check != 0x5a5a5a5a) { + int i; + unsigned long max = 0; + unsigned long cur = (unsigned long)h; + struct alloc *maxap = NULL; + + for (i = 0; i < HASHNUM; i++) + for (ap = allochash[i]; ap; ap = ap->next) + if ((unsigned long)ap->p < cur && + (unsigned long)ap->p > max) { + max = (unsigned long)ap->p; + maxap = ap; + } + + kprintf("%s: detect buffer overrun, alc=%s:%d size=%ld h=%p, s=%ld\n", msg, maxap->loc->file, maxap->loc->line, maxap->size, maxap->p, maxap->p->size); + kprintf("broken header: h=%p next=%p size=%ld cpu_id=%d\n", h, h->next, h->size, h->cpu_id); + } + + for (ap = allochash[hash]; ap; ap = ap->next) + if (ap->p == h) + break; + if (!ap) { + if(file) + kprintf("%s: address not found, %s:%d p=%p\n", msg, file, line, ptr); + else + kprintf("%s: address not found p=%p\n", msg, ptr); + return 1; + } + + alcsize = h->size * sizeof(struct malloc_header); + chksize = alcsize - ap->size; + if (chksize > 8) + chksize = 8; + next = (struct malloc_header *)((char *)ptr + alcsize); + + if (next->check != 0x5a5a5a5a || + memcmp((char *)ptr + ap->size, &check, chksize)) { + unsigned long buf = 0x5a5a5a5a5a5a5a5aUL; + unsigned char *p; + unsigned char *q; + memcpy(&buf, (char *)ptr + ap->size, chksize); + p = (unsigned char *)&(next->check); + q = (unsigned char *)&buf; + + if (file) + kprintf("%s: broken, %s:%d alc=%s:%d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x size=%ld\n", msg, file, line, ap->loc->file, ap->loc->line, q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7], p[0], p[1], p[2], p[3], ap->size); + else + kprintf("%s: broken, alc=%s:%d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x size=%ld\n", msg, ap->loc->file, ap->loc->line, q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7], p[0], p[1], p[2], p[3], ap->size); + + + if (next->check != 0x5a5a5a5a) + kprintf("next->HEADER: next=%p size=%ld cpu_id=%d\n", next->next, next->size, next->cpu_id); + + return 1; + } + + if(flags & 1){ + ap->p = NULL; + ap->loc = NULL; + ap->size = 0; + } + return 0; +} + +int memcheckall() +{ + int i; + struct alloc *ap; + int r = 0; + +kprintf("memcheckall\n"); + for(i = 0; i < HASHNUM; i++) + for(ap = allochash[i]; ap; ap = ap->next) + if(ap->p) + r |= _memcheck(ap->p + 1, "memcheck", NULL, 0, 2); +kprintf("done\n"); + return r; +} + +int freecheck(int runcount) +{ + int i; + struct alloc *ap; + struct location *lp; + int r = 0; + + for (i = 0; i < HASHNUM; i++) + for (lp = lochash[i]; lp; lp = lp->next) + lp->cnt = 0; + + for (i = 0; i < HASHNUM; i++) + for (ap = allochash[i]; ap; ap = ap->next) + if (ap->p && ap->runcount == runcount) { + ap->loc->cnt++; + r++; + } + + if (r) { + kprintf("memory leak?\n"); + for (i = 0; i < HASHNUM; i++) + for (lp = lochash[i]; lp; lp = lp->next) + if (lp->cnt) + kprintf(" alc=%s:%d cnt=%d\n", lp->file, lp->line, lp->cnt); + } + + return r; +} + +void _kfree(void *ptr, char *file, int line) +{ + if (memdebug) + _memcheck(ptr, "KFREE", file, line, 1); + ___kfree(ptr); +} + +void *__kmalloc(int size, enum ihk_mc_ap_flag flag) +{ + return kmalloc(size, flag); +} + +void __kfree(void *ptr) +{ + kfree(ptr); +} + void kmalloc_init(void) { struct cpu_local_var *v = get_this_cpu_local_var(); struct malloc_header *h = &v->free_list; ihk_mc_spinlock_init(&v->free_list_lock); + int i; + h->check = 0x5a5a5a5a; h->next = &v->free_list; h->size = 0; register_kmalloc(); + + memdebug = find_command_line("memdebug"); + for (i = 0; i < HASHNUM; i++) { + allochash[i] = NULL; + lochash[i] = NULL; + } + page = allocate_pages(16, IHK_MC_AP_NOWAIT); + space = 16 * 4096; + ihk_mc_spinlock_init(&alloclock); } -void *kmalloc(int size, enum ihk_mc_ap_flag flag) + +void *___kmalloc(int size, enum ihk_mc_ap_flag flag) { struct cpu_local_var *v = get_this_cpu_local_var(); struct malloc_header *h = &v->free_list, *prev, *p; int u, req_page; unsigned long flags; - if (size >= PAGE_SIZE * 4) { return NULL; } @@ -679,10 +944,12 @@ void *kmalloc(int size, enum ihk_mc_ap_flag flag) h = allocate_pages(req_page, flag); if(h == NULL) return NULL; + h->check = 0x5a5a5a5a; prev->next = h; h->size = (req_page * PAGE_SIZE) / sizeof(*h) - 2; /* Guard entry */ p = h + h->size + 1; + p->check = 0x5a5a5a5a; p->next = &v->free_list; p->size = 0; h->next = p; @@ -699,6 +966,7 @@ void *kmalloc(int size, enum ihk_mc_ap_flag flag) h->size -= u + 1; p = h + h->size + 1; + p->check = 0x5a5a5a5a; p->size = u; p->cpu_id = ihk_mc_get_processor_id(); @@ -711,7 +979,7 @@ void *kmalloc(int size, enum ihk_mc_ap_flag flag) } } -void kfree(void *ptr) +void ___kfree(void *ptr) { struct malloc_header *p = (struct malloc_header *)ptr; struct cpu_local_var *v = get_cpu_local_var((--p)->cpu_id); @@ -729,12 +997,15 @@ void kfree(void *ptr) if (h + h->size + 1 == p && h->size != 0) { combined = 1; h->size += p->size + 1; + h->check = 0x5a5a5a5a; } if (h->next == p + p->size + 1 && h->next->size != 0) { if (combined) { + h->check = 0x5a5a5a5a; h->size += h->next->size + 1; h->next = h->next->next; } else { + p->check = 0x5a5a5a5a; p->size += h->next->size + 1; p->next = h->next->next; h->next = p;