Date: Sat, 02 Jun 2001 15:01:54 +0300 From: "Eli Zaretskii" Sender: halo1 AT zahav DOT net DOT il To: djgpp-workers AT delorie DOT com Message-Id: <9003-Sat02Jun2001150153+0300-eliz@is.elta.co.il> X-Mailer: Emacs 20.6 (via feedmail 8.3.emacs20_6 I) and Blat ver 1.8.9 Subject: malloc debugging facilities Reply-To: djgpp-workers AT delorie DOT com The changes below add some simple diagnostics and debugging facilities to the memory-allocation routines. They also implement functions for reporting the amount of heap that is free and in use, which is something quite a few users asked about over the years. The API is modeled after similar functionality on Unix and GNU/Linux systems; adding Borland-compatible coreleft, heapwalk, heapcheck, etc. is left as an exercise. Comments? Index: include/stdlib.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/stdlib.h,v retrieving revision 1.8 diff -u -r1.8 stdlib.h --- include/stdlib.h 2001/01/20 22:04:14 1.8 +++ include/stdlib.h 2001/06/02 11:36:55 @@ -1,3 +1,4 @@ +/* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ @@ -143,6 +144,31 @@ #define __system_emulate_chdir 0x4000 /* handle `cd' internally */ extern int __system_flags; + +extern void (*__libc_malloc_hook)(size_t, void *); +extern void (*__libc_malloc_fail_hook)(size_t); +extern void (*__libc_free_hook)(void *); +extern void (*__libc_free_null_hook)(void); +extern void (*__libc_realloc_hook)(void *, size_t); + +struct mallinfo { + int arena; + int ordblks; + int smblks; + int hblks; + int hblkhd; + int usmblks; + int fsmblks; + int uordblks; + int fordblks; + int keepcost; +}; + +struct mallinfo mallinfo(void); + +int malloc_verify(void); +int malloc_debug(int); +void mallocmap(void); #endif /* !_POSIX_SOURCE */ #endif /* !__STRICT_ANSI__ */ Index: include/libc/malloc.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/libc/malloc.h,v retrieving revision 1.1 diff -u -r1.1 malloc.h --- include/libc/malloc.h 2001/01/20 04:08:38 1.1 +++ include/libc/malloc.h 2001/06/02 11:37:04 @@ -24,7 +24,11 @@ #define AFTER(bp) ((BLOCK *)((char *)bp + ((bp->size & ~1) + 8))) #define DATA(bp) ((char *)&(bp->next)) +#ifndef NUMSMALL +#define NUMSMALL 0 +#endif #define ALIGN 8 +#define SMALL (NUMSMALL*ALIGN) #endif /* !_POSIX_SOURCE */ #endif /* !__STRICT_ANSI__ */ Index: src/docs/kb/wc204.txi =================================================================== RCS file: /cvs/djgpp/djgpp/src/docs/kb/wc204.txi,v retrieving revision 1.71 diff -u -p -r1.71 wc204.txi --- src/docs/kb/wc204.txi 2001/06/01 18:04:29 1.71 +++ src/docs/kb/wc204.txi 2001/06/02 11:38:26 @@ -433,3 +433,17 @@ Two new functions, @code{monstartup} and control when profiling starts and ends, and what range of addresses is recorded in the profiling data. +@findex mallinfo AT r{, added} +@findex malloc_verify AT r{, added} +@findex malloc_debug AT r{, added} +@findex mallocmap AT r{, added} +@findex malloc AT r{, debug facilities} +There are new @code{malloc} debug facilities. While not as powerful and +versatile as existing external packages, such as @acronym{YAMD}, these +facilities do allow to detect a wide variety of heap corruption +problems, and to report important heap usage statistics. The new +functions are @code{mallinfo}, @code{malloc_debug}, +@code{malloc_verify}, and @code{mallocmap}; these names are compatible +with many Unix and GNU/Linux systems. In addition, special hooks, such +as @code{__libc_malloc_hook} and @code{__libc_free_hook}, are provided +for building custom @code{malloc} debugging packages. Index: src/libc/ansi/stdlib/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/stdlib/makefile,v retrieving revision 1.3 diff -u -p -r1.3 makefile --- src/libc/ansi/stdlib/makefile 1998/01/02 01:06:08 1.3 +++ src/libc/ansi/stdlib/makefile 2001/06/02 11:38:28 @@ -20,6 +20,7 @@ SRC += ldiv.c SRC += llabs.c SRC += lldiv.c SRC += malloc.c +SRC += malldbg.c SRC += qsort.c SRC += rand.c SRC += strtod.c Index: src/libc/ansi/stdlib/malloc.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/stdlib/malloc.c,v retrieving revision 1.9 diff -u -p -r1.9 malloc.c --- src/libc/ansi/stdlib/malloc.c 2001/05/31 18:21:04 1.9 +++ src/libc/ansi/stdlib/malloc.c 2001/06/02 11:38:30 @@ -9,20 +9,17 @@ #include #include #include +#include "xmalloc.h" -#define NUMSMALL 0 -#define SMALL (NUMSMALL*ALIGN) - static BLOCK *slop = 0; static BLOCK *freelist[30]; #if NUMSMALL static BLOCK *smallblocks[NUMSMALL]; #endif - -#define MIN_SAVE_EXTRA 64 -#define BIG_BLOCK 4096 -#define DEBUG 0 +static unsigned long malloc_bytes_in_use; +static unsigned long malloc_chunks_in_use; +static unsigned long malloc_sbrked; #if DEBUG static void @@ -49,27 +46,6 @@ consistency() #define CHECK(p) #endif -static inline int -size2bucket(size_t size) -{ - int rv=0; - size>>=2; - while (size) - { - rv++; - size>>=1; - } - return rv; -} - -static inline int -b2bucket(BLOCK *b) -{ - if (b->bucket == -1) - b->bucket = size2bucket(b->size); - return b->bucket; -} - static inline BLOCK * split_block(BLOCK *b, size_t size) { @@ -87,8 +63,65 @@ split_block(BLOCK *b, size_t size) CHECK(rv); return rv; } + +/* These hooks can be used to gain access to the innards of memory + allocation routines without bloating each program's size (since + malloc is called from the startup code) or adverse effects on + run-time performance of programs which don't call these hooks or + malloc debug routines (which call them internally). */ +BLOCK ** +__malloc_get_freelist(void) +{ + return freelist; +} + +BLOCK * +__malloc_get_slop(void) +{ + return slop; +} + +#if NUMSMALL +BLOCK ** +__malloc_get_smallblocks(void) +{ + return smallblocks; +} +#endif + +unsigned long +__malloc_get_bytes_in_use(void) +{ + return malloc_bytes_in_use; +} + +unsigned long +__malloc_get_chunks_in_use(void) +{ + return malloc_chunks_in_use; +} + +unsigned long +__malloc_get_sbrked(void) +{ + return malloc_sbrked; +} + +void (*__libc_malloc_hook)(size_t, void *); +void (*__libc_malloc_fail_hook)(size_t); +void (*__libc_free_hook)(void *); +void (*__libc_free_null_hook)(void); +void (*__libc_realloc_hook)(void *, size_t); -#define RET(rv) CHECK(rv); ENDSZ(rv) |= 1; rv->size |= 1; return DATA(rv) +#define RET(rv) \ + do { \ + CHECK(rv); \ + ENDSZ(rv) |= 1; \ + malloc_bytes_in_use += rv->size; \ + malloc_chunks_in_use++; \ + rv->size |= 1; \ + return DATA(rv); \ + } while (0) void * malloc(size_t size) @@ -110,6 +143,10 @@ malloc(size_t size) if (rv) { smallblocks[size/ALIGN] = rv->next; + if (__libc_malloc_hook) + __libc_malloc_hook(size, rv); + malloc_bytes_in_use += rv->size; + malloc_chunks_in_use++; return DATA(rv); } } @@ -130,6 +167,8 @@ malloc(size_t size) } else slop = 0; + if (__libc_malloc_hook) + __libc_malloc_hook(size, rv); RET(rv); } @@ -140,6 +179,8 @@ malloc(size_t size) if (rv->size >= size && rv->size < size+size/4) { *prev = rv->next; + if (__libc_malloc_hook) + __libc_malloc_hook(size, rv); RET(rv); } } @@ -177,6 +218,8 @@ malloc(size_t size) printf(" slop size %u/%08x\n", slop->size, slop); #endif } + if (__libc_malloc_hook) + __libc_malloc_hook(size, rv); RET(rv); } b++; @@ -185,7 +228,12 @@ malloc(size_t size) chunk_size = size+16; /* two ends plus two placeholders */ rv = (BLOCK *)sbrk(chunk_size); if (rv == (BLOCK *)(-1)) + { + if (__libc_malloc_fail_hook) + __libc_malloc_fail_hook(size); return 0; + } + malloc_sbrked += chunk_size; #if DEBUG printf("sbrk(%d) -> %08x, expected %08x\n", chunk_size, rv, expected_sbrk); #endif @@ -214,6 +262,8 @@ malloc(size_t size) AFTER(rv)->size = 1; CHECK(rv); + if (__libc_malloc_hook) + __libc_malloc_hook(size, rv); RET(rv); } @@ -289,19 +339,29 @@ free(void *ptr) { BLOCK *block; if (ptr == 0) + { + if (__libc_free_null_hook) + __libc_free_null_hook(); return; + } block = (BLOCK *)((char *)ptr-4); + if (__libc_free_hook) + __libc_free_hook(block); #if NUMSMALL if (block->size < SMALL) { block->next = smallblocks[block->size/ALIGN]; smallblocks[block->size/ALIGN] = block; + malloc_bytes_in_use -= block->size; + malloc_chunks_in_use--; return; } #endif block->size &= ~1; + malloc_bytes_in_use -= block->size; + malloc_chunks_in_use--; ENDSZ(block) &= ~1; block->bucket = -1; #if DEBUG @@ -376,6 +436,7 @@ realloc_inplace(BLOCK *cur, size_t old_s ENDSZ(after2) = after2->size; cur->size += alloc_delta; ENDSZ(cur) = cur->size; + malloc_bytes_in_use += alloc_delta; if (is_slop_ptr) slop = after2; else @@ -386,6 +447,7 @@ realloc_inplace(BLOCK *cur, size_t old_s /* Merge the entire free block with the block being expanded. */ cur->size += after_sz + 8; ENDSZ(cur) = cur->size; + malloc_bytes_in_use += after_sz + 8; if (is_slop_ptr) slop = 0; } @@ -403,6 +465,8 @@ realloc(void *ptr, size_t size) return malloc(size); b = (BLOCK *)((char *)ptr-4); + if (__libc_realloc_hook) + __libc_realloc_hook(b, size); copysize = b->size & ~1; if (size <= copysize) { --- /dev/null Sat Jun 2 14:54:59 2001 +++ src/libc/ansi/stdlib/malldbg.c Sat Jun 2 14:20:18 2001 @@ -0,0 +1,468 @@ +/* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ + +#include +#include +#include +#include +#include + +#include "xmalloc.h" + +struct mallinfo +mallinfo(void) +{ + struct mallinfo info; + int b; + BLOCK *block, **free_list, *slop; + unsigned long free_size = 0; +#if NUMSMALL + BLOCK **small_blocks; +#endif + + info.arena = __malloc_get_sbrked(); + + info.ordblks = __malloc_get_chunks_in_use(); + /* To the chunks in use add the number of blocks on the free list + and the slop, if non-NULL. */ + free_list = __malloc_get_freelist(); + for (b = 0; b < 30; b++) + for (block = free_list[b]; block; block = block->next) + { + info.ordblks++; + free_size += block->size; + } + slop = __malloc_get_slop(); + if (slop) + { + info.ordblks++; + free_size += slop->size; + } + + info.smblks = info.hblks = info.hblkhd = info.usmblks = info.fsmblks = 0; +#if NUMSMALL + small_blocks = __malloc_get_smallblocks(); + for (b = 0; b < NUMSMALL; b++) + for (block = small_blocks[b]; block; block = block->next) + { + info.smblks++; + info.fsmblks += block->size; + } +#endif + + info.uordblks = __malloc_get_bytes_in_use(); + info.fordblks = free_size; + info.keepcost = 0; /* we don't support mallopt(M_KEEP, ...) */ + + return info; +} + +static int malloc_debug_level; + +static BLOCK **recorded_blocks; +static int n_recorded_blocks; + +static inline int +check_block(BLOCK *b) +{ + size_t size, endsz; + size_t *endsz_ptr; + extern unsigned __djgpp_selector_limit; + + if (!b) + { + if (malloc_debug_level) + fprintf(stderr, "Corrupted heap block: addr=0x00000000\n"); + if (malloc_debug_level > 2) + abort(); + return 0; + } + + if ((((unsigned)b + 4) & 3) != 0) + { + if (malloc_debug_level) + fprintf(stderr, "Heap block 0x%08x: address incorrectly aligned\n", + (unsigned)b); + if (malloc_debug_level > 2) + abort(); + return 0; + } + + size = b->size; + endsz_ptr = (size_t *)((char *)b + (b->size & ~1) + 4); + if ((unsigned)endsz_ptr < 0x1000 + || (unsigned)endsz_ptr >= __djgpp_selector_limit) + { + if (malloc_debug_level) + { + fprintf(stderr, + "Bad size info in heap block: addr=0x%08x size=0x%08x\n", + (unsigned)b, (unsigned)endsz_ptr); + } + if (malloc_debug_level > 2) + abort(); + return 0; + } + else + endsz = *endsz_ptr; + if (size != endsz) + { + if (malloc_debug_level) + { + fprintf(stderr, + "Corrupted heap block: addr=0x%08x size=0x%08x ENDSZ=0x%08x\n", + (unsigned)b, (unsigned)size, (unsigned)endsz); + } + if (malloc_debug_level > 2) + abort(); + return 0; + } + return 1; +} + +int +malloc_verify(void) +{ + BLOCK *b, **free_list; +#if NUMSMALL + BLOCK **small_blks; +#endif + int i, result = 1; + extern unsigned __djgpp_selector_limit; + BLOCK *slop = __malloc_get_slop(); + + if (slop + && ((unsigned)slop < 0x1000 + || (unsigned)slop >= __djgpp_selector_limit + || (((unsigned)slop + 4) & 3) != 0)) + { + result = 0; + if (malloc_debug_level) + { + fprintf(stderr, "Bad slop 0x%08x\n", (unsigned)slop); + if (malloc_debug_level > 2) + abort(); + } + } + else if (slop) + result &= check_block(slop); + + free_list = __malloc_get_freelist(); + if (free_list) + { + for (i = 0; i < 30; i++) + for (b = free_list[i]; b; b = b->next) + { + if ((unsigned)b < 0x1000 || (unsigned)b >= __djgpp_selector_limit + || (((unsigned)b + 4) & 3) != 0) + { + result = 0; + if (malloc_debug_level) + { + if ((((unsigned)b + 4) & 3) != 0) + fprintf(stderr, + "Free block 0x%08x: address incorrectly aligned\n", + (unsigned)b); + else + fprintf(stderr, + "Free block 0x%08x: address out of valid range\n", + (unsigned)b); + if (malloc_debug_level > 2) + abort(); + else + break; /* we cannot use b->next, so go to next bucket */ + } + } + else + result &= check_block(b); + } + } + +#if NUMSMALL + small_blks = __malloc_get_smallblocks(); + if (small_blks) + { + for (i = 0; i < NUMSMALL; i++) + for (b = small_blks[i]; b; b = b->next) + { + if ((unsigned)b < 0x1000 || (unsigned)b >= __djgpp_selector_limit + || (((unsigned)b + 4) & 3) != 0) + { + result = 0; + if (malloc_debug_level) + { + if ((((unsigned)b + 4) & 3) != 0) + fprintf(stderr, + "Free small block 0x%08x: address incorrectly aligned\n", + (unsigned)b); + else + fprintf(stderr, + "Free small block 0x%08x: address out of valid range\n", + (unsigned)b); + if (malloc_debug_level > 2) + abort(); + else + break; /* we cannot use b->next, so go to next bucket */ + } + } + else + result &= check_block(b); + } + } +#endif + + if (recorded_blocks) + for (i = 0; i < n_recorded_blocks; i++) + if (recorded_blocks[i]) + result &= check_block(recorded_blocks[i]); + + return result; +} + +static int next_vacant; + +static void +note_malloc(size_t size, void *ptr) +{ + int i; + BLOCK *b = ptr; + extern unsigned __djgpp_selector_limit; + + /* Record the allocated block. */ + if (recorded_blocks) + { + /* See if we can find a vacant cell using the hint from the last + invocation. */ + if (!recorded_blocks[next_vacant]) + { + recorded_blocks[next_vacant++] = b; + if (next_vacant >= n_recorded_blocks) + next_vacant = 0; + i = 0; + } + else + { + for (i = 0; i < n_recorded_blocks; i++) + if (!recorded_blocks[i]) + { + recorded_blocks[i] = b; + next_vacant = i + 1; + if (next_vacant >= n_recorded_blocks) + next_vacant = 0; + break; + } + } + + if (i == n_recorded_blocks && malloc_debug_level > 2) + fprintf(stderr, "No space to record block 0x%08x of size 0x%08x\n", + (unsigned)b, (unsigned)size); + } + + if (malloc_debug_level > 1) + { + if ((unsigned)b < 0x1000 || (unsigned)b >= __djgpp_selector_limit) + { + fprintf(stderr, "Block address 0x%08x out of range\n", + (unsigned)b); + if (malloc_debug_level > 2) + abort(); + } + malloc_verify(); + } +} + +static void +note_free(void *ptr) +{ + BLOCK *b = ptr; + int i; + + /* Remove this block from the list of recorded blocks. */ + if (recorded_blocks) + { + /* Check the cached vacant position, in case they are freeing + memory in the same or reverse order as they allocated it. */ + if (next_vacant < n_recorded_blocks - 1 + && recorded_blocks[next_vacant + 1] == b) + i = next_vacant + 1; + else if (next_vacant > 0 && recorded_blocks[next_vacant - 1] == b) + i = next_vacant - 1; + else if (recorded_blocks[next_vacant] == b) + i = next_vacant; + else + i = 0; + for ( ; i < n_recorded_blocks; i++) + if (recorded_blocks[i] == b) + { + recorded_blocks[i] = NULL; + next_vacant = i; + break; + } + + if (i == n_recorded_blocks && malloc_debug_level > 1) + { + fprintf(stderr, + "Block 0x%08x freed, but is not known to be allocated\n", + (unsigned)ptr); + if (malloc_debug_level > 2) + abort(); + } + } + + if (malloc_debug_level > 1) + malloc_verify(); +} + +static void +note_malloc_fail(size_t size) +{ + fprintf(stderr, "Allocation failed for size 0x%08x\n", (unsigned)size); +} + +static void +note_free_null(void) +{ + fprintf(stderr, "NULL pointer free'd\n"); +} + +static void +note_realloc(void *ptr, size_t size) +{ + if (malloc_debug_level > 1) + { + BLOCK *b = ptr; + extern unsigned __djgpp_selector_limit; + + if ((unsigned)b < 0x1000 || (unsigned)b >= __djgpp_selector_limit) + { + fprintf(stderr, "Block address 0x%08x out of range\n", (unsigned)b); + if (malloc_debug_level > 2) + abort(); + } + else if ((((unsigned)b + 4) & 3) != 0) + { + fprintf(stderr, "Block address 0x%08x incorrectly aligned\n", + (unsigned)b); + if (malloc_debug_level > 2) + abort(); + } + } +} + +int +malloc_debug(int level) +{ + int old_level = malloc_debug_level; + static char debug_linebuf[160]; /* enough? */ + + malloc_debug_level = level; + + if (malloc_debug_level) + { + /* Make sure we can use fprintf to stderr in the hooks without a + risk of reentering malloc. */ + if (stderr->_base == NULL && (stderr->_flag & _IONBF) == 0) + if (setvbuf(stderr, debug_linebuf, _IOLBF, sizeof(debug_linebuf))) + abort(); + + __libc_malloc_hook = note_malloc; + __libc_free_hook = note_free; + __libc_realloc_hook = note_realloc; + + if (!recorded_blocks) + { + char *mdinfo = getenv("MALLOC_DEBUG"), *endp; + if (mdinfo) + { + n_recorded_blocks = strtol(mdinfo, &endp, 0); + if (n_recorded_blocks < 0 || n_recorded_blocks > 16*1024*1024) + n_recorded_blocks = 0; + } + if (!n_recorded_blocks) + n_recorded_blocks = 100*1024; + + recorded_blocks = (BLOCK **) sbrk(n_recorded_blocks * sizeof(BLOCK *)); + if ((long)recorded_blocks == -1) + { + recorded_blocks = NULL; + n_recorded_blocks = 0; + } + else + memset(recorded_blocks, 0, n_recorded_blocks * sizeof(BLOCK *)); + } + + if (malloc_debug_level > 2) + { + __libc_malloc_fail_hook = note_malloc_fail; + if (malloc_debug_level > 3) + __libc_free_null_hook = note_free_null; + else + __libc_free_null_hook = NULL; + } + else + { + __libc_malloc_fail_hook = NULL; + __libc_free_null_hook = NULL; + } + } + else + { + __libc_malloc_hook = NULL; + __libc_free_hook = NULL; + __libc_realloc_hook = NULL; + __libc_malloc_fail_hook = NULL; + __libc_free_null_hook = NULL; + } + return old_level; +} + +static inline void +print_block(BLOCK *b, const char *remark) +{ + if (b) + { + size_t size = b->size; + size_t endsz = *(size_t *)((char *)b + (b->size & ~1) + 4); + + printf(" Addr=0x%08x size=0x%08x ENDSZ=0x%08x %s\n", + (unsigned)b, (unsigned)size, (unsigned)endsz, remark); + } +} + +void +mallocmap(void) +{ + BLOCK *b, *slop, **free_list; +#if NUMSMALL + BLOCK **small_blks; +#endif + int i; + + slop = __malloc_get_slop(); + free_list = __malloc_get_freelist(); + + if (slop) + print_block(slop, "slop"); + + if (free_list) + { + for (i = 0; i < 30; i++) + for (b = free_list[i]; b; b = b->next) + print_block(b, "free"); + } + +#if NUMSMALL + small_blks = __malloc_get_smallblocks(); + if (small_blks) + { + for (i = 0; i < NUMSMALL; i++) + for (b = small_blks[i]; b; b = b->next) + print_block(b, "small"); + } +#endif + + if (recorded_blocks) + { + for (i = 0; i < n_recorded_blocks; i++) + print_block(recorded_blocks[i], "in use"); + } +} --- /dev/null Sat Jun 2 14:55:08 2001 +++ src/libc/ansi/stdlib/xmalloc.h Sat Jun 2 13:33:22 2001 @@ -0,0 +1,34 @@ +/* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ + +#define MIN_SAVE_EXTRA 64 +#define BIG_BLOCK 4096 + +#define DEBUG 0 + +static inline int +size2bucket(size_t size) +{ + int rv=0; + size>>=2; + while (size) + { + rv++; + size>>=1; + } + return rv; +} + +static inline int +b2bucket(BLOCK *b) +{ + if (b->bucket == -1) + b->bucket = size2bucket(b->size); + return b->bucket; +} + +BLOCK ** __malloc_get_freelist(void); +BLOCK * __malloc_get_slop(void); +BLOCK ** __malloc_get_smallblocks(void); +unsigned long __malloc_get_bytes_in_use(void); +unsigned long __malloc_get_chunks_in_use(void); +unsigned long __malloc_get_sbrked(void); Index: src/libc/ansi/stdlib/malloc.txh =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/stdlib/malloc.txh,v retrieving revision 1.5 diff -u -p -r1.5 malloc.txh --- src/libc/ansi/stdlib/malloc.txh 2001/01/24 14:01:13 1.5 +++ src/libc/ansi/stdlib/malloc.txh 2001/06/02 11:38:32 @@ -113,3 +113,356 @@ if (now+new > max) @} @end example +@c ---------------------------------------------------------------------- + +@node mallinfo, memory +@subheading Syntax + +@example +#include + +struct mallinfo mallinfo(void); +@end example + +@subheading Description + +This function returns information about heap space usage. It is +intended to be used for debugging dynamic memory allocation and tracking +heap usage. The @code{struct mallinfo} structure is defined by +@file{stdlib.h} as follows: + +@example + struct mallinfo @{ + int arena; + int ordblks; + int smblks; + int hblks; + int hblkhd; + int usmblks; + int fsmblks; + int uordblks; + int fordblks; + int keepcost; + @}; +@end example + +@noindent +whose members are: + +@table @code +@item arena +The total amount of space, in bytes, handed by @code{sbrk} to +@code{malloc}. Note that this is not the same as @code{sbrk(0)}, since +@code{sbrk} allocates memory in large chunks and then subdivides them +and passes them to @code{malloc} as required. In particular, the result +of @code{sbrk(0)} might be much larger than the @code{arena} member of +@code{struct mallinfo} when the DPMI host allocates memory in +non-contiguous regions (happens on MS-Windows). + +@item ordblks +The number of ``ordinary blocks'': the total number of allocated and +free blocks maintained by @code{malloc}. + +@item smblks +The number of ``small blocks''. This is normally zero, unless +@code{malloc} was compiled with the symbol @code{NUMSMALL} defined to a +non-zero value. Doing so activates an optional algorithm which serves +small allocations quickly from a special pool. If this option is +activated, the @code{smblks} member returns the number of free small +blocks (the allocated small blocks are included in the value of +@code{ordblks}). + +@item hblks +@itemx hblkhd +Always zero, kept for compatibility with other systems. + +@item usmblks +The space (in bytes) in ``small blocks'' that are in use. This is +always zero in the DJGPP implementation. + +@item fsmblks +The space in free ``small blocks''. Non-zero only of @code{malloc} was +compiled with @code{NUMSMALL} defined to a non-zero value. In that +case, gives the amount of space in bytes in free small blocks. + +@item uordblks +The amount of space, in bytes, in the heap space currently used by the +application. This does not include the small overhead (8 bytes per +block) used by @code{malloc} to maintain its hidden information in each +allocated block. + +@item fordblks +The amount of free heap space maintained by @code{malloc} in its free +list. + +@item keepcost +Always zero, kept for compatibility. +@end table + +@subheading Return Value + +The @code{mallinfo} structure filled with information. + +@subheading Portability + +@port-note posix This function is available on many Unix systems. +@portability !ansi, !posix + +@subheading Example + +@example + struct mallinfo info = mallinfo(); + + printf("Memory in use: %d bytes\n", + info.usmblks + info.uordblks); + printf("Total heap size: %d bytes\n", info.arena); +@end example + +@c ---------------------------------------------------------------------- + +@node malloc_verify, memory +@subheading Syntax + +@example +#include + +int malloc_verify(void); +@end example + +@subheading Description + +This function attempts to determine if the heap has been corrupted. It +scans all the blocks allocated by @code{malloc} and handed to the +application, and also all the free blocks maintained by @code{malloc} +and @code{free} in the internal free list. Each block is checked for +consistency of the hidden bookkeeping information recorded in it by +@code{malloc} and @code{free}. The blocks on the free list are +additionally validated by chasing all the @code{next} pointers in the +linked list and checking them against limits for valid pointers (between +0x1000 and the data segment limit), and the alignment. (Unaligned +pointers are probably corrupted, since @code{malloc} always returns a +properly aligned storage.) + +What happens when a bad block is found depends on the current +@dfn{malloc diagnostics level}: for example, the block can be reported, +or the program may be aborted. @xref{malloc_debug}, for the details. + +@subheading Return Value + +If the program isn't aborted during the function's run (this depends on +the current diagnostics level), @code{malloc_verify} returns 1 if the +heap passes all tests, or zero of some of the tests failed. + +@subheading Portability + +@port-note posix This function is available on many Unix systems. +@portability !ansi, !posix + +@subheading Example + +@example + if (malloc_verify() == 0) + printf ("Heap corruption detected!\n"); +@end example + +@c ---------------------------------------------------------------------- + +@node malloc_debug, memory +@subheading Syntax + +@example +#include + +int malloc_debug(int level); +@end example + +@subheading Description + +This function sets the level of error diagnosis and reporting during +subsequent calls to @code{malloc}, @code{free}, @code{realloc}, and all +functions which call them internally. The argument @var{level} is +interpreted as follows: + +@table @asis +@item Level 0 +No checking; the memory allocation functions behave as they do if +@code{malloc_debug} was never called. Memory in use by the application +which was allocated while level 0 was in effect cannot be checked by +@code{malloc_verify} unless it is @code{free}d first. + +@item Level 1 +Each one of the allocated blocks is recorded in a special structure, +where @code{malloc_verify} can test them for corruption, even if these +blocks were not yet @code{free}d. If errors are detected by +@code{malloc_verify}, it prints diagnostic messages to the standard +error stream, with address and size of the offending block and other +pertinent information. This level slows down memory allocation to some +extent due to additional overhead of calling special functions which +record extra debugging info. + +@item Level 2 +Like level 1, but in addition the consistency of the entire heap is +verified (by calling @code{malloc_verify}) on every call to the memory +allocation functions. @emph{Warning: this may significantly slow down +the application.} + +@item Level 3 +Like level 2, except that the program is aborted whenever a heap +corruption is detected. In addition, failed allocations (i.e.@: when +@code{malloc} returns @code{NULL} because it cannot satisfy a request) +are reported to standard error. Also, if the storage where allocated +blocks are recorded is exhausted, a message to that effect is printed. + +@item Level 4 +Like level 3, but calls to @code{free} with a @code{NULL} pointer as an +argument are also reported. +@end table + +When @code{malloc_debug} is first called with a positive argument, it +allocates storage for recording blocks in use. To avoid reentrancy +problems, this storage is allocated via a direct call to @code{sbrk}, +and its size is fixed. The size used to allocate this storage is by +default 400KB, which is enough to record 100 thousand allocated blocks. +You can tailor the size to your needs by setting the environment +variable @code{MALLOC_DEBUG} to the maximum number of blocks you want to +be able to track. (The value of @code{MALLOC_DEBUG} should only be as +large as the maximum number of allocations which is expected to be in +use at any given time, because when a buffer is freed, it is removed +from this storage and its cell can be reused for another allocation.) +Note that the larger this storage size, the more slow-down will your +program experience when the diagnostic level is set to a non-zero value, +since the debugging code needs to search the list of recorded blocks in +use each time you call @code{malloc} or @code{free}. + +@subheading Return Value + +@code{malloc_debug} returns the previous error diagnostic level. The +default level is 0. + +@subheading Portability + +@port-note posix This function is available on many Unix systems. +@portability !ansi, !posix + +@subheading Example + +@example + malloc_debug(2); + ... + malloc_verify(); +@end example + +@c ---------------------------------------------------------------------- + +@node mallocmap, memory +@subheading Syntax + +@example +#include + +void mallocmap(int level); +@end example + +@subheading Description + +This function prints a map of the heap storage to standard output. For +each block, its address and size are printed, as well as an indication +whether it is free or in use. If the @dfn{slop} (a special free block +cached for performance reasons) and the small blocks are available, they +are printed as well (these two are variants of free blocks). Blocks in +use will only be printed if the diagnostic level was set to a non-zero +value by a call to @code{malloc_debug} (@pxref{malloc_debug}), since +otherwise the allocated blocks are not recorded by @code{malloc}. + +@subheading Return Value + +None. + +@subheading Portability + +@port-note posix This function is available on many Unix systems. +@portability !ansi, !posix + +@c ---------------------------------------------------------------------- + +@node malloc hook functions, memory +@subheading Syntax + +@example +#include +#include + +void (*__libc_malloc_hook)(size_t size, void *block); +void (*__libc_malloc_fail_hook)(size_t size); +void (*__libc_free_hook)(void *block); +void (*__libc_free_null_hook)(void); +void (*__libc_realloc_hook)(void *block, size_t size); +@end example + +@subheading Description + +These hooks are provided for building custom @code{malloc} debugging +packages. Such packages typically need to be notified when memory is +allocated and freed by the application, in order to be able to find +memory leaks, code that writes beyond the limits of allocated buffers or +attempts to free buffers which were not allocated by @code{malloc}, etc. +These hooks can be used to define callback functions which will be +called by the library at strategic points. Each callback is only called +if it is non- AT code{NULL}; by default, all of them are initialized to a +@code{NULL} value. + +@table @code +@item __libc_malloc_hook +Called just before a chunk of memory is about to be returned to the +application in response to an allocation request. @var{size} is the +size requested by the application (@strong{not} the actual size of the +allocated buffer, which may be larger). @var{block} is a pointer to the +block that was allocated, which is 4 bytes before the pointer that +@code{malloc} will return to the application; these 4 bytes are used to +record the actual size of the buffer. An additional copy of the block's +size is recorded immediately after the buffer's end. Thus, +@w{@code{*(size_t *)((char *)block + 4 + (BLOCK *)block->size)}} gives +the second copy of the block's size. + +@item __libc_malloc_fail_hook +Called if @code{malloc} failed to find a free block large enough to +satisfy a request, and also failed to obtain additional memory from +@code{sbrk}. @var{size} is the requested allocation size. + +@item __libc_free_hook +Called when a buffer is about to be freed. @var{block} is a pointer 4 +bytes before the address passed to @code{free} by the application, +i.e.@: it is a pointer to the beginning of the size information +maintained before the user buffer. + +@item __libc_free_null_hook +Called whenever a @code{NULL} pointer is passed to @code{free}. +@acronym{ANSI} C specifically rules that this is allowed and should have +no effect, but you might want to catch such cases if your program needs +to be portable to old compilers whose libraries don't like @code{NULL} +pointers in @code{free}. + +@item __libc_realloc_hook +Called at entry to @code{realloc}, before the actual reallocation. +@var{block} is a pointer 4 bytes before the address passed to +@code{free} by the application, i.e.@: it is a pointer to the beginning +of the size information maintained before the user buffer. @var{size} +is the new size requested by the application. (This hook is called +@emph{in addition} to the other hooks which will be called by +@code{free} and @code{malloc} if and when @code{realloc} calls them.) +@end table + +The @code{BLOCK} data type is used by @code{malloc} and @code{free} to +maintain the heap. The only member which is always guaranteed to be +valid is @code{size} (the additional copy of the size, recorded beyond +the buffer's end, is also guaranteed to be valid). The @code{next} +member is valid in all blocks that are part of the free list. This +means that @code{__libc_malloc_hook} can use the @code{next} member, but +@code{__libc_free_hook} cannot. + +@subheading Portability + +@portability !ansi, !posix + +These hooks are specific to DJGPP. + Index: tests/libc/ansi/stdlib/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/ansi/stdlib/makefile,v retrieving revision 1.2 diff -u -p -r1.2 makefile --- tests/libc/ansi/stdlib/makefile 1998/01/01 21:42:08 1.2 +++ tests/libc/ansi/stdlib/makefile 2001/06/02 11:48:55 @@ -6,5 +6,6 @@ SRC += shell.c SRC += strtod.c SRC += system.c SRC += system2.c +SRC += tmalloc.c include $(TOP)/../makefile.inc --- /dev/null Sat Jun 2 14:55:25 2001 +++ tests/libc/ansi/stdlib/tmalloc.c Sat Jun 2 13:02:42 2001 @@ -0,0 +1,114 @@ +/* Testbed for the malloc-debug facilities. */ + +#include +#include +#include + +static void +print_malloc_info (void) +{ + struct mallinfo info = mallinfo (); + + printf (">>> mallinfo reports:\n" + " Arena: %d\n" + " Ordinary blocks: %d\n" + " Small blocks: %d\n" + " Space in small blocks in use: %d\n" + " Space in free small blocks: %d\n" + " Space in ordinary blocks in use: %d\n" + " Space in free ordinary blocks: %d\n", + info.arena, info.ordblks, info.smblks, + info.usmblks, info.fsmblks, info.uordblks, info.fordblks); +} + +#define MAXTBL 1024 +struct alloc { + char *ptr; + size_t size; +}; + +int main (int argc, char *argv[]) +{ + struct alloc table[MAXTBL]; + size_t total = 0; + volatile int i, j; + char buf[80]; + int corrupted_blocks; + + printf ("\n *** Test of malloc debugging facilities ***\n\n"); + sprintf (buf, "MALLOC_DEBUG=%d", MAXTBL * 2); + putenv (buf); + print_malloc_info (); + printf ("\n>>> mallocmap reports (expect to see only free blocks):\n"); + mallocmap (); + + /* First, allocate and then free some memory, to prime the malloc + internal data structures. */ + malloc_debug (1); + for (i = 0; i < MAXTBL; i++) + { + size_t chunk_size = (rand() >> 18) + 10; + + table[i].ptr = malloc (chunk_size); + table[i].size = chunk_size; + if (table[i].ptr == NULL) + { + printf ("!!!malloc failed after %d requests for chunk size %lu%s\n", + i, chunk_size < 1024 ? chunk_size : chunk_size / 1024, + chunk_size < 1024 ? "" : "KB"); + break; + } + total += chunk_size; + } + + printf ("\n=== Application requested %lu bytes in %d allocations\n", + total, i); + print_malloc_info (); + printf ("\n>>> mallocmap reports (expect to see only used blocks):\n"); + mallocmap (); + printf ("\n=== Heap corruption test:\n"); + if (malloc_verify () == 0) + printf ("!!! FAILED (??? better look for bugs...)\n"); + else + printf (">>> PASSED (as expected)\n"); + for (j = 0; j < i; j++) + free (table[j].ptr); + printf ("\n=== Freed everything.\n"); + print_malloc_info (); + + /* Now allocate memory again, and fill each chunk with random + characters, overrunning some of the buffers in the process. */ + printf ("\n=== Corrupt memory test (expect early exit and corrupted block reports).\n"); + malloc_debug (1); + for (i = 0, corrupted_blocks = 0; i < MAXTBL; i++) + { + size_t chunk_size = (rand() >> 18) + 10; + int fill_value = (rand () & 0x7f) + ' '; + size_t n_to_fill = chunk_size + + /* about 1% of corrupted blocks */ + ((rand () > RAND_MAX - RAND_MAX/100) ? 50 : 0); + + fflush (stdout); + table[i].ptr = malloc (chunk_size); + table[i].size = chunk_size; + if (table[i].ptr == NULL) + { + printf ("!!!malloc failed after %d requests for chunk size %lu%s\n", + i, chunk_size < 1024 ? chunk_size : chunk_size / 1024, + chunk_size < 1024 ? "" : "KB"); + break; + } + if (n_to_fill > chunk_size) + corrupted_blocks++; + memset (table[i].ptr, fill_value, n_to_fill); + if (malloc_verify () == 0) + { + printf ("!!!Heap corruption detected after %d allocations\n" + " and %d corrupted blocks; exiting.\n", + i, corrupted_blocks); + return 1; + } + } + + return 0; +}