J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 1999-2002 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | #if defined(DEBUG) |
| 27 | |
| 28 | #include "debug_util.h" |
| 29 | |
| 30 | #define DMEM_MIN(a,b) (a) < (b) ? (a) : (b) |
| 31 | #define DMEM_MAX(a,b) (a) > (b) ? (a) : (b) |
| 32 | |
| 33 | typedef char byte_t; |
| 34 | |
| 35 | static const byte_t ByteInited = '\xCD'; |
| 36 | static const byte_t ByteFreed = '\xDD'; |
| 37 | static const byte_t ByteGuard = '\xFD'; |
| 38 | |
| 39 | enum { |
| 40 | MAX_LINENUM = 50000, /* I certainly hope we don't have source files bigger than this */ |
| 41 | MAX_CHECK_BYTES = 27, /* max bytes to check at start of block */ |
| 42 | MAX_GUARD_BYTES = 8, /* size of guard areas on either side of a block */ |
| 43 | MAX_DECIMAL_DIGITS = 15 |
| 44 | }; |
| 45 | |
| 46 | /* Debug Info Header to precede allocated block */ |
| 47 | typedef struct MemoryBlockHeader { |
| 48 | char filename[FILENAME_MAX+1]; /* filename where alloc occurred */ |
| 49 | int linenumber; /* line where alloc occurred */ |
| 50 | size_t size; /* size of the allocation */ |
| 51 | int order; /* the order the block was allocated in */ |
| 52 | struct MemoryListLink * listEnter; /* pointer to the free list node */ |
| 53 | byte_t guard[MAX_GUARD_BYTES]; /* guard area for underrun check */ |
| 54 | } MemoryBlockHeader; |
| 55 | |
| 56 | /* Tail to follow allocated block */ |
| 57 | typedef struct MemoryBlockTail { |
| 58 | byte_t guard[MAX_GUARD_BYTES]; /* guard area overrun check */ |
| 59 | } MemoryBlockTail; |
| 60 | |
| 61 | /* Linked list of allocated memory blocks */ |
| 62 | typedef struct MemoryListLink { |
| 63 | struct MemoryListLink * next; |
| 64 | MemoryBlockHeader * header; |
| 65 | int freed; |
| 66 | } MemoryListLink; |
| 67 | |
| 68 | /************************************************** |
| 69 | * Global Data structures |
| 70 | */ |
| 71 | static DMemState DMemGlobalState; |
| 72 | extern const DMemState * DMemStatePtr = &DMemGlobalState; |
| 73 | static MemoryListLink MemoryList = {NULL,NULL,FALSE}; |
| 74 | static dmutex_t DMemMutex = NULL; |
| 75 | |
| 76 | /**************************************************/ |
| 77 | |
| 78 | /************************************************* |
| 79 | * Client callback invocation functions |
| 80 | */ |
| 81 | static void * DMem_ClientAllocate(size_t size) { |
| 82 | if (DMemGlobalState.pfnAlloc != NULL) { |
| 83 | return (*DMemGlobalState.pfnAlloc)(size); |
| 84 | } |
| 85 | return malloc(size); |
| 86 | } |
| 87 | |
| 88 | static void DMem_ClientFree(void * ptr) { |
| 89 | if (DMemGlobalState.pfnFree != NULL) { |
| 90 | (*DMemGlobalState.pfnFree)(ptr); |
| 91 | } |
| 92 | free(ptr); |
| 93 | } |
| 94 | |
| 95 | static dbool_t DMem_ClientCheckPtr(void * ptr, size_t size) { |
| 96 | if (DMemGlobalState.pfnCheckPtr != NULL) { |
| 97 | return (*DMemGlobalState.pfnCheckPtr)(ptr, size); |
| 98 | } |
| 99 | return ptr != NULL; |
| 100 | } |
| 101 | |
| 102 | /**************************************************/ |
| 103 | |
| 104 | /************************************************* |
| 105 | * Debug Memory Manager implementation |
| 106 | */ |
| 107 | |
| 108 | static MemoryListLink * DMem_TrackBlock(MemoryBlockHeader * header) { |
| 109 | MemoryListLink * link; |
| 110 | |
| 111 | link = (MemoryListLink *)DMem_ClientAllocate(sizeof(MemoryListLink)); |
| 112 | if (link != NULL) { |
| 113 | link->header = header; |
| 114 | link->header->listEnter = link; |
| 115 | link->next = MemoryList.next; |
| 116 | link->freed = FALSE; |
| 117 | MemoryList.next = link; |
| 118 | } |
| 119 | |
| 120 | return link; |
| 121 | } |
| 122 | |
| 123 | static int DMem_VerifyGuardArea(const byte_t * area) { |
| 124 | int nbyte; |
| 125 | |
| 126 | for ( nbyte = 0; nbyte < MAX_GUARD_BYTES; nbyte++ ) { |
| 127 | if (area[nbyte] != ByteGuard) { |
| 128 | return FALSE; |
| 129 | } |
| 130 | } |
| 131 | return TRUE; |
| 132 | } |
| 133 | |
| 134 | static void DMem_VerifyHeader(MemoryBlockHeader * header) { |
| 135 | DASSERTMSG( DMem_ClientCheckPtr(header, sizeof(MemoryBlockHeader)), "Invalid header" ); |
| 136 | DASSERTMSG( DMem_VerifyGuardArea(header->guard), "Header corruption, possible underwrite" ); |
| 137 | DASSERTMSG( header->linenumber > 0 && header->linenumber < MAX_LINENUM, "Header corruption, bad line number" ); |
| 138 | DASSERTMSG( header->size <= DMemGlobalState.biggestBlock, "Header corruption, block size is too large"); |
| 139 | DASSERTMSG( header->order <= DMemGlobalState.totalAllocs, "Header corruption, block order out of range"); |
| 140 | } |
| 141 | |
| 142 | static void DMem_VerifyTail(MemoryBlockTail * tail) { |
| 143 | DASSERTMSG( DMem_ClientCheckPtr(tail, sizeof(MemoryBlockTail)), "Tail corruption, invalid pointer"); |
| 144 | DASSERTMSG( DMem_VerifyGuardArea(tail->guard), "Tail corruption, possible overwrite" ); |
| 145 | } |
| 146 | |
| 147 | static MemoryBlockHeader * DMem_VerifyBlock(void * memptr) { |
| 148 | MemoryBlockHeader * header; |
| 149 | MemoryBlockTail * tail; |
| 150 | |
| 151 | /* check if the pointer is valid */ |
| 152 | DASSERTMSG( DMem_ClientCheckPtr(memptr, 1), "Invalid pointer"); |
| 153 | |
| 154 | /* check if the block header is valid */ |
| 155 | header = (MemoryBlockHeader *)((byte_t *)memptr - sizeof(MemoryBlockHeader)); |
| 156 | DMem_VerifyHeader(header); |
| 157 | /* check that the memory itself is valid */ |
| 158 | DASSERTMSG( DMem_ClientCheckPtr(memptr, DMEM_MIN(MAX_CHECK_BYTES,header->size)), "Block memory invalid" ); |
| 159 | /* check that the pointer to the alloc list is valid */ |
| 160 | DASSERTMSG( DMem_ClientCheckPtr(header->listEnter, sizeof(MemoryListLink)), "Header corruption, alloc list pointer invalid" ); |
| 161 | /* check the tail of the block for overruns */ |
| 162 | tail = (MemoryBlockTail *) ( (byte_t *)memptr + header->size ); |
| 163 | DMem_VerifyTail(tail); |
| 164 | |
| 165 | return header; |
| 166 | } |
| 167 | |
| 168 | static MemoryBlockHeader * DMem_GetHeader(void * memptr) { |
| 169 | MemoryBlockHeader * header = DMem_VerifyBlock(memptr); |
| 170 | return header; |
| 171 | } |
| 172 | |
| 173 | /* |
| 174 | * Should be called before any other DMem_XXX function |
| 175 | */ |
| 176 | void DMem_Initialize() { |
| 177 | DMemMutex = DMutex_Create(); |
| 178 | DMutex_Enter(DMemMutex); |
| 179 | DMemGlobalState.pfnAlloc = NULL; |
| 180 | DMemGlobalState.pfnFree = NULL; |
| 181 | DMemGlobalState.pfnCheckPtr = NULL; |
| 182 | DMemGlobalState.biggestBlock = 0; |
| 183 | DMemGlobalState.maxHeap = INT_MAX; |
| 184 | DMemGlobalState.totalHeapUsed = 0; |
| 185 | DMemGlobalState.failNextAlloc = FALSE; |
| 186 | DMemGlobalState.totalAllocs = 0; |
| 187 | DMutex_Exit(DMemMutex); |
| 188 | } |
| 189 | |
| 190 | void DMem_Shutdown() { |
| 191 | DMutex_Destroy(DMemMutex); |
| 192 | } |
| 193 | /* |
| 194 | * Allocates a block of memory, reserving extra space at the start and end of the |
| 195 | * block to store debug info on where the block was allocated, it's size, and |
| 196 | * 'guard' areas to catch overwrite/underwrite bugs |
| 197 | */ |
| 198 | void * DMem_AllocateBlock(size_t size, const char * filename, int linenumber) { |
| 199 | MemoryBlockHeader * header; |
| 200 | MemoryBlockTail * tail; |
| 201 | size_t debugBlockSize; |
| 202 | byte_t * memptr = NULL; |
| 203 | |
| 204 | DMutex_Enter(DMemMutex); |
| 205 | if (DMemGlobalState.failNextAlloc) { |
| 206 | /* force an allocation failure if so ordered */ |
| 207 | DMemGlobalState.failNextAlloc = FALSE; /* reset flag */ |
| 208 | goto Exit; |
| 209 | } |
| 210 | |
| 211 | /* allocate a block large enough to hold extra debug info */ |
| 212 | debugBlockSize = sizeof(MemoryBlockHeader) + size + sizeof(MemoryBlockTail); |
| 213 | header = (MemoryBlockHeader *)DMem_ClientAllocate(debugBlockSize); |
| 214 | if (header == NULL) { |
| 215 | goto Exit; |
| 216 | } |
| 217 | |
| 218 | /* add block to list of allocated memory */ |
| 219 | header->listEnter = DMem_TrackBlock(header); |
| 220 | if ( header->listEnter == NULL ) { |
| 221 | goto Exit; |
| 222 | } |
| 223 | |
| 224 | /* store size of requested block */ |
| 225 | header->size = size; |
| 226 | /* update maximum block size */ |
| 227 | DMemGlobalState.biggestBlock = DMEM_MAX(header->size, DMemGlobalState.biggestBlock); |
| 228 | /* update used memory total */ |
| 229 | DMemGlobalState.totalHeapUsed += header->size; |
| 230 | /* store filename and linenumber where allocation routine was called */ |
| 231 | strncpy(header->filename, filename, FILENAME_MAX); |
| 232 | header->linenumber = linenumber; |
| 233 | /* store the order the block was allocated in */ |
| 234 | header->order = DMemGlobalState.totalAllocs++; |
| 235 | /* initialize memory to a recognizable 'inited' value */ |
| 236 | memptr = (byte_t *)header + sizeof(MemoryBlockHeader); |
| 237 | memset(memptr, ByteInited, size); |
| 238 | /* put guard area before block */ |
| 239 | memset(header->guard, ByteGuard, MAX_GUARD_BYTES); |
| 240 | /* put guard area after block */ |
| 241 | tail = (MemoryBlockTail *)(memptr + size); |
| 242 | memset(tail->guard, ByteGuard, MAX_GUARD_BYTES); |
| 243 | |
| 244 | Exit: |
| 245 | DMutex_Exit(DMemMutex); |
| 246 | return memptr; |
| 247 | } |
| 248 | |
| 249 | /* |
| 250 | * Frees block of memory allocated with DMem_AllocateBlock |
| 251 | */ |
| 252 | void DMem_FreeBlock(void * memptr) { |
| 253 | MemoryBlockHeader * header; |
| 254 | |
| 255 | DMutex_Enter(DMemMutex); |
| 256 | if ( memptr == NULL) { |
| 257 | goto Exit; |
| 258 | } |
| 259 | |
| 260 | /* get the debug block header preceding the allocated memory */ |
| 261 | header = DMem_GetHeader(memptr); |
| 262 | /* fill memory with recognizable 'freed' value */ |
| 263 | memset(memptr, ByteFreed, header->size); |
| 264 | /* mark block as freed */ |
| 265 | header->listEnter->freed = TRUE; |
| 266 | /* update used memory total */ |
| 267 | DMemGlobalState.totalHeapUsed -= header->size; |
| 268 | Exit: |
| 269 | DMutex_Exit(DMemMutex); |
| 270 | } |
| 271 | |
| 272 | static void DMem_DumpHeader(MemoryBlockHeader * header) { |
| 273 | char report[FILENAME_MAX+MAX_DECIMAL_DIGITS*3+1]; |
| 274 | static const char * reportFormat = |
| 275 | "file: %s, line %d\n" |
| 276 | "size: %d bytes\n" |
| 277 | "order: %d\n" |
| 278 | "-------"; |
| 279 | |
| 280 | DMem_VerifyHeader(header); |
| 281 | sprintf(report, reportFormat, header->filename, header->linenumber, header->size, header->order); |
| 282 | DTRACE_PRINTLN(report); |
| 283 | } |
| 284 | |
| 285 | /* |
| 286 | * Call this function at shutdown time to report any leaked blocks |
| 287 | */ |
| 288 | void DMem_ReportLeaks() { |
| 289 | MemoryListLink * link; |
| 290 | |
| 291 | DMutex_Enter(DMemMutex); |
| 292 | |
| 293 | /* Force memory leaks to be output regardless of trace settings */ |
| 294 | DTrace_EnableFile(__FILE__, TRUE); |
| 295 | DTRACE_PRINTLN("--------------------------"); |
| 296 | DTRACE_PRINTLN("Debug Memory Manager Leaks"); |
| 297 | DTRACE_PRINTLN("--------------------------"); |
| 298 | |
| 299 | /* walk through allocated list and dump any blocks not marked as freed */ |
| 300 | link = MemoryList.next; |
| 301 | while (link != NULL) { |
| 302 | if ( !link->freed ) { |
| 303 | DMem_DumpHeader(link->header); |
| 304 | } |
| 305 | link = link->next; |
| 306 | } |
| 307 | |
| 308 | DMutex_Exit(DMemMutex); |
| 309 | } |
| 310 | |
| 311 | void DMem_SetAllocCallback( DMEM_ALLOCFN pfn ) { |
| 312 | DMutex_Enter(DMemMutex); |
| 313 | DMemGlobalState.pfnAlloc = pfn; |
| 314 | DMutex_Exit(DMemMutex); |
| 315 | } |
| 316 | |
| 317 | void DMem_SetFreeCallback( DMEM_FREEFN pfn ) { |
| 318 | DMutex_Enter(DMemMutex); |
| 319 | DMemGlobalState.pfnFree = pfn; |
| 320 | DMutex_Exit(DMemMutex); |
| 321 | } |
| 322 | |
| 323 | void DMem_SetCheckPtrCallback( DMEM_CHECKPTRFN pfn ) { |
| 324 | DMutex_Enter(DMemMutex); |
| 325 | DMemGlobalState.pfnCheckPtr = pfn; |
| 326 | DMutex_Exit(DMemMutex); |
| 327 | } |
| 328 | |
| 329 | void DMem_DisableMutex() { |
| 330 | DMemMutex = NULL; |
| 331 | } |
| 332 | |
| 333 | #endif /* defined(DEBUG) */ |
| 334 | |
| 335 | /* The following line is only here to prevent compiler warnings |
| 336 | * on release (non-debug) builds |
| 337 | */ |
| 338 | static int dummyVariable = 0; |