|  | /* $Id: xmalloc-debug.c,v 1.2 2007-10-04 11:52:03 nicm Exp $ */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> | 
|  | * | 
|  | * 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 <sys/types.h> | 
|  |  | 
|  | #include <ctype.h> | 
|  | #include <dlfcn.h> | 
|  | #include <stdint.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #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 */ |