/* $Id: xmalloc-debug.c,v 1.2 2007-10-04 11:52:03 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef DEBUG #include #include #include #include #include #include #include "tmux.h" /* Single xmalloc allocated block. */ struct xmalloc_blk { void *caller; void *ptr; size_t size; SPLAY_ENTRY(xmalloc_blk) entry; }; /* Splay tree of allocated blocks. */ SPLAY_HEAD(xmalloc_tree, xmalloc_blk); struct xmalloc_tree xmalloc_tree = SPLAY_INITIALIZER(&xmalloc_tree); /* Various statistics. */ size_t xmalloc_allocated; size_t xmalloc_freed; size_t xmalloc_peak; u_int xmalloc_frees; u_int xmalloc_mallocs; u_int xmalloc_reallocs; /* Print function. */ #define XMALLOC_PRINT log_debug /* Bytes of unallocated blocks and number of allocated blocks to show. */ #define XMALLOC_BYTES 8 #define XMALLOC_LINES 32 /* Macro to update peek usage variable. */ #define XMALLOC_UPDATE() do { \ if (xmalloc_allocated - xmalloc_freed > xmalloc_peak) \ xmalloc_peak = xmalloc_allocated - xmalloc_freed; \ } while (0) /* Tree functions. */ int xmalloc_cmp(struct xmalloc_blk *, struct xmalloc_blk *); SPLAY_PROTOTYPE(xmalloc_tree, xmalloc_blk, entry, xmalloc_cmp); SPLAY_GENERATE(xmalloc_tree, xmalloc_blk, entry, xmalloc_cmp); /* Compare two blocks. */ int xmalloc_cmp(struct xmalloc_blk *blk1, struct xmalloc_blk *blk2) { uintptr_t ptr1 = (uintptr_t) blk1->ptr; uintptr_t ptr2 = (uintptr_t) blk2->ptr; if (ptr1 < ptr2) return (-1); if (ptr1 > ptr2) return (1); return (0); } /* Clear statistics and block list; used to start fresh after fork(2). */ void xmalloc_clear(void) { struct xmalloc_blk *blk; xmalloc_allocated = 0; xmalloc_freed = 0; xmalloc_peak = 0; xmalloc_frees = 0; xmalloc_mallocs = 0; xmalloc_reallocs = 0; while (!SPLAY_EMPTY(&xmalloc_tree)) { blk = SPLAY_ROOT(&xmalloc_tree); SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk); free(blk); } } /* Print report of statistics and unfreed blocks. */ void xmalloc_report(pid_t pid, const char *hdr) { struct xmalloc_blk *blk; u_char *iptr; char buf[4 * XMALLOC_BYTES + 1], *optr; size_t len; u_int n; Dl_info info; XMALLOC_PRINT("%s: %ld: allocated=%zu, freed=%zu, difference=%zd, " "peak=%zu", hdr, (long) pid, xmalloc_allocated, xmalloc_freed, xmalloc_allocated - xmalloc_freed, xmalloc_peak); XMALLOC_PRINT("%s: %ld: mallocs=%u, reallocs=%u, frees=%u", hdr, (long) pid, xmalloc_mallocs, xmalloc_reallocs, xmalloc_frees); n = 0; SPLAY_FOREACH(blk, xmalloc_tree, &xmalloc_tree) { n++; if (n >= XMALLOC_LINES) continue; len = blk->size; if (len > XMALLOC_BYTES) len = XMALLOC_BYTES; memset(&info, 0, sizeof info); if (dladdr(blk->caller, &info) == 0) info.dli_sname = info.dli_saddr = NULL; optr = buf; iptr = blk->ptr; for (; len > 0; len--) { if (isascii(*iptr) && !iscntrl(*iptr)) { *optr++ = *iptr++; continue; } *optr++ = '\\'; *optr++ = '0' + ((*iptr >> 6) & 07); *optr++ = '0' + ((*iptr >> 3) & 07); *optr++ = '0' + (*iptr & 07); iptr++; } *optr = '\0'; XMALLOC_PRINT("%s: %ld: %u, %s+0x%02tx: [%p %zu: %s]", hdr, (long) pid, n, info.dli_sname, ((u_char *) blk->caller) - ((u_char *) info.dli_saddr), blk->ptr, blk->size, buf); } XMALLOC_PRINT("%s: %ld: %u unfreed blocks", hdr, (long) pid, n); } /* Record a newly created block. */ void xmalloc_new(void *caller, void *ptr, size_t size) { struct xmalloc_blk *blk; xmalloc_allocated += size; XMALLOC_UPDATE(); if ((blk = malloc(sizeof *blk)) == NULL) abort(); blk->ptr = ptr; blk->size = size; blk->caller = caller; SPLAY_INSERT(xmalloc_tree, &xmalloc_tree, blk); xmalloc_mallocs++; XMALLOC_UPDATE(); } /* Record changes to a block. */ void xmalloc_change(void *caller, void *oldptr, void *newptr, size_t newsize) { struct xmalloc_blk *blk, key; ssize_t change; if (oldptr == NULL) { xmalloc_new(caller, newptr, newsize); return; } key.ptr = oldptr; blk = SPLAY_FIND(xmalloc_tree, &xmalloc_tree, &key); if (blk == NULL) return; change = newsize - blk->size; if (change > 0) xmalloc_allocated += change; else xmalloc_freed -= change; XMALLOC_UPDATE(); SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk); blk->ptr = newptr; blk->size = newsize; blk->caller = caller; SPLAY_INSERT(xmalloc_tree, &xmalloc_tree, blk); xmalloc_reallocs++; XMALLOC_UPDATE(); } /* Record a block free. */ void xmalloc_free(void *ptr) { struct xmalloc_blk *blk, key; key.ptr = ptr; blk = SPLAY_FIND(xmalloc_tree, &xmalloc_tree, &key); if (blk == NULL) return; xmalloc_freed += blk->size; SPLAY_REMOVE(xmalloc_tree, &xmalloc_tree, blk); free(blk); xmalloc_frees++; XMALLOC_UPDATE(); } #endif /* DEBUG */