/* *@@sourcefile memdebug.c: * memory debugging helpers. * * Several things are in here which might turn out to * be useful: * * -- Memory block dumping (memdDumpMemoryBlock). * * -- Sophisticated heap debugging functions, which * automatically replace malloc() and free() etc. * If the __XWPMEMDEBUG__ #define is set before including * memdebug.h, all those standard library calls * are remapped to use the functions in this file * instead. * * At present, malloc(), calloc(), realloc(), strdup() * and free() are supported. * * These log every memory allocation made and log * much more data compared to the VAC++ memory * debugging functions. See HEAPITEM for details. * Automatic heap checking is also included, using * special "magic string" which are checked to * detect overwrites. * * To be able to trace memory errors, set the global * variable G_pMemdLogFunc to a function which can * write an error string to a meaningful place (the * screen or a file). WARNING: While that error function * is executed, the system might be in a global memory * lock, so DON'T display a message box while in that * function, and DO NOT call malloc() or other memory * functions in there. * * These debug functions have been added with V0.9.3 * and should now be compiler-independent. * * V0.9.6 added realloc() support and fixed a few bugs. * * With V0.9.16, most of this was rewritten to be much * faster. This no longer slows down the system enormously. * * -- A PM heap debugging window which shows the status * of the heap logging list. See memdCreateMemDebugWindow * (memdebug_win.c) for details. * * To enable memory debugging, do the following in each (!) * of your code modules: * * 1) Include at least and . * * 2) Include memdebug.h AFTER those two. This will remap * the malloc() etc. calls to the debug functions in * this file by defining macros for them. * * If you don't want those replaced, add + #define DONT_REPLACE_MALLOC * before including memdebug.h. * * To avoid calling a debug function for a single call, * place the malloc call (or whatever) in brackets. * * That's all. XWorkplace's setup.h does this automatically * if XWorkplace is compiled with debug code. * * A couple of WARNINGS: * * 1) When free() is invoked, the memory that was allocated * is freed, but not the memory log entry (the HEAPITEM) * to allow tracing what was freed. As a result, the tree * of memory items keeps growing longer. Do not expect * this to work forever, even though things have greatly * improved with V0.9.16. * * 2) The replacement functions in this file allocate * extra memory for the magic strings. For example, if * you call malloc(100), more than 100 bytes get allocated * to allow for storing the magic strings to detect * memory overwrites. Two magic strings are allocated, * one before the actual buffer, and one behind it. * The pointer returned is _not_ identical to the one * that was internally allocated. * * As a result, YOU MUST NOT confuse the replacement * memory functions with the original ones. If you * use malloc() in one source file and free() the * buffer in another one where debug memory has not * been enabled, you'll get crashes. * * As a rule of thumb, enable memory debugging for all * your source files or for none. And make sure everything * is recompiled when you change your mind. * *@@added V0.9.1 (2000-02-12) [umoeller] */ /* * Copyright (C) 2000-2001 Ulrich M”ller. * This program is part of the XWorkplace package. * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, in version 2 as it comes in the COPYING * file of the XWorkplace main distribution. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define OS2EMX_PLAIN_CHAR // this is needed for "os2emx.h"; if this is defined, // emx will define PSZ as _signed_ char, otherwise // as unsigned char #define INCL_DOSSEMAPHORES #define INCL_DOSEXCEPTIONS #define INCL_DOSPROCESS #define INCL_DOSERRORS #include #include #include #include #include "helpers\tree.h" #define DONT_REPLACE_MALLOC // never do debug memory for this #define MEMDEBUG_PRIVATE #include "setup.h" #ifdef __XWPMEMDEBUG__ #include "helpers\dosh.h" #include "helpers\except.h" #include "helpers\memdebug.h" // included by setup.h already #include "helpers\stringh.h" /* *@@category: Helpers\C helpers\Heap debugging * See memdebug.c. */ /* ****************************************************************** * * Global variables * ********************************************************************/ #define MEMBLOCKMAGIC_HEAD "\210\203`H&cx$&%\254" // size must be a multiple of 4 or dword-alignment will fail; // there's an extra 0 byte at the end, so we have a size of 12 // V0.9.3 (2000-04-17) [umoeller] #define MEMBLOCKMAGIC_TAIL "\250\210&%/dfjsk%#,dlhf\223" HMTX G_hmtxMallocList = NULLHANDLE; extern TREE *G_pHeapItemsRoot = NULL; extern LONG G_cHeapItems = 0; PFNCBMEMDLOG G_pMemdLogFunc = NULL; extern ULONG G_ulItemsReleased = 0; extern ULONG G_ulBytesReleased = 0; /* ****************************************************************** * * Debug heap management * ********************************************************************/ /* *@@ memdLock: * enables the global memory lock to protect * the global variables here. Use memdUnlock * to unlock again, and lock only for the shortest * possible time. This is only used by the memdebug.c * functions. * *@@added V0.9.3 (2000-04-10) [umoeller] */ BOOL memdLock(VOID) { if (!G_hmtxMallocList) { // first call: if (!DosCreateMutexSem(NULL, &G_hmtxMallocList, 0, // unshared TRUE)) // request now! { treeInit(&G_pHeapItemsRoot, &G_cHeapItems); return TRUE; } } else return !DosRequestMutexSem(G_hmtxMallocList, SEM_INDEFINITE_WAIT); return FALSE; } /* *@@ memdUnlock: * the reverse to memdLock. * *@@added V0.9.3 (2000-04-10) [umoeller] */ VOID memdUnlock(VOID) { DosReleaseMutexSem(G_hmtxMallocList); } /* *@@ LogError: * *@@added V0.9.16 (2001-12-08) [umoeller] */ STATIC VOID LogError(const char *pcszFormat, // in: format string (like with printf) ...) // in: additional stuff (like with printf) { if (G_pMemdLogFunc) { CHAR szMsg[1000]; va_list args; va_start(args, pcszFormat); vsprintf(szMsg, pcszFormat, args); va_end(args); G_pMemdLogFunc(szMsg); } } /* *@@ FindHeapItem: * *@@added V0.9.16 (2001-12-08) [umoeller] */ STATIC PHEAPITEM FindHeapItem(void *p) { return (PHEAPITEM)treeFind(G_pHeapItemsRoot, (ULONG)p, treeCompareKeys); } /* *@@ FillHeapItem: * *@@added V0.9.16 (2001-12-08) [umoeller] */ STATIC VOID FillHeapItem(PHEAPITEM pHeapItem, void *prc, size_t stSize, const char *pcszSourceFile, // in: source file name unsigned long ulLine, // in: source line const char *pcszFunction) // in: function name { pHeapItem->ulSize = stSize; pHeapItem->pcszSourceFile = pcszSourceFile; pHeapItem->ulLine = ulLine; pHeapItem->pcszFunction = pcszFunction; DosGetDateTime(&pHeapItem->dtAllocated); pHeapItem->ulTID = doshMyTID(); pHeapItem->fFreed = FALSE; // use the return pointer as the tree sort key // V0.9.16 (2001-12-08) [umoeller] pHeapItem->Tree.ulKey = (ULONG)prc; } /* *@@ CheckMagics: * *@@added V0.9.16 (2001-12-08) [umoeller] */ STATIC VOID CheckMagics(const char *pcszParentFunc, PHEAPITEM pHeapItem, PBYTE p, const char *pcszSourceFile, // in: source file name unsigned long ulLine, // in: source line const char *pcszFunction) // in: function name { void *pBeforeMagic = ((PBYTE)p) - sizeof(MEMBLOCKMAGIC_HEAD); ULONG ulError = 0; // check magic string if (memcmp(pBeforeMagic, MEMBLOCKMAGIC_HEAD, sizeof(MEMBLOCKMAGIC_HEAD))) ulError = 1; else if (memcmp(((PBYTE)p) + pHeapItem->ulSize, MEMBLOCKMAGIC_TAIL, sizeof(MEMBLOCKMAGIC_TAIL))) ulError = 2; if (ulError) { LogError("%s: Magic string %s memory block at 0x%lX has been overwritten.\n" "This was detected by the free() call at %s (%s, line %d).\n" "The block was allocated by %s (%s, line %d).", pcszParentFunc, (ulError == 1) ? "before" : "after", p, pcszFunction, pcszSourceFile, ulLine, // free pHeapItem->pcszFunction, pHeapItem->pcszSourceFile, pHeapItem->ulLine); } } /* *@@ memdMalloc: * wrapper function for malloc() to trace malloc() * calls more precisely. * * If XWorkplace is compiled with debug code, setup.h * automatically #includes memdebug.h, which maps * malloc to this function so that the source file * etc. parameters automatically get passed. * * For each call, we call the default malloc(), whose * return value is returned, and create a HEAPITEM * for remembering the call, which is stored in a global * linked list. * *@@added V0.9.3 (2000-04-11) [umoeller] *@@changed V0.9.16 (2001-12-08) [umoeller]: reworked to use trees now, much faster */ void* memdMalloc(size_t stSize, const char *pcszSourceFile, // in: source file name unsigned long ulLine, // in: source line const char *pcszFunction) // in: function name { void *prc = NULL; if (stSize == 0) // malloc(0) called: report error LogError(__FUNCTION__ ": Function %s (%s, line %d) called malloc(0).", pcszFunction, pcszSourceFile, ulLine); else if (memdLock()) { // call default malloc(), but with the additional // size of our MEMBLOCKMAGIC strings; we'll return // the first byte after the "front" string so we can // check for string overwrites void *pObj; if (pObj = malloc( sizeof(MEMBLOCKMAGIC_HEAD) + stSize + sizeof(MEMBLOCKMAGIC_TAIL))) { PHEAPITEM pHeapItem; BOOL fInsert = TRUE; // store "front" magic string memcpy(pObj, MEMBLOCKMAGIC_HEAD, sizeof(MEMBLOCKMAGIC_HEAD)); // return address: first byte after "front" magic string prc = ((PBYTE)pObj) + sizeof(MEMBLOCKMAGIC_HEAD); // store "tail" magic string to block which // will be returned plus the size which was requested memcpy(((PBYTE)prc) + stSize, MEMBLOCKMAGIC_TAIL, sizeof(MEMBLOCKMAGIC_TAIL)); if (!(pHeapItem = FindHeapItem(prc))) // not re-using old address: // create a new heap item pHeapItem = (PHEAPITEM)malloc(sizeof(HEAPITEM)); else fInsert = FALSE; FillHeapItem(pHeapItem, prc, stSize, pcszSourceFile, ulLine, pcszFunction); if (fInsert) // append heap item to linked list if (treeInsert(&G_pHeapItemsRoot, &G_cHeapItems, (TREE*)pHeapItem, treeCompareKeys)) { LogError(__FUNCTION__ ": treeInsert failed for memory block at 0x%lX.\n" "The block was allocated by %s (%s, line %d).", prc, pcszFunction, pcszSourceFile, ulLine); } } memdUnlock(); } return prc; } /* *@@ memdCalloc: * similar to memdMalloc; this is the wrapper for * the calloc() call. This is automatically * remapped also. * *@@added V0.9.3 (2000-04-11) [umoeller] */ void* memdCalloc(size_t num, size_t stSize, const char *pcszSourceFile, unsigned long ulLine, const char *pcszFunction) { void *p = memdMalloc(num * stSize, pcszSourceFile, ulLine, pcszFunction); memset(p, 0, num * stSize); return p; } /* *@@ memdFree: * wrapper for the free() call, which is remapped * by setup.h and memdebug.h like memdMalloc * and memdCalloc. This searches the memory object * (p) which was previously allocated on the linked * list of HEAPITEM's and frees it then by calling * the default free(). * * The HEAPITEM itself is not freed, but only marked * as freed. As a result, the linked list can grow * REALLY large. While memdMalloc does not become * slower with large HEAPITEM lists because it only * appends to the end of the list, which is remembered, * memdFree can become extremely slow because the entire * list needs to be searched with each call. * So call memdReleaseFreed from time to time. * *@@added V0.9.3 (2000-04-10) [umoeller] *@@changed V0.9.16 (2001-12-08) [umoeller]: reworked to use trees now, much faster */ void memdFree(void *p, const char *pcszSourceFile, unsigned long ulLine, const char *pcszFunction) { if (memdLock()) { PHEAPITEM pHeapItem; // search the list with the pointer which was // really returned by the original malloc(), // that is, the byte after the magic string if (pHeapItem = FindHeapItem(p)) { // the same address may be allocated and freed // several times, so check if (!pHeapItem->fFreed) { // found: void *pBeforeMagic = ((PBYTE)p) - sizeof(MEMBLOCKMAGIC_HEAD); CheckMagics(__FUNCTION__, pHeapItem, (PBYTE)p, pcszSourceFile, ulLine, pcszFunction); // free the real memory item free(pBeforeMagic); // mark the heap item as freed, but // keep it in the list pHeapItem->fFreed = TRUE; } // if (!pHeapItem->fFreed) else // memory block has been freed twice: LogError(__FUNCTION__ ": Memory block at 0x%lX has been freed twice.\n" "This was detected by the free() call at %s (%s, line %d).\n" "The block was originally allocated by %s (%s, line %d).", p, pcszFunction, pcszSourceFile, ulLine, // free pHeapItem->pcszFunction, pHeapItem->pcszSourceFile, pHeapItem->ulLine); } else // not found: LogError(__FUNCTION__ ": free() called with invalid object 0x%lX from %s (%s, line %d).", p, pcszFunction, pcszSourceFile, ulLine); memdUnlock(); } } /* *@@ memdRealloc: * wrapper function for realloc(). See memdMalloc for * details. * *@@added V0.9.6 (2000-11-12) [umoeller] *@@changed V0.9.12 (2001-05-21) [umoeller]: this reported errors on realloc(0), which is a valid call, fixed *@@changed V0.9.16 (2001-12-08) [umoeller]: reworked to use trees now, much faster */ void* memdRealloc(void *p, size_t stSize, const char *pcszSourceFile, // in: source file name unsigned long ulLine, // in: source line const char *pcszFunction) // in: function name { void *prc = NULL; if (!p) // p == NULL: this is valid, use malloc() instead // V0.9.12 (2001-05-21) [umoeller] return memdMalloc(stSize, pcszSourceFile, ulLine, pcszFunction); if (memdLock()) { // search the list with the pointer which was // really returned by the original malloc(), // that is, the byte after the magic string PHEAPITEM pHeapItem, pExisting; if (pHeapItem = FindHeapItem(p)) { // found: if (pHeapItem->fFreed) { LogError(__FUNCTION__ ": realloc() called with memory block at 0x%lX that was already freed.\n" "This was detected by the realloc() call at %s (%s, line %d).\n" "The block was originally allocated by %s (%s, line %d).", p, pcszFunction, pcszSourceFile, ulLine, // free pHeapItem->pcszFunction, pHeapItem->pcszSourceFile, pHeapItem->ulLine); } else { // block is valid: void *pBeforeMagic = ((PBYTE)p) - sizeof(MEMBLOCKMAGIC_HEAD); PVOID pObjNew = 0; ULONG ulError = 0; ULONG cbCopy = 0; CheckMagics(__FUNCTION__, pHeapItem, (PBYTE)p, pcszSourceFile, ulLine, pcszFunction); // now reallocate! pObjNew = malloc( sizeof(MEMBLOCKMAGIC_HEAD) + stSize // new size + sizeof(MEMBLOCKMAGIC_TAIL)); // store "front" magic string memcpy(pObjNew, MEMBLOCKMAGIC_HEAD, sizeof(MEMBLOCKMAGIC_HEAD)); // return address: first byte after "front" magic string prc = ((PBYTE)pObjNew) + sizeof(MEMBLOCKMAGIC_HEAD); // bytes to copy: the smaller of the old and the new size cbCopy = pHeapItem->ulSize; if (stSize < pHeapItem->ulSize) cbCopy = stSize; // copy buffer from old memory object memcpy(prc, // after "front" magic p, cbCopy); // store "tail" magic string to block which // will be returned plus the size which was requested memcpy(((PBYTE)prc) + stSize, MEMBLOCKMAGIC_TAIL, sizeof(MEMBLOCKMAGIC_TAIL)); // free the old buffer free(pBeforeMagic); // update the tree, since prc has changed treeDelete(&G_pHeapItemsRoot, &G_cHeapItems, (TREE*)pHeapItem); // append heap item to linked list if (pExisting = FindHeapItem(prc)) { // a different heap item exists for this address: // delete this one and use that instead; there's // no need to re-insert either free(pHeapItem); pHeapItem = pExisting; } FillHeapItem(pHeapItem, prc, stSize, pcszSourceFile, ulLine, pcszFunction); // insert only if we didn't use an existing item if (!pExisting) if (treeInsert(&G_pHeapItemsRoot, &G_cHeapItems, (TREE*)pHeapItem, treeCompareKeys)) { LogError(__FUNCTION__ ": treeInsert failed for memory block at 0x%lX.\n" "The block was allocated by %s (%s, line %d).", prc, pcszFunction, pcszSourceFile, ulLine); } } // if (!pHeapItem->fFreed) } else LogError(__FUNCTION__ ": realloc() called with invalid object from %s (%s, line %d) for object 0x%lX.", pcszFunction, pcszSourceFile, ulLine, p); memdUnlock(); } return prc; } /* *@@ memdReleaseFreed: * goes thru the entire global HEAPITEM's list * and throws out all items which have been freed. * Call this from time to time in order to keep * the system usable. See memdFree() for details. * * Returns the no. of HEAPITEM's that have been * released. * *@@added V0.9.3 (2000-04-11) [umoeller] */ unsigned long memdReleaseFreed(void) { BOOL ulItemsReleased = 0, ulBytesReleased = 0; if (memdLock()) { /* PHEAPITEM pHeapItem = treeFirst(G_pHeapItemsRoot); while (pHeapItem) { // store next first, because we can change the "next" pointer PHEAPITEM pNext = treeNext(pHeapItem); if (pHeapItem->fFreed) { // item was freed: if (pPrevious == NULL) // head of list: G_pHeapItemsRoot = pNext; // can be NULL else // somewhere later: // link next to previous to skip current pPrevious->pNext = pNext; // can be NULL ulItemsReleased++; ulBytesReleased += pHeapItem->ulSize; if (pHeapItem == G_pHeapItemsLast) // reset "last item" cache G_pHeapItemsLast = NULL; free(pHeapItem); } else // item still valid: pPrevious = pHeapItem; pHeapItem = pNext; } */ G_ulItemsReleased += ulItemsReleased; G_ulBytesReleased += ulBytesReleased; memdUnlock(); } return ulItemsReleased; } /* ****************************************************************** * * XFolder debugging helpers * ********************************************************************/ #ifdef _PMPRINTF_ /* *@@ memdDumpMemoryBlock: * if _PMPRINTF_ has been #define'd before including * memdebug.h, * this will dump a block of memory to the PMPRINTF * output window. Useful for debugging internal * structures. * If _PMPRINTF_ has been NOT #define'd, * no code will be produced at all. :-) */ /* void memdDumpMemoryBlock(PBYTE pb, // in: start address ULONG ulSize, // in: size of block ULONG ulIndent) // in: how many spaces to put // before each output line { TRY_QUIET(excpt1) { PSZ psz; if (psz = strhCreateDump(pb, ulSize, ulIndent)) { _Pmpf(("\n%s", psz)); free(psz); } } CATCH(excpt1) { _Pmpf(("Crash in " __FUNCTION__ )); } END_CATCH(); } */ #endif #endif