1 /*////////////////////////////////////////////////////////////////////////////
3 * Memory_and_Exception_Trace
5 * ///////////////////////////////////////////////////////////////////////////
10 * Dumps memory leaks (unreleased allocations) for CRT-Allocs and COM-Allocs
11 * Dumps the stack of an thread if an exepction occurs
14 * - If the allocation-RequestID wrap, then allocations will get lost...
17 * Jochen Kalmbach, Germany
18 * (c) 2002-2005 (Freeware)
19 * http://www.codeproject.com/tools/leakfinder.asp
21 * License (The zlib/libpng License, http://www.opensource.org/licenses/zlib-license.php):
23 * Copyright (c) 2005 Jochen Kalmbach
25 * This software is provided 'as-is', without any express or implied warranty.
26 * In no event will the authors be held liable for any damages arising from the
27 * use of this software.
29 * Permission is granted to anyone to use this software for any purpose, including
30 * commercial applications, and to alter it and redistribute it freely, subject to
31 * the following restrictions:
33 * 1. The origin of this software must not be misrepresented; you must not claim
34 * that you wrote the original software. If you use this software in a product,
35 * an acknowledgment in the product documentation would be appreciated but is
38 * 2. Altered source versions must be plainly marked as such, and must not be
39 * misrepresented as being the original software.
41 * 3. This notice may not be removed or altered from any source distribution.
43 *//////////////////////////////////////////////////////////////////////////////
45 //#include "stdafx.h" // should be uncommented for precompiled headers
56 #include "Stackwalker.h"
59 #pragma warning(disable : 4100)
60 #pragma warning(disable : 4996)
61 #pragma warning(disable : 4189)
62 #pragma warning(disable : 4245)
63 #pragma warning(disable : 4701)
65 // If the following is defined, only the used memories are stored in the hash-table.
66 // If the memory is freed, it will be removed from the hash-table (to reduce memory)
67 // Consequences: At DeInitAllocHook, only Leaks will be reported
68 #define HASH_ENTRY_REMOVE_AT_FREE
71 // 0 = Do not write any output during runtime-alloc-call
72 // 1 = Write only the alloc action (malloc, realloc, free)
73 // 2 = Write alloc action and callstack only for malloc/realloc
74 // 3 = Write alloc action and callstack for all actions
75 static ULONG g_ulShowStackAtAlloc = 0;
77 // the form of the output file
78 static eAllocCheckOutput g_CallstackOutputType = ACOutput_Simple;
81 // Size of Hash-Table (this should be a prime number to avoid collisions)
82 #define ALLOC_HASH_ENTRIES 1023
85 // Size of Callstack-trace in bytes (0x500 => appr. 5-9 functions, depending on parameter count for each function)
86 #define MAX_ESP_LEN_BUF 0x500
89 // Normally we can ignore allocations from the Runtime-System
90 #define IGNORE_CRT_ALLOC
92 // MaxSize: 1 MByte (only for StackwalkFilter)
93 #define LOG_FILE_MAX_SIZE 1024*1024
95 // If the following is defined, then COM-Leaks will also be tracked
96 #define WITH_IMALLOC_SPY
99 // #############################################################################################
100 #ifdef WITH_IMALLOC_SPY
102 void IMallocHashInsert(void *pData, CONTEXT &Context, size_t nDataSize);
103 BOOL IMallocHashRemove(void *pData);
105 // IMallocSpy-Interface
106 class CMallocSpy : public IMallocSpy
115 STDMETHOD(QueryInterface) (REFIID riid, LPVOID *ppUnk) {
117 if (IsEqualIID(riid, IID_IUnknown)) {
118 *ppUnk = (IUnknown *) this;
120 else if (IsEqualIID(riid, IID_IMallocSpy)) {
121 *ppUnk = (IMalloc *) this;
130 STDMETHOD_(ULONG, AddRef) (void) {
131 return InterlockedIncrement(&m_cRef);
133 STDMETHOD_(ULONG, Release) (void) {
135 cRef = InterlockedDecrement(&m_cRef);
142 // IMallocSpy methods
143 STDMETHOD_(ULONG, PreAlloc) (ULONG cbRequest) {
144 m_cbRequest = cbRequest;
147 STDMETHOD_(void *, PostAlloc) (void *pActual) {
149 if (DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
150 GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS ) != 0) {
153 memset( &c, '\0', sizeof c );
154 c.ContextFlags = CONTEXT_FULL;
156 if ( GetThreadContext(hThread, &c) != 0) {
168 IMallocHashInsert(pActual, c, m_cbRequest);
170 CloseHandle(hThread);
174 STDMETHOD_(void *, PreFree) (void *pRequest, BOOL fSpyed) {
175 IMallocHashRemove(pRequest);
178 STDMETHOD_(void, PostFree) (BOOL fSpyed) {
181 STDMETHOD_(ULONG, PreRealloc) (void *pRequest, ULONG cbRequest,
182 void **ppNewRequest, BOOL fSpyed) {
183 IMallocHashRemove(pRequest);
184 m_cbRequest = cbRequest;
185 *ppNewRequest = pRequest; // Bug fixed. Thanx to Christoph Weber
188 STDMETHOD_(void *, PostRealloc) (void *pActual, BOOL fSpyed) {
190 if (DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
191 GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS ) != 0) {
194 memset( &c, '\0', sizeof c );
195 c.ContextFlags = CONTEXT_FULL;
197 if ( GetThreadContext(hThread, &c) != 0) {
209 IMallocHashInsert(pActual, c, m_cbRequest);
211 CloseHandle(hThread);
215 STDMETHOD_(void *, PreGetSize) (void *pRequest, BOOL fSpyed) {
218 STDMETHOD_(ULONG, PostGetSize) (ULONG cbActual, BOOL fSpyed) {
221 STDMETHOD_(void *, PreDidAlloc) (void *pRequest, BOOL fSpyed) {
224 STDMETHOD_(BOOL, PostDidAlloc) (void *pRequest, BOOL fSpyed, BOOL fActual) {
227 STDMETHOD_(void, PreHeapMinimize) (void) {
230 STDMETHOD_(void, PostHeapMinimize) (void) {
239 // #############################################################################################
240 // Here I have included the API-Version 9 declarations, so it will also compile on systems, where the new PSDK is not installed
241 // Normally we just need to include the "dbghelp.h" file
242 #include <imagehlp.h>
243 #if API_VERSION_NUMBER < 9
246 (__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)(
248 DWORD64 qwBaseAddress,
251 LPDWORD lpNumberOfBytesRead
254 typedef struct _IMAGEHLP_LINE64 {
255 DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64)
256 PVOID Key; // internal
257 DWORD LineNumber; // line number in file
258 PCHAR FileName; // full filename
259 DWORD64 Address; // first instruction of line
260 } IMAGEHLP_LINE64, *PIMAGEHLP_LINE64;
263 typedef struct _IMAGEHLP_MODULE64 {
264 DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
265 DWORD64 BaseOfImage; // base load address of module
266 DWORD ImageSize; // virtual size of the loaded module
267 DWORD TimeDateStamp; // date/time stamp from pe header
268 DWORD CheckSum; // checksum from the pe header
269 DWORD NumSyms; // number of symbols in the symbol table
270 SYM_TYPE SymType; // type of symbols loaded
271 CHAR ModuleName[32]; // module name
272 CHAR ImageName[256]; // image name
273 CHAR LoadedImageName[256]; // symbol file name
274 } IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64;
276 typedef struct _IMAGEHLP_SYMBOL64 {
277 DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64)
278 DWORD64 Address; // virtual address including dll base address
279 DWORD Size; // estimated size of symbol, can be zero
280 DWORD Flags; // info about the symbols, see the SYMF defines
281 DWORD MaxNameLength; // maximum size of symbol name in 'Name'
282 CHAR Name[1]; // symbol name (null terminated string)
283 } IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64;
285 typedef struct _tagADDRESS64 {
289 } ADDRESS64, *LPADDRESS64;
291 typedef struct _KDHELP64 {
294 // address of kernel thread object, as provided in the
295 // WAIT_STATE_CHANGE packet.
300 // offset in thread object to pointer to the current callback frame
303 DWORD ThCallbackStack;
306 // offset in thread object to pointer to the current callback backing
307 // store frame in kernel stack.
309 DWORD ThCallbackBStore;
312 // offsets to values in frame:
314 // address of next callback frame
317 // address of saved frame pointer (if applicable)
322 // Address of the kernel function that calls out to user mode
324 DWORD64 KiCallUserMode;
327 // Address of the user mode dispatcher function
329 DWORD64 KeUserCallbackDispatcher;
332 // Lowest kernel mode address
334 DWORD64 SystemRangeStart;
338 } KDHELP64, *PKDHELP64;
341 typedef struct _tagSTACKFRAME64 {
342 ADDRESS64 AddrPC; // program counter
343 ADDRESS64 AddrReturn; // return address
344 ADDRESS64 AddrFrame; // frame pointer
345 ADDRESS64 AddrStack; // stack pointer
346 ADDRESS64 AddrBStore; // backing store pointer
347 PVOID FuncTableEntry; // pointer to pdata/fpo or NULL
348 DWORD64 Params[4]; // possible arguments to the function
349 BOOL Far; // WOW far call
350 BOOL Virtual; // is this a virtual frame?
353 } STACKFRAME64, *LPSTACKFRAME64;
357 (__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)(
364 (__stdcall *PGET_MODULE_BASE_ROUTINE64)(
371 (__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)(
377 // #############################################################################################
381 // Forward definitions of functions:
382 static void ShowStackRM( HANDLE hThread, CONTEXT& c, FILE *fLogFile, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryFunction, HANDLE hProcess);
383 static void ShowStack( HANDLE hThread, CONTEXT& c, FILE *fLogFile);
385 //static void AllocHashOut(FILE*);
386 static ULONG AllocHashOutLeaks(FILE*);
391 static TCHAR *g_pszAllocLogName = NULL;
392 static FILE *g_fFile = NULL;
394 // AllocCheckFileOpen
395 // Checks if the log-file is already opened
396 // if not, try to open file (append or create if not exists)
397 // if open failed, redirect output to stdout
398 static void AllocCheckFileOpen(bool bAppend = true) {
399 // is the File already open? If not open it...
401 if (g_pszAllocLogName != NULL)
403 if (bAppend == false)
404 g_fFile = _tfopen(g_pszAllocLogName, _T("w"));
406 g_fFile = _tfopen(g_pszAllocLogName, _T("a"));
412 // Write Date/Time to specified file (will also work after 2038)
413 static void WriteDateTime(FILE *fFile, BOOL asXMLAttrs = FALSE) {
414 TCHAR pszTemp[11], pszTemp2[11];
417 _tstrdate( pszTemp );
418 _tstrtime( pszTemp2 );
419 if (asXMLAttrs == FALSE)
420 _ftprintf(fFile, _T("%s %s"), pszTemp, pszTemp2 ); // also ok after year 2038 (asctime is NOT ok)
422 _ftprintf(fFile, _T("date=\"%s\" time=\"%s\" "), pszTemp, pszTemp2 ); // also ok after year 2038 (asctime is NOT ok)
427 /*******************************************************************************
429 *******************************************************************************/
430 // Memory for the EIP-Address (is used by the ShowStack-method)
431 #define MAX_EIP_LEN_BUF 4
433 #define ALLOC_ENTRY_NOT_FOUND 0xFFFFFFFF
435 typedef struct AllocHashEntryType {
436 long lRequestID; // RequestID from CRT (if 0, then this entry is empty)
437 size_t nDataSize; // Size of the allocated memory
438 char cRemovedFlag; // 0 => memory was not yet released
439 struct AllocHashEntryType *Next;
443 char pcEIPAddr[MAX_EIP_LEN_BUF];
447 char pcESPAddr[MAX_ESP_LEN_BUF];
448 } AllocHashEntryType;
450 static AllocHashEntryType AllocHashTable[ALLOC_HASH_ENTRIES];
451 static ULONG AllocHashEntries = 0;
452 static ULONG AllocHashCollisions = 0;
453 static ULONG AllocHashFreed = 0;
454 static ULONG AllocHashMaxUsed = 0; // maximal number of concurrent entries
455 static ULONG AllocHashCurrentCount = 0;
457 static ULONG AllocHashMaxCollisions = 0;
458 static ULONG AllocHashCurrentCollisions = 0;
460 // ##########################################################################################
461 #ifdef WITH_IMALLOC_SPY
462 // eigene Tabelle für die IMallocs:
463 typedef struct IMallocHashEntryType {
464 void *pData; // Key-Word
465 size_t nDataSize; // größe des Datenblocks (optional)
466 char cRemovedFlag; // 0 => nicht wurde noch nicht freigegeben
467 struct IMallocHashEntryType *Next;
468 // Callstack für EIP
471 char pcEIPAddr[MAX_EIP_LEN_BUF];
472 // Callstack für ESP
475 char pcESPAddr[MAX_ESP_LEN_BUF];
476 } IMallocHashEntryType;
478 static IMallocHashEntryType IMallocHashTable[ALLOC_HASH_ENTRIES];
480 static ULONG IMallocHashEntries = 0;
481 static ULONG IMallocHashCollisions = 0;
482 static ULONG IMallocHashFreed = 0;
483 static ULONG IMallocHashMaxUsed = 0; // maximal number of concurrent entries
484 static ULONG IMallocHashCurrentCount = 0;
486 static ULONG IMallocHashMaxCollisions = 0;
487 static ULONG IMallocHashCurrentCollisions = 0;
490 //static void AllocHashOut(FILE*);
491 static ULONG IMallocHashOutLeaks(FILE*);
494 // Die eigentliche Hash-Funktion (hier ganz simpel)
495 static ULONG IMallocHashFunction(void *pData) {
497 DWORD dwPointer = (DWORD) pData;
499 // relativ simpler Mechanismus für die Hash-Funktion,
500 // mir ist nur nix besseres eingefallen...
501 ulTemp = dwPointer % ALLOC_HASH_ENTRIES;
503 _ASSERTE( (ulTemp >= 0) && (ulTemp < ALLOC_HASH_ENTRIES) );
506 } // AllocHashFunction
509 // pData: Key-Word (Pointer to address)
510 // pContext: Context-Record, for retrieving Callstack (EIP and EBP is only needed)
511 // nDataSize: How many bytes
512 void IMallocHashInsert(void *pData, CONTEXT &Context, size_t nDataSize) {
514 IMallocHashEntryType *pHashEntry;
516 // ermittle Statistische Werte
517 IMallocHashEntries++;
518 IMallocHashCurrentCount++;
519 if (IMallocHashCurrentCount > IMallocHashMaxUsed)
520 IMallocHashMaxUsed = IMallocHashCurrentCount;
522 // ermittle den Hash-Wert
523 HashIdx = IMallocHashFunction(pData);
525 // Eintrag darf nicht größer als die Hash-Tabelle sein
526 _ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
528 pHashEntry = &IMallocHashTable[HashIdx];
529 if (pHashEntry->pData == 0) {
530 // es ist noch kein Eintrag da
533 //Statistische Daten:
534 IMallocHashCollisions++;
535 IMallocHashCurrentCollisions++;
536 if (IMallocHashCurrentCollisions > IMallocHashMaxCollisions)
537 IMallocHashMaxCollisions = IMallocHashCurrentCollisions;
539 // Eintrag ist schon belegt, verkette die Einträge
540 // wenn dies oft vorkommt, sollte man entweder die Tabelle vergrößern oder eine
541 // andere Hash-Funktion wählen
542 while(pHashEntry->Next != NULL) {
543 pHashEntry = pHashEntry->Next;
546 pHashEntry->Next = (IMallocHashEntryType*) _calloc_dbg(sizeof(IMallocHashEntryType), 1, _CRT_BLOCK, __FILE__, __LINE__);
547 pHashEntry = pHashEntry->Next;
550 pHashEntry->pData = pData; // Key-Word
551 pHashEntry->nDataSize = nDataSize;
552 pHashEntry->Next = NULL;
553 // Get EIP and save it in the record
554 pHashEntry->dwEIPOffset = Context.Eip;
555 if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Eip, &(pHashEntry->pcEIPAddr), MAX_EIP_LEN_BUF, &(pHashEntry->dwEIPLen)) == 0) {
556 // Could not read memory... remove everything...
557 memset(pHashEntry->pcEIPAddr, 0, MAX_EIP_LEN_BUF);
558 pHashEntry->dwEIPLen = 0;
559 pHashEntry->dwEIPOffset = 0;
562 // Get ESP and save it in the record
563 pHashEntry->dwESPOffset = Context.Ebp;
564 if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Ebp, &(pHashEntry->pcESPAddr), MAX_ESP_LEN_BUF, &(pHashEntry->dwESPLen)) == 0) {
565 // Could not read memory... remove everything...
566 memset(pHashEntry->pcESPAddr, 0, MAX_ESP_LEN_BUF);
567 pHashEntry->dwESPLen = 0;
568 pHashEntry->dwESPOffset = 0;
570 // Check if I tried to read too much...
571 if (GetLastError() == ERROR_PARTIAL_COPY)
573 // ask how many I can read:
574 MEMORY_BASIC_INFORMATION MemBuffer;
575 DWORD dwRet = VirtualQuery((LPCVOID) Context.Ebp, &MemBuffer, sizeof(MemBuffer));
578 // calculate the length
579 DWORD len = ((DWORD) MemBuffer.BaseAddress + MemBuffer.RegionSize) - Context.Ebp;
580 if ( (len > 0) && (len < MAX_ESP_LEN_BUF) )
582 // try to read it again (with the shorter length)
583 if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Ebp, &(pHashEntry->pcESPAddr), len, &(pHashEntry->dwESPLen)) == 0)
585 // ok, now everything goes wrong... remove it...
586 memset(pHashEntry->pcESPAddr, 0, MAX_ESP_LEN_BUF);
587 pHashEntry->dwESPLen = 0;
588 pHashEntry->dwESPOffset = 0;
592 pHashEntry->dwESPOffset = Context.Ebp;
595 } // VirtualQuery was successfully
596 } // ERROR_PARTIAL_COPY
601 // Wird ALLOC_ENTRY_NOT_FOUND zurückgegeben, so wurde der Key nicht
602 // gefunden, ansonsten wird ein Zeiger auf den Hash-Eintrag zurückgegeben
603 // ACHTUNG: In einem preemptiven Tasking-System kann hier nicht
604 // garantiert werden, ob der Zeiger noch gültig ist, wenn er
605 // zurückgegeben wird, da er von einem anderen Thread schon
606 // freigegeben sein könnte.
607 // Die synchronisation muß eine Ebene höher erfolgen
608 static IMallocHashEntryType *IMallocHashFind(void *pData) {
610 IMallocHashEntryType *pHashEntry;
612 // ermittle den Hash-Wert
613 HashIdx = IMallocHashFunction(pData);
615 // Eintrag darf nicht größer als die Hash-Tabelle sein
616 _ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
618 pHashEntry = &IMallocHashTable[HashIdx];
619 while(pHashEntry != NULL) {
620 if (pHashEntry->pData == pData) {
623 pHashEntry = pHashEntry->Next;
626 // wenn hier angelangt, dann wurde der Eintrag nicht gefunden!
627 return (IMallocHashEntryType*) ALLOC_ENTRY_NOT_FOUND;
631 // Return: FALSE (0) : Key wurde gefunden und entfernt/markiert
632 // TRUE (!=0): Key wurde nicht gefunden!
633 BOOL IMallocHashRemove(void *pData) {
635 IMallocHashEntryType *pHashEntry, *pHashEntryLast;
637 // ermittle den Hash-Wert
638 HashIdx = IMallocHashFunction(pData);
640 // Eintrag darf nicht größer als die Hash-Tabelle sein
641 _ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
643 pHashEntryLast = NULL;
644 pHashEntry = &IMallocHashTable[HashIdx];
645 while(pHashEntry != NULL) {
646 if (pHashEntry->pData == pData) {
647 #ifdef HASH_ENTRY_REMOVE_AT_FREE
649 IMallocHashCurrentCount--;
650 // gebe den Speicher frei
651 if (pHashEntryLast == NULL) {
652 // Es ist ein Eintrag direkt in der Tabelle
653 if (pHashEntry->Next == NULL) {
654 // Es ist der letze Eintrag lösche also die Tabelle
655 memset(&IMallocHashTable[HashIdx], 0, sizeof(IMallocHashTable[HashIdx]));
658 // Es sind noch Einträge verkettet, überschreibe einfach den nicht mehr gebrauchten...
659 IMallocHashEntryType *pTmp = pHashEntry->Next;
660 *pHashEntry = *(pHashEntry->Next);
661 _free_dbg(pTmp, _CRT_BLOCK);
666 // ich bin in einem dynamischen Bereich
667 // dies war eine kollisions, zähle also wieder zurück:
668 IMallocHashCurrentCollisions--;
669 pHashEntryLast->Next = pHashEntry->Next;
670 _free_dbg(pHashEntry, _CRT_BLOCK);
674 // erhöhe nur den Removed counter und behalte das Object im Speicher
675 pHashEntry->cRemovedFlag++;
676 return TRUE; // erfolgreich
679 pHashEntryLast = pHashEntry;
680 pHashEntry = pHashEntry->Next;
683 // wenn hier angelangt, dann wurde der Eintrag nicht gefunden!
689 // Callback-Funtion for StackWalk für meine CallStack-Ausgabe aus der Hash-Tabelle
690 static BOOL __stdcall ReadProcMemoryFromIMallocHash(HANDLE pData, DWORD64 lpBaseAddress, PVOID lpBuffer, DWORD nSize, PDWORD lpNumberOfBytesRead) {
691 // Versuche die hRequestID zu finden
692 IMallocHashEntryType *pHashEntry;
693 *lpNumberOfBytesRead = 0;
695 pHashEntry = IMallocHashFind((PVOID) pData);
696 if (pHashEntry == (IMallocHashEntryType*) ALLOC_ENTRY_NOT_FOUND) {
697 // nicht gefunden, somit kann ich den Speicher nicht lesen
698 *lpNumberOfBytesRead = 0;
701 if ( ((DWORD) lpBaseAddress >= pHashEntry->dwESPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwESPOffset+pHashEntry->dwESPLen)) ) {
702 // Speicher liegt im ESP:
703 // Errechne den Offset
704 DWORD dwOffset = (DWORD) lpBaseAddress - pHashEntry->dwESPOffset;
705 DWORD dwSize = __min(nSize, MAX_ESP_LEN_BUF-dwOffset);
706 memcpy(lpBuffer, &(pHashEntry->pcESPAddr[dwOffset]), dwSize);
707 *lpNumberOfBytesRead = dwSize;
712 if ( ((DWORD) lpBaseAddress >= pHashEntry->dwEIPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwEIPOffset+pHashEntry->dwEIPLen)) ) {
713 // Speicher liegt im EIP:
714 // Errechne den Offset
715 DWORD dwOffset = (DWORD) lpBaseAddress - pHashEntry->dwEIPOffset;
716 DWORD dwSize = __min(nSize, MAX_ESP_LEN_BUF-dwOffset);
717 memcpy(lpBuffer, &(pHashEntry->pcEIPAddr[dwOffset]), dwSize);
718 *lpNumberOfBytesRead = dwSize;
723 if (*lpNumberOfBytesRead == 0) // Der Speicher konnte nicht gefunden werden
729 // Gibt allen Speicher aus, der noch nicht wieder freigegeben wurde
730 // Returns the number of bytes, that are not freed (leaks)
731 ULONG IMallocHashOutLeaks(FILE *fFile) {
733 IMallocHashEntryType *pHashEntry;
735 ULONG ulLeaksByte = 0;
737 // Gehe jeden Eintrag durch und gebe ihn aus
738 for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
739 pHashEntry = &IMallocHashTable[ulTemp];
740 if (pHashEntry->pData != 0) {
741 while(pHashEntry != NULL) {
742 // gebe die Zeile aus
743 if ( (pHashEntry->cRemovedFlag <= 0) || (pHashEntry->cRemovedFlag > 1) ) {
745 if (g_CallstackOutputType == ACOutput_XML)
746 _ftprintf(fFile, _T("<LEAK requestID=\"%u\" size=\"%u\">\n"), pHashEntry->pData, pHashEntry->nDataSize);
748 _ftprintf(fFile, _T("Pointer (RequestID): %12i, Removed: %i, Size: %12i\n"), pHashEntry->pData, pHashEntry->cRemovedFlag, pHashEntry->nDataSize);
750 memset( &c, '\0', sizeof c );
751 c.Eip = pHashEntry->dwEIPOffset;
752 c.Ebp = pHashEntry->dwESPOffset;
753 ShowStackRM( NULL, c, fFile, &ReadProcMemoryFromIMallocHash, (HANDLE) pHashEntry->pData);
754 // Zähle zusammen wieviel Byte noch nicht freigegeben wurden
755 if (pHashEntry->nDataSize > 0)
756 ulLeaksByte += pHashEntry->nDataSize;
758 ulLeaksByte++; // Wenn zwar Speicher allokiert wurde, dieser aber 0 Bytes lang war, so reserviere für diesen zumindest 1 Byte
760 if (g_CallstackOutputType == ACOutput_XML)
761 _ftprintf(fFile, _T("</LEAK>\n")); // terminate the xml-node
763 pHashEntry = pHashEntry->Next;
767 if (g_CallstackOutputType != ACOutput_XML)
768 _ftprintf(fFile, _T("\n**** Number of leaks: %i\n"), ulCount);
770 } // AllocHashOutLeaks
774 static void AllocHashInit(void) {
776 memset(AllocHashTable, 0, sizeof(AllocHashTable));
777 AllocHashEntries = 0;
778 AllocHashCollisions = 0;
780 AllocHashCurrentCount = 0;
781 AllocHashMaxUsed = 0;
783 AllocHashMaxCollisions = 0;
784 AllocHashCurrentCollisions = 0;
786 #ifdef WITH_IMALLOC_SPY
787 memset(IMallocHashTable, 0, sizeof(IMallocHashTable));
788 IMallocHashEntries = 0;
789 IMallocHashCollisions = 0;
790 IMallocHashFreed = 0;
791 IMallocHashCurrentCount = 0;
792 IMallocHashMaxUsed = 0;
794 IMallocHashMaxCollisions = 0;
795 IMallocHashCurrentCollisions = 0;
802 // Returns the number of bytes, that are not freed (leaks)
803 static ULONG AllocHashDeinit(void) {
805 bool bAppend = g_CallstackOutputType != ACOutput_XML;
806 AllocCheckFileOpen(false);//bAppend); // open global log-file
808 if (g_CallstackOutputType == ACOutput_XML)
810 _ftprintf(g_fFile, _T("<MEMREPORT "));
811 WriteDateTime(g_fFile, TRUE);
812 _ftprintf(g_fFile, _T(">\n"));
816 _ftprintf(g_fFile, _T("\n##### Memory Report ########################################\n"));
817 WriteDateTime(g_fFile);
818 _ftprintf(g_fFile, _T("\n"));
821 #ifndef HASH_ENTRY_REMOVE_AT_FREE
822 // output the used memory
823 if (g_CallstackOutputType != ACOutput_XML)
824 _ftprintf(g_fFile, _T("##### Memory used: #########################################\n"));
825 AllocHashOut(g_fFile);
828 // output the Memoty leaks
829 if (g_CallstackOutputType != ACOutput_XML)
830 _ftprintf(g_fFile, _T("\n##### Leaks: ###############################################\n"));
831 ulRet = AllocHashOutLeaks(g_fFile);
833 if (g_CallstackOutputType == ACOutput_Advanced)
835 // output some statistics from the hash-table
836 _ftprintf(g_fFile, _T("***** Hash-Table statistics:\n"));
837 _ftprintf(g_fFile, _T(" Table-Size: %i\n"), ALLOC_HASH_ENTRIES);
838 _ftprintf(g_fFile, _T(" Inserts: %i\n"), AllocHashEntries);
839 _ftprintf(g_fFile, _T(" Freed: %i\n"), AllocHashFreed);
840 _ftprintf(g_fFile, _T(" Sum Collisions: %i\n"), AllocHashCollisions);
841 _ftprintf(g_fFile, _T("\n"));
842 _ftprintf(g_fFile, _T(" Max used: %i\n"), AllocHashMaxUsed);
843 _ftprintf(g_fFile, _T(" Max Collisions: %i\n"), AllocHashMaxCollisions);
848 AllocHashEntryType *pHashEntry, *pHashEntryOld;
850 // Now, free my own memory
851 for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
852 pHashEntry = &AllocHashTable[ulTemp];
853 while(pHashEntry != NULL) {
854 pHashEntryOld = pHashEntry;
855 pHashEntry = pHashEntry->Next;
856 if (pHashEntryOld != &AllocHashTable[ulTemp]) {
857 // now free the dynamically allocated memory
862 // empty the hash-table
863 memset(AllocHashTable, 0, sizeof(AllocHashTable));
865 #ifdef WITH_IMALLOC_SPY
866 // output the Memoty leaks
867 if (g_CallstackOutputType != ACOutput_XML)
868 _ftprintf(g_fFile, _T("\n##### COM-Leaks: ###############################################\n"));
869 ulRet = IMallocHashOutLeaks(g_fFile);
871 if (g_CallstackOutputType == ACOutput_Advanced)
873 // output some statistics from the hash-table
874 _ftprintf(g_fFile, _T("***** Hash-Table statistics:\n"));
875 _ftprintf(g_fFile, _T(" Table-Size: %i\n"), ALLOC_HASH_ENTRIES);
876 _ftprintf(g_fFile, _T(" Inserts: %i\n"), IMallocHashEntries);
877 _ftprintf(g_fFile, _T(" Freed: %i\n"), IMallocHashFreed);
878 _ftprintf(g_fFile, _T(" Sum Collisions: %i\n"), IMallocHashCollisions);
879 _ftprintf(g_fFile, _T("\n"));
880 _ftprintf(g_fFile, _T(" Max used: %i\n"), IMallocHashMaxUsed);
881 _ftprintf(g_fFile, _T(" Max Collisions: %i\n"), IMallocHashMaxCollisions);
886 IMallocHashEntryType *pIMHashEntry, *pIMHashEntryOld;
888 // Gehe jeden Eintrag durch und gebe ihn frei
889 for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
890 pIMHashEntry = &IMallocHashTable[ulTemp];
891 while(pHashEntry != NULL) {
892 pIMHashEntryOld = pIMHashEntry;
893 pIMHashEntry = pIMHashEntry->Next;
894 if (pIMHashEntryOld != &IMallocHashTable[ulTemp]) {
895 // es ist dynamischer Speicher, gebe ihn also frei:
896 _free_dbg(pIMHashEntryOld, _CRT_BLOCK);
900 // Lösche die gesamte Hash-Tabelle
901 memset(IMallocHashTable, 0, sizeof(IMallocHashTable));
905 if (g_CallstackOutputType == ACOutput_XML)
906 _ftprintf(g_fFile, _T("</MEMREPORT>\n"));
912 // The has-function (very simple)
913 static inline ULONG AllocHashFunction(long lRequestID) {
914 // I couldn´t find any better and faster
915 return lRequestID % ALLOC_HASH_ENTRIES;
916 } // AllocHashFunction
919 // lRequestID: Key-Word (RequestID from AllocHook)
920 // pContext: Context-Record, for retrieving Callstack (EIP and EBP is only needed)
921 // nDataSize: How many bytes
922 static void AllocHashInsert(long lRequestID, CONTEXT &Context, size_t nDataSize) {
924 AllocHashEntryType *pHashEntry;
926 // change statistical data
928 AllocHashCurrentCount++;
929 if (AllocHashCurrentCount > AllocHashMaxUsed)
930 AllocHashMaxUsed = AllocHashCurrentCount;
932 // generate hash-value
933 HashIdx = AllocHashFunction(lRequestID);
935 pHashEntry = &AllocHashTable[HashIdx];
936 if (pHashEntry->lRequestID == 0) {
940 // Entry is not empy! make a list of entries for this hash value...
941 // change statistical data
942 // if this happens often, you should increase the hah size or change the heash-function;
943 // to fasten the allocation time
944 AllocHashCollisions++;
945 AllocHashCurrentCollisions++;
946 if (AllocHashCurrentCollisions > AllocHashMaxCollisions)
947 AllocHashMaxCollisions = AllocHashCurrentCollisions;
949 while(pHashEntry->Next != NULL) {
950 pHashEntry = pHashEntry->Next;
953 pHashEntry->Next = (AllocHashEntryType*) calloc(sizeof(AllocHashEntryType), 1);
954 pHashEntry = pHashEntry->Next;
957 pHashEntry->lRequestID = lRequestID; // Key-Word
958 pHashEntry->nDataSize = nDataSize;
959 pHashEntry->Next = NULL;
960 // Get EIP and save it in the record
961 pHashEntry->dwEIPOffset = Context.Eip;
962 if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Eip, &(pHashEntry->pcEIPAddr), MAX_EIP_LEN_BUF, &(pHashEntry->dwEIPLen)) == 0) {
963 // Could not read memory... remove everything...
964 memset(pHashEntry->pcEIPAddr, 0, MAX_EIP_LEN_BUF);
965 pHashEntry->dwEIPLen = 0;
966 pHashEntry->dwEIPOffset = 0;
969 // Get ESP and save it in the record
970 pHashEntry->dwESPOffset = Context.Ebp;
971 if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Ebp, &(pHashEntry->pcESPAddr), MAX_ESP_LEN_BUF, &(pHashEntry->dwESPLen)) == 0) {
972 // Could not read memory... remove everything...
973 memset(pHashEntry->pcESPAddr, 0, MAX_ESP_LEN_BUF);
974 pHashEntry->dwESPLen = 0;
975 pHashEntry->dwESPOffset = 0;
977 // Check if I tried to read too much...
978 if (GetLastError() == ERROR_PARTIAL_COPY)
980 // ask how many I can read:
981 MEMORY_BASIC_INFORMATION MemBuffer;
982 DWORD dwRet = VirtualQuery((LPCVOID) Context.Ebp, &MemBuffer, sizeof(MemBuffer));
985 // calculate the length
986 DWORD len = ((DWORD) MemBuffer.BaseAddress + MemBuffer.RegionSize) - Context.Ebp;
987 if ( (len > 0) && (len < MAX_ESP_LEN_BUF) )
989 // try to read it again (with the shorter length)
990 if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Ebp, &(pHashEntry->pcESPAddr), len, &(pHashEntry->dwESPLen)) == 0)
992 // ok, now everything goes wrong... remove it...
993 memset(pHashEntry->pcESPAddr, 0, MAX_ESP_LEN_BUF);
994 pHashEntry->dwESPLen = 0;
995 pHashEntry->dwESPOffset = 0;
999 pHashEntry->dwESPOffset = Context.Ebp;
1002 } // VirtualQuery was successfully
1003 } // ERROR_PARTIAL_COPY
1008 // If ALLOC_ENTRY_NOT_FOUND is returned, the Key was not found!
1009 // If the Key was found, a pointer to the entry is returned
1010 static AllocHashEntryType *AllocHashFind(long lRequestID) {
1012 AllocHashEntryType *pHashEntry;
1014 // get the Hash-Value
1015 HashIdx = AllocHashFunction(lRequestID);
1017 // Just do some simple checks:
1018 _ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
1020 pHashEntry = &AllocHashTable[HashIdx];
1021 while(pHashEntry != NULL) {
1022 if (pHashEntry->lRequestID == lRequestID) {
1025 pHashEntry = pHashEntry->Next;
1028 // entry was not found!
1029 return (AllocHashEntryType*) ALLOC_ENTRY_NOT_FOUND;
1033 // Return: FALSE (0) : Key was found and removed/marked
1034 // TRUE (!=0): Key was not found
1035 static BOOL AllocHashRemove(long lRequestID) {
1037 AllocHashEntryType *pHashEntry, *pHashEntryLast;
1039 // get the Hash-Value
1040 HashIdx = AllocHashFunction(lRequestID);
1042 // Just do some simple checks:
1043 _ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
1045 pHashEntryLast = NULL;
1046 pHashEntry = &AllocHashTable[HashIdx];
1047 while(pHashEntry != NULL) {
1048 if (pHashEntry->lRequestID == lRequestID) {
1049 #ifdef HASH_ENTRY_REMOVE_AT_FREE
1051 AllocHashCurrentCount--;
1052 // release my memory
1053 if (pHashEntryLast == NULL) {
1054 // It is an entry in the table, so do not release this memory
1055 if (pHashEntry->Next == NULL) {
1056 // It was the last entry, so empty the table entry
1057 memset(&AllocHashTable[HashIdx], 0, sizeof(AllocHashTable[HashIdx]));
1060 // There are some more entries, so shorten the list
1061 AllocHashEntryType *pTmp = pHashEntry->Next;
1062 *pHashEntry = *(pHashEntry->Next);
1068 // now, I am in an dynamic allocated entry
1069 // it was a collision, so decrease the current collision count
1070 AllocHashCurrentCollisions--;
1071 pHashEntryLast->Next = pHashEntry->Next;
1076 // increase the Remove-Count and let the objet stay in memory
1077 pHashEntry->cRemovedFlag++;
1081 pHashEntryLast = pHashEntry;
1082 pHashEntry = pHashEntry->Next;
1085 // if we are here, we could not find the RequestID
1089 // ReadProcMemoryFromHash
1090 // Callback-Funtion for StackWalk for my own CallStack from the Hash-Table-Entries
1091 static BOOL __stdcall ReadProcMemoryFromHash(HANDLE hRequestID, DWORD64 lpBaseAddress, PVOID lpBuffer, DWORD nSize, PDWORD lpNumberOfBytesRead) {
1092 // Try to find the RequestID
1093 AllocHashEntryType *pHashEntry;
1094 *lpNumberOfBytesRead = 0;
1096 pHashEntry = AllocHashFind((LONG) hRequestID);
1097 if (pHashEntry == (AllocHashEntryType*) ALLOC_ENTRY_NOT_FOUND) {
1098 // Not found, so I cannot return any memory
1099 *lpNumberOfBytesRead = 0;
1102 if ( ((DWORD) lpBaseAddress >= pHashEntry->dwESPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwESPOffset+pHashEntry->dwESPLen)) ) {
1103 // Memory is located in ESP:
1104 // Calculate the offset
1105 DWORD dwOffset = (DWORD) lpBaseAddress - pHashEntry->dwESPOffset;
1106 DWORD dwSize = __min(nSize, MAX_ESP_LEN_BUF-dwOffset);
1107 memcpy(lpBuffer, &(pHashEntry->pcESPAddr[dwOffset]), dwSize);
1108 *lpNumberOfBytesRead = dwSize;
1109 if (dwSize != nSize)
1113 if ( ((DWORD) lpBaseAddress >= pHashEntry->dwEIPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwEIPOffset+pHashEntry->dwEIPLen)) ) {
1114 // Memory is located in EIP:
1115 // Calculate the offset
1116 DWORD dwOffset = (DWORD) lpBaseAddress - pHashEntry->dwEIPOffset;
1117 DWORD dwSize = __min(nSize, MAX_ESP_LEN_BUF-dwOffset);
1118 memcpy(lpBuffer, &(pHashEntry->pcEIPAddr[dwOffset]), dwSize);
1119 *lpNumberOfBytesRead = dwSize;
1120 if (dwSize != nSize)
1124 if (*lpNumberOfBytesRead == 0) // Memory could not be found
1130 // AllocHashOutLeaks
1131 // Write all Memory (with callstack) which was not freed yet
1132 // Returns the number of bytes, that are not freed (leaks)
1133 ULONG AllocHashOutLeaks(FILE *fFile) {
1135 AllocHashEntryType *pHashEntry;
1137 ULONG ulLeaksByte = 0;
1139 // Move throu every entry
1140 for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
1141 pHashEntry = &AllocHashTable[ulTemp];
1142 if (pHashEntry->lRequestID != 0) {
1143 while(pHashEntry != NULL) {
1144 if ( (pHashEntry->cRemovedFlag <= 0) || (pHashEntry->cRemovedFlag > 1) ) {
1146 if (g_CallstackOutputType == ACOutput_XML)
1147 _ftprintf(fFile, _T("<LEAK requestID=\"%u\" size=\"%u\">\n"), pHashEntry->lRequestID, pHashEntry->nDataSize);
1149 _ftprintf(fFile, _T("RequestID: %12i, Removed: %i, Size: %12i\n"), pHashEntry->lRequestID, pHashEntry->cRemovedFlag, pHashEntry->nDataSize);
1151 memset( &c, '\0', sizeof c );
1152 c.Eip = pHashEntry->dwEIPOffset;
1153 c.Ebp = pHashEntry->dwESPOffset;
1154 ShowStackRM( NULL, c, fFile, &ReadProcMemoryFromHash, (HANDLE) pHashEntry->lRequestID);
1155 // Count the number of leaky bytes
1156 if (pHashEntry->nDataSize > 0)
1157 ulLeaksByte += pHashEntry->nDataSize;
1159 ulLeaksByte++; // If memory was allocated with zero bytes, then just increase the counter 1
1161 if (g_CallstackOutputType == ACOutput_XML)
1162 _ftprintf(fFile, _T("</LEAK>\n")); // terminate the xml-node
1164 pHashEntry = pHashEntry->Next;
1168 if (g_CallstackOutputType != ACOutput_XML)
1169 _ftprintf(fFile, _T("\n**** Number of leaks: %i\n"), ulCount);
1171 } // AllocHashOutLeaks
1173 // Write all used memory to a file
1174 void AllocHashOut(FILE *fFile) {
1176 AllocHashEntryType *pHashEntry;
1178 for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
1179 pHashEntry = &AllocHashTable[ulTemp];
1180 if (pHashEntry->lRequestID != 0) {
1181 while(pHashEntry != NULL) {
1182 if (g_CallstackOutputType == ACOutput_XML)
1183 _ftprintf(fFile, _T("<MEMUSED requestID=\"%u\" size=\"%u\"\n"), pHashEntry->lRequestID, pHashEntry->nDataSize);
1185 _ftprintf(fFile, _T("RequestID: %12i, Removed: %i, Size: %12i\n"), pHashEntry->lRequestID, pHashEntry->cRemovedFlag, pHashEntry->nDataSize);
1186 pHashEntry = pHashEntry->Next;
1192 /*******************************************************************************
1193 * Ende der Hash-Tabelle
1194 *******************************************************************************/
1197 // The follwoing is copied from dbgint.h:
1200 * For diagnostic purpose, blocks are allocated with extra information and
1201 * stored in a doubly-linked list. This makes all blocks registered with
1202 * how big they are, when they were allocated, and what they are used for.
1205 #define nNoMansLandSize 4
1207 typedef struct _CrtMemBlockHeader
1209 struct _CrtMemBlockHeader * pBlockHeaderNext;
1210 struct _CrtMemBlockHeader * pBlockHeaderPrev;
1214 /* These items are reversed on Win64 to eliminate gaps in the struct
1215 * and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
1216 * maintained in the debug heap.
1225 unsigned char gap[nNoMansLandSize];
1227 * unsigned char data[nDataSize];
1228 * unsigned char anotherGap[nNoMansLandSize];
1230 } _CrtMemBlockHeader;
1231 #define pbData(pblock) ((unsigned char *)((_CrtMemBlockHeader *)pblock + 1))
1232 #define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1)
1240 static BOOL g_bInitialized = FALSE;
1241 static HINSTANCE g_hImagehlpDll = NULL;
1243 static DWORD g_dwShowCount = 0; // increase at every ShowStack-Call
1244 static CRITICAL_SECTION g_csFileOpenClose = {0};
1246 // Is used for syncronising call to MyAllocHook (to prevent reentrant calls)
1247 static LONG g_lMallocCalled = 0;
1249 static _CRT_ALLOC_HOOK pfnOldCrtAllocHook = NULL;
1251 // Deaktivate AllocHook, by increasing the Syncronisation-Counter
1252 //static void DeactivateMallocStackwalker(void) {
1253 // InterlockedIncrement(&g_lMallocCalled);
1257 // MyAllocHook is Single-Threaded, that means the the calls are serialized in the calling function!
1258 // Special case for VC 5
1259 #if _MSC_VER <= 1100
1260 static int MyAllocHook(int nAllocType, void *pvData,
1261 size_t nSize, int nBlockUse, long lRequest,
1262 const char * szFileName, int nLine ) {
1264 static int MyAllocHook(int nAllocType, void *pvData,
1265 size_t nSize, int nBlockUse, long lRequest,
1266 const unsigned char * szFileName, int nLine ) {
1268 static TCHAR *operation[] = { _T(""), _T("ALLOCATIONG"), _T("RE-ALLOCATING"), _T("FREEING") };
1269 static TCHAR *blockType[] = { _T("Free"), _T("Normal"), _T("CRT"), _T("Ignore"), _T("Client") };
1271 #ifdef IGNORE_CRT_ALLOC
1272 if (_BLOCK_TYPE(nBlockUse) == _CRT_BLOCK) // Ignore internal C runtime library allocations
1275 extern int _crtDbgFlag;
1276 if ( ((_CRTDBG_ALLOC_MEM_DF & _crtDbgFlag) == 0) && ( (nAllocType == _HOOK_ALLOC) || (nAllocType == _HOOK_REALLOC) ) )
1278 // Someone has disabled that the runtime should log this allocation
1279 // so we do not log this allocation
1280 if (pfnOldCrtAllocHook != NULL)
1281 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1285 // Prevent from reentrat calls
1286 if (InterlockedIncrement(&g_lMallocCalled) > 1) { // I was already called
1287 InterlockedDecrement(&g_lMallocCalled);
1288 // call the previous alloc hook
1289 if (pfnOldCrtAllocHook != NULL)
1290 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1294 if (g_ulShowStackAtAlloc > 0) {
1295 AllocCheckFileOpen(); // Open logfile
1298 _ASSERT( (nAllocType == _HOOK_ALLOC) || (nAllocType == _HOOK_REALLOC) || (nAllocType == _HOOK_FREE) );
1299 _ASSERT( ( _BLOCK_TYPE(nBlockUse) >= 0 ) && ( _BLOCK_TYPE(nBlockUse) < 5 ) );
1301 if (nAllocType == _HOOK_FREE) { // freeing
1302 // Try to get the header information
1303 if (_CrtIsValidHeapPointer(pvData)) { // it is a valid Heap-Pointer
1305 _CrtMemBlockHeader *pHead;
1306 // get a pointer to memory block header
1307 pHead = pHdr(pvData);
1308 nSize = pHead->nDataSize;
1309 lRequest = pHead->lRequest; // This is the ID!
1311 if (pHead->nBlockUse == _IGNORE_BLOCK)
1313 InterlockedDecrement(&g_lMallocCalled);
1314 if (pfnOldCrtAllocHook != NULL)
1315 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1321 if (g_ulShowStackAtAlloc > 0) {
1322 _ftprintf( g_fFile, _T("##### Memory operation: %s a %d-byte '%s' block (# %ld)"),
1323 operation[nAllocType], nSize, blockType[_BLOCK_TYPE(nBlockUse)], lRequest );
1324 if ( pvData != NULL )
1325 _ftprintf( g_fFile, _T(" at 0x%X"), pvData );
1326 _ftprintf(g_fFile, _T("\n"));
1329 if (nAllocType == _HOOK_FREE) { // freeing:
1330 if (lRequest != 0) { // RequestID was found
1332 // Try to find the RequestID in the Hash-Table, mark it that it was freed
1333 bRet = AllocHashRemove(lRequest);
1334 if(g_ulShowStackAtAlloc > 0) {
1335 if (bRet == FALSE) {
1336 // RequestID not found!
1337 _ftprintf(g_fFile, _T("###### RequestID not found in hash table for FREEING (%i)!\n"), lRequest);
1339 } // g_ulShowStackAtAlloc > 0
1342 if(g_ulShowStackAtAlloc > 0) {
1343 // No valid RequestID found, display error
1344 _ftprintf(g_fFile, _T("###### No valid RequestID for FREEING! (0x%X)\n"), pvData);
1350 if (nAllocType == _HOOK_REALLOC) { // re-allocating
1351 // Try to get the header information
1352 if (_CrtIsValidHeapPointer(pvData)) { // it is a valid Heap-Pointer
1354 LONG lReallocRequest;
1356 _CrtMemBlockHeader *pHead;
1357 // get a pointer to memory block header
1358 pHead = pHdr(pvData);
1359 // Try to find the RequestID in the Hash-Table, mark it that it was freed
1360 lReallocRequest = pHead->lRequest;
1361 bRet = AllocHashRemove(lReallocRequest);
1362 if (g_ulShowStackAtAlloc > 0) {
1363 if (bRet == FALSE) {
1364 // RequestID not found!
1365 _ftprintf(g_fFile, _T("###### RequestID not found in hash table for RE-ALLOCATING (%i)!\n"), lReallocRequest);
1368 _ftprintf(g_fFile, _T("##### Implicit freeing because of re-allocation (# old: %ld, new: %ld)\n"), lReallocRequest, lRequest);
1370 } // g_ulShowStackAtAlloc > 0
1371 } // ValidHeapPointer
1374 if ( (g_ulShowStackAtAlloc < 3) && (nAllocType == _HOOK_FREE) ) {
1375 InterlockedDecrement(&g_lMallocCalled);
1376 // call the previous alloc hook
1377 if (pfnOldCrtAllocHook != NULL)
1378 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1383 if (DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
1384 GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS ) == 0) {
1385 // Something was wrong...
1386 _ftprintf(g_fFile, _T("###### Could not call 'DuplicateHandle' successfully\n"));
1387 InterlockedDecrement(&g_lMallocCalled);
1388 // call the previous alloc hook
1389 if (pfnOldCrtAllocHook != NULL)
1390 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1395 memset( &c, '\0', sizeof c );
1396 c.ContextFlags = CONTEXT_FULL;
1398 // Get the context of this thread
1400 // init CONTEXT record so we know where to start the stackwalk
1401 if ( MyGetCurrentThreadContext( hThread, &c ) == 0) {
1402 if(g_ulShowStackAtAlloc > 1) {
1403 _ftprintf(g_fFile, _T("###### Could not call 'GetThreadContext' successfully\n"));
1405 InterlockedDecrement(&g_lMallocCalled);
1406 // call the previous alloc hook
1407 if (pfnOldCrtAllocHook != NULL)
1408 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1409 CloseHandle(hThread);
1410 return TRUE; // could not get context
1422 if(g_ulShowStackAtAlloc > 1) {
1423 if(g_ulShowStackAtAlloc > 2) {
1424 // output the callstack
1425 ShowStack( hThread, c, g_fFile);
1428 // Output only (re)allocs
1429 if (nAllocType != _HOOK_FREE) {
1430 ShowStack( hThread, c, g_fFile);
1433 } // g_ulShowStackAtAlloc > 1
1434 CloseHandle( hThread );
1436 // Only isert in the Hash-Table if it is not a "freeing"
1437 if (nAllocType != _HOOK_FREE) {
1438 if(lRequest != 0) // Always a valid RequestID should be provided (see comments in the header)
1439 AllocHashInsert(lRequest, c, nSize);
1442 InterlockedDecrement(&g_lMallocCalled);
1443 // call the previous alloc hook
1444 if (pfnOldCrtAllocHook != NULL)
1445 pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
1446 return TRUE; // allow the memory operation to proceed
1452 // ##########################################################################################
1453 // ##########################################################################################
1454 // ##########################################################################################
1455 // ##########################################################################################
1457 #define gle (GetLastError())
1458 #define lenof(a) (sizeof(a) / sizeof((a)[0]))
1459 #define MAXNAMELEN 1024 // max name length for found symbols
1460 #define IMGSYMLEN ( sizeof IMAGEHLP_SYMBOL64 )
1461 #define TTBUFLEN 8096 // for a temp buffer (2^13)
1466 typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess );
1469 // SymFunctionTableAccess64()
1470 typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase );
1473 // SymGetLineFromAddr64()
1474 typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr,
1475 OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line );
1476 tSGLFA pSGLFA = NULL;
1478 // SymGetModuleBase64()
1479 typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr );
1482 // SymGetModuleInfo64()
1483 typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo );
1487 typedef DWORD (__stdcall *tSGO)( VOID );
1490 // SymGetSymFromAddr64()
1491 typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr,
1492 OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol );
1493 tSGSFA pSGSFA = NULL;
1496 typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess );
1499 // SymLoadModule64()
1500 typedef DWORD (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile,
1501 IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll );
1505 typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions );
1509 typedef BOOL (__stdcall *tSW)(
1513 LPSTACKFRAME64 StackFrame,
1514 PVOID ContextRecord,
1515 PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
1516 PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
1517 PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
1518 PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress );
1521 // UnDecorateSymbolName()
1522 typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName,
1523 DWORD UndecoratedLength, DWORD Flags );
1530 std::string imageName;
1531 std::string moduleName;
1535 typedef std::vector< ModuleEntry > ModuleList;
1536 typedef ModuleList::iterator ModuleListIter;
1538 // **************************************** ToolHelp32 ************************
1539 #define MAX_MODULE_NAME32 255
1540 #define TH32CS_SNAPMODULE 0x00000008
1541 #pragma pack( push, 8 )
1542 typedef struct tagMODULEENTRY32
1545 DWORD th32ModuleID; // This module
1546 DWORD th32ProcessID; // owning process
1547 DWORD GlblcntUsage; // Global usage count on the module
1548 DWORD ProccntUsage; // Module usage count in th32ProcessID's context
1549 BYTE * modBaseAddr; // Base address of module in th32ProcessID's context
1550 DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
1551 HMODULE hModule; // The hModule of this module in th32ProcessID's context
1552 char szModule[MAX_MODULE_NAME32 + 1];
1553 char szExePath[MAX_PATH];
1555 typedef MODULEENTRY32 * PMODULEENTRY32;
1556 typedef MODULEENTRY32 * LPMODULEENTRY32;
1561 static bool GetModuleListTH32(ModuleList& modules, DWORD pid, FILE *fLogFile)
1563 // CreateToolhelp32Snapshot()
1564 typedef HANDLE (__stdcall *tCT32S)(DWORD dwFlags, DWORD th32ProcessID);
1566 typedef BOOL (__stdcall *tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
1568 typedef BOOL (__stdcall *tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme);
1571 const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") };
1572 HINSTANCE hToolhelp;
1579 me.dwSize = sizeof(me);
1584 for (i = 0; i<lenof(dllname); i++ )
1586 hToolhelp = LoadLibrary( dllname[i] );
1587 if (hToolhelp == NULL)
1589 pCT32S = (tCT32S) GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot");
1590 pM32F = (tM32F) GetProcAddress(hToolhelp, "Module32First");
1591 pM32N = (tM32N) GetProcAddress(hToolhelp, "Module32Next");
1592 if ( pCT32S != 0 && pM32F != 0 && pM32N != 0 )
1593 break; // found the functions!
1594 FreeLibrary(hToolhelp);
1598 if (hToolhelp == NULL)
1601 hSnap = pCT32S( TH32CS_SNAPMODULE, pid );
1602 if (hSnap == (HANDLE) -1)
1605 keepGoing = !!pM32F( hSnap, &me );
1608 e.imageName = me.szExePath;
1609 e.moduleName = me.szModule;
1610 e.baseAddress = (DWORD) me.modBaseAddr;
1611 e.size = me.modBaseSize;
1612 modules.push_back( e );
1613 keepGoing = !!pM32N( hSnap, &me );
1617 FreeLibrary(hToolhelp);
1619 return modules.size() != 0;
1620 } // GetModuleListTH32
1623 // **************************************** PSAPI ************************
1624 typedef struct _MODULEINFO {
1628 } MODULEINFO, *LPMODULEINFO;
1630 static bool GetModuleListPSAPI(ModuleList &modules, DWORD pid, HANDLE hProcess, FILE *fLogFile)
1632 // EnumProcessModules()
1633 typedef BOOL (__stdcall *tEPM)(HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded );
1634 // GetModuleFileNameEx()
1635 typedef DWORD (__stdcall *tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
1636 // GetModuleBaseName()
1637 typedef DWORD (__stdcall *tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
1638 // GetModuleInformation()
1639 typedef BOOL (__stdcall *tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize );
1654 hPsapi = LoadLibrary( _T("psapi.dll") );
1660 pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" );
1661 pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" );
1662 pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" );
1663 pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
1664 if ( pEPM == 0 || pGMFNE == 0 || pGMBN == 0 || pGMI == 0 )
1666 // we couldn´t find all functions
1667 FreeLibrary( hPsapi );
1671 hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof HMODULE));
1672 tt = (char*) malloc(sizeof(char) * TTBUFLEN);
1674 if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) )
1676 _ftprintf(fLogFile, _T("%lu: EPM failed, GetLastError = %lu\n"), g_dwShowCount, gle );
1680 if ( cbNeeded > TTBUFLEN )
1682 _ftprintf(fLogFile, _T("%lu: More than %lu module handles. Huh?\n"), g_dwShowCount, lenof( hMods ) );
1686 for ( i = 0; i < cbNeeded / sizeof hMods[0]; i++ )
1688 // base address, size
1689 pGMI(hProcess, hMods[i], &mi, sizeof mi );
1690 e.baseAddress = (DWORD) mi.lpBaseOfDll;
1691 e.size = mi.SizeOfImage;
1694 pGMFNE(hProcess, hMods[i], tt, TTBUFLEN );
1698 pGMBN(hProcess, hMods[i], tt, TTBUFLEN );
1701 modules.push_back(e);
1706 FreeLibrary(hPsapi);
1710 return modules.size() != 0;
1711 } // GetModuleListPSAPI
1714 static bool GetModuleList(ModuleList& modules, DWORD pid, HANDLE hProcess, FILE *fLogFile)
1716 // first try toolhelp32
1717 if (GetModuleListTH32(modules, pid, fLogFile) )
1720 return GetModuleListPSAPI(modules, pid, hProcess, fLogFile);
1724 static void EnumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid, FILE *fLogFile )
1726 static ModuleList modules;
1727 static ModuleListIter it;
1730 // fill in module list
1731 GetModuleList(modules, pid, hProcess, fLogFile);
1733 for ( it = modules.begin(); it != modules.end(); ++ it )
1735 // SymLoadModule() wants writeable strings
1736 img = strdup(it->imageName.c_str());
1737 mod = strdup(it->moduleName.c_str());
1739 pSLM( hProcess, 0, img, mod, it->baseAddress, it->size );
1745 } // EnumAndLoadModuleSymbols
1747 static int InitStackWalk(void)
1749 if (g_bInitialized != FALSE)
1750 return 0; // already initialized
1752 // 02-12-19: Now we only support dbghelp.dll!
1753 // To use it on NT you have to install the redistrubutable for DBGHELP.DLL
1754 g_hImagehlpDll = LoadLibrary( _T("dbghelp.dll") );
1755 if ( g_hImagehlpDll == NULL )
1757 printf( "LoadLibrary( \"dbghelp.dll\" ): GetLastError = %lu\n", gle );
1758 g_bInitialized = FALSE;
1762 // now we only support the newer dbghlp.dll with the "64"-functions (StackWalk64, a.s.o.)
1763 // If your dbghlp.dll does not support this, please download the redistributable from MS
1764 // Normally from: http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=CD1FC4B2-0885-47F4-AF45-7FD5E14DB6C0
1766 pSC = (tSC) GetProcAddress( g_hImagehlpDll, "SymCleanup" );
1767 pSFTA = (tSFTA) GetProcAddress( g_hImagehlpDll, "SymFunctionTableAccess64" );
1768 pSGLFA = (tSGLFA) GetProcAddress( g_hImagehlpDll, "SymGetLineFromAddr64" );
1769 pSGMB = (tSGMB) GetProcAddress( g_hImagehlpDll, "SymGetModuleBase64" );
1770 pSGMI = (tSGMI) GetProcAddress( g_hImagehlpDll, "SymGetModuleInfo64" );
1771 pSGO = (tSGO) GetProcAddress( g_hImagehlpDll, "SymGetOptions" );
1772 pSGSFA = (tSGSFA) GetProcAddress( g_hImagehlpDll, "SymGetSymFromAddr64" );
1773 pSI = (tSI) GetProcAddress( g_hImagehlpDll, "SymInitialize" );
1774 pSSO = (tSSO) GetProcAddress( g_hImagehlpDll, "SymSetOptions" );
1775 pSW = (tSW) GetProcAddress( g_hImagehlpDll, "StackWalk64" );
1776 pUDSN = (tUDSN) GetProcAddress( g_hImagehlpDll, "UnDecorateSymbolName" );
1777 pSLM = (tSLM) GetProcAddress( g_hImagehlpDll, "SymLoadModule64" );
1779 if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||
1780 pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||
1781 pSW == NULL || pUDSN == NULL || pSLM == NULL )
1783 printf( "GetProcAddress(): some required function not found.\n" );
1784 FreeLibrary( g_hImagehlpDll );
1785 g_bInitialized = FALSE;
1789 g_bInitialized = TRUE;
1790 InitializeCriticalSection(&g_csFileOpenClose);
1794 // This function if NOT multi-threading capable
1795 // It should only be called from the main-Function!
1796 int InitAllocCheckWN(eAllocCheckOutput eOutput, LPCTSTR pszFileName, ULONG ulShowStackAtAlloc) {
1797 if (g_bInitialized) {
1798 return 2; // already initialized!
1800 if (ulShowStackAtAlloc <= 3)
1801 g_ulShowStackAtAlloc = ulShowStackAtAlloc;
1803 g_ulShowStackAtAlloc = 0;
1805 if (pszFileName != NULL)
1806 g_pszAllocLogName = _tcsdup(pszFileName);
1808 g_pszAllocLogName = NULL;
1810 g_CallstackOutputType = eOutput;
1815 #ifdef WITH_IMALLOC_SPY
1817 // erzeuge mein malloc-Spy object
1818 LPMALLOCSPY pMallocSpy = new CMallocSpy(); // wird später durch Release freigegeben
1819 if (pMallocSpy != NULL)
1821 // CoInitilize(); // ??? Ist dies notwendig ?
1822 hr = CoRegisterMallocSpy(pMallocSpy);
1825 _tprintf(_T("\nCoRegisterMallocSpay failed with %.8x"), hr);
1830 // save the previous alloc hook
1831 pfnOldCrtAllocHook = _CrtSetAllocHook(MyAllocHook);
1834 return InitStackWalk();
1835 } // InitAllocCheckWN
1837 static TCHAR s_szExceptionLogFileName[_MAX_PATH] = _T("\\exceptions.log"); // default
1838 static BOOL s_bUnhandledExeptionFilterSet = FALSE;
1839 static LONG __stdcall CrashHandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
1841 if (pExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
1843 static char MyStack[1024*128]; // be sure that we have enought space...
1844 // it assumes that DS and SS are the same!!! (this is the case for Win32)
1845 // change the stack only if the selectors are the same (this is the case for Win32)
1846 //__asm push offset MyStack[1024*128];
1848 __asm mov eax,offset MyStack[1024*128];
1853 lRet = StackwalkFilter(pExPtrs, /*EXCEPTION_CONTINUE_SEARCH*/EXCEPTION_EXECUTE_HANDLER, s_szExceptionLogFileName);
1856 _T("*** Unhandled Exception!\n")
1857 _T(" ExpCode: 0x%8.8X\n")
1858 _T(" ExpFlags: %d\n")
1859 _T(" ExpAddress: 0x%8.8X\n")
1860 _T(" Please report!"),
1861 pExPtrs->ExceptionRecord->ExceptionCode,
1862 pExPtrs->ExceptionRecord->ExceptionFlags,
1863 pExPtrs->ExceptionRecord->ExceptionAddress);
1864 FatalAppExit(-1,lString);
1868 int InitAllocCheck(eAllocCheckOutput eOutput, BOOL bSetUnhandledExeptionFilter, ULONG ulShowStackAtAlloc) // will create the filename by itself
1870 TCHAR szModName[_MAX_PATH];
1871 if (GetModuleFileName(NULL, szModName, sizeof(szModName)/sizeof(TCHAR)) != 0)
1873 _tcscpy(s_szExceptionLogFileName, szModName);
1874 if (eOutput == ACOutput_XML)
1875 _tcscat(s_szExceptionLogFileName, _T(".exp.xml"));
1877 _tcscat(s_szExceptionLogFileName, _T(".exp.log"));
1879 if (eOutput == ACOutput_XML)
1880 _tcscat(szModName, _T(".mem.xml-leaks"));
1882 _tcscat(szModName, _T(".mem.log"));
1886 if (eOutput == ACOutput_XML)
1887 _tcscpy(szModName, _T("\\mem-leaks.xml-leaks")); // default
1889 _tcscpy(szModName, _T("\\mem-leaks.log")); // default
1892 if ((bSetUnhandledExeptionFilter != FALSE) && (s_bUnhandledExeptionFilterSet == FALSE) )
1894 // set global exception handler (for handling all unhandled exceptions)
1895 SetUnhandledExceptionFilter(CrashHandlerExceptionFilter);
1896 s_bUnhandledExeptionFilterSet = TRUE;
1899 return InitAllocCheckWN(eOutput, szModName, ulShowStackAtAlloc);
1903 // This function if NOT multi-threading capable
1904 // It should only be called from the main-Function!
1905 // Returns the number of bytes that are not freed (leaks)
1906 ULONG DeInitAllocCheck(void) {
1908 if (g_bInitialized) {
1911 InterlockedIncrement(&g_lMallocCalled); // No deactivate MyAllocHook, because StackWalker will allocate some memory)
1912 ulRet = AllocHashDeinit(); // output the not freed memory
1913 // remove the hook and set the old one
1914 _CrtSetAllocHook(pfnOldCrtAllocHook);
1916 #ifdef WITH_IMALLOC_SPY
1917 CoRevokeMallocSpy();
1922 EnterCriticalSection(&g_csFileOpenClose); // wait until a running stack dump was created
1923 g_bInitialized = FALSE;
1925 // de-init symbol handler etc. (SymCleanup())
1927 pSC( GetCurrentProcess() );
1928 FreeLibrary( g_hImagehlpDll );
1930 LeaveCriticalSection(&g_csFileOpenClose);
1931 if (g_pszAllocLogName != NULL) {
1932 free(g_pszAllocLogName);
1933 g_pszAllocLogName = NULL;
1935 if (g_fFile != NULL) {
1940 DeleteCriticalSection(&g_csFileOpenClose);
1941 InterlockedDecrement(&g_lMallocCalled);
1944 if (s_bUnhandledExeptionFilterSet != TRUE)
1946 SetUnhandledExceptionFilter(NULL);
1947 s_bUnhandledExeptionFilterSet = FALSE;
1950 } // DeInitAllocCheck
1954 void OnlyInstallUnhandeldExceptionFilter(eAllocCheckOutput eOutput)
1956 if (s_bUnhandledExeptionFilterSet == FALSE)
1958 TCHAR szModName[_MAX_PATH];
1959 if (GetModuleFileName(NULL, szModName, sizeof(szModName)/sizeof(TCHAR)) != 0)
1961 _tcscpy(s_szExceptionLogFileName, szModName);
1962 if (eOutput == ACOutput_XML)
1963 _tcscat(s_szExceptionLogFileName, _T(".exp.xml"));
1965 _tcscat(s_szExceptionLogFileName, _T(".exp.log"));
1967 if (eOutput == ACOutput_XML)
1968 _tcscat(szModName, _T(".mem.xml-leaks"));
1970 _tcscat(szModName, _T(".mem.log"));
1974 if (eOutput == ACOutput_XML)
1975 _tcscpy(szModName, _T("\\mem-leaks.xml-leaks")); // default
1977 _tcscpy(szModName, _T("\\mem-leaks.log")); // default
1979 // set it again; WARNING: this will override the setting for a possible AllocCheck-Setting
1980 g_CallstackOutputType = eOutput;
1982 // set global exception handler (for handling all unhandled exceptions)
1983 SetUnhandledExceptionFilter(CrashHandlerExceptionFilter);
1984 s_bUnhandledExeptionFilterSet = TRUE;
1990 static TCHAR *GetExpectionCodeText(DWORD dwExceptionCode) {
1991 switch(dwExceptionCode) {
1992 case EXCEPTION_ACCESS_VIOLATION: return _T("ACCESS VIOLATION");
1993 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return _T("ARRAY BOUNDS EXCEEDED");
1994 case EXCEPTION_BREAKPOINT: return _T("BREAKPOINT");
1995 case EXCEPTION_DATATYPE_MISALIGNMENT: return _T("DATATYPE MISALIGNMENT");
1996 case EXCEPTION_FLT_DENORMAL_OPERAND: return _T("FLT DENORMAL OPERAND");
1997 case EXCEPTION_FLT_DIVIDE_BY_ZERO: return _T("FLT DIVIDE BY ZERO");
1998 case EXCEPTION_FLT_INEXACT_RESULT: return _T("FLT INEXACT RESULT");
1999 case EXCEPTION_FLT_INVALID_OPERATION: return _T("FLT INVALID OPERATION");
2000 case EXCEPTION_FLT_OVERFLOW: return _T("FLT OVERFLOW");
2001 case EXCEPTION_FLT_STACK_CHECK: return _T("FLT STACK CHECK");
2002 case EXCEPTION_FLT_UNDERFLOW: return _T("FLT UNDERFLOW");
2003 case EXCEPTION_ILLEGAL_INSTRUCTION: return _T("ILLEGAL INSTRUCTION");
2004 case EXCEPTION_IN_PAGE_ERROR: return _T("IN PAGE ERROR");
2005 case EXCEPTION_INT_DIVIDE_BY_ZERO: return _T("INT DIVIDE BY ZERO");
2006 case EXCEPTION_INT_OVERFLOW: return _T("INT OVERFLOW");
2007 case EXCEPTION_INVALID_DISPOSITION: return _T("INVALID DISPOSITION");
2008 case EXCEPTION_NONCONTINUABLE_EXCEPTION: return _T("NONCONTINUABLE EXCEPTION");
2009 case EXCEPTION_PRIV_INSTRUCTION: return _T("PRIV INSTRUCTION");
2010 case EXCEPTION_SINGLE_STEP: return _T("SINGLE STEP");
2011 case EXCEPTION_STACK_OVERFLOW: return _T("STACK OVERFLOW");
2012 case DBG_CONTROL_C : return _T("DBG CONTROL C ");
2014 return _T("<unkown exception>");
2016 } // GetExpectionCodeText
2018 // Function is not multi-threading safe, because of static char!
2019 static TCHAR *GetAdditionalExpectionCodeText(PEXCEPTION_RECORD pExceptionRecord) {
2020 static TCHAR szTemp[100];
2022 switch(pExceptionRecord->ExceptionCode) {
2023 case EXCEPTION_ACCESS_VIOLATION:
2024 if (pExceptionRecord->NumberParameters == 2) {
2025 switch(pExceptionRecord->ExceptionInformation[0]) {
2026 case 0: // read attempt
2027 _stprintf(szTemp, _T(" read attempt to address 0x%8.8X "), pExceptionRecord->ExceptionInformation[1]);
2029 case 1: // write attempt
2030 _stprintf(szTemp, _T(" write attempt to address 0x%8.8X "), pExceptionRecord->ExceptionInformation[1]);
2035 } // if (pExceptionRecord->NumberParameters == 2)
2039 } // switch(pExceptionRecord->ExceptionCode)
2040 } // GetAdditionalExpectionCodeText
2042 std::string SimpleXMLEncode(PCSTR szText)
2046 for (size_t i=0; i<strlen(szText); i++)
2051 szRet.append("&");
2054 szRet.append("<");
2057 szRet.append(">");
2060 szRet.append(""");
2063 szRet.append("'");
2073 // #################################################################################
2074 // #################################################################################
2075 // Here the Stackwalk-Part begins.
2076 // Some of the code is from an example from a book
2077 // But I couldn´t find the reference anymore... sorry...
2078 // If someone knowns, please let me know...
2079 // #################################################################################
2080 // #################################################################################
2083 // if you use C++ exception handling: install a translator function
2084 // with set_se_translator(). In the context of that function (but *not*
2085 // afterwards), you can either do your stack dump, or save the CONTEXT
2086 // record as a local copy. Note that you must do the stack sump at the
2087 // earliest opportunity, to avoid the interesting stackframes being gone
2088 // by the time you do the dump.
2091 // - EXCEPTION_CONTINUE_SEARCH: exception wird weitergereicht
2092 // - EXCEPTION_CONTINUE_EXECUTION:
2093 // - EXCEPTION_EXECUTE_HANDLER:
2094 DWORD StackwalkFilter( EXCEPTION_POINTERS *ep, DWORD status, LPCTSTR pszLogFile)
2097 FILE *fFile = stdout; // default to stdout
2099 if (pszLogFile != NULL) { // a filename is provided
2101 fFile = _tfopen(pszLogFile, _T("a"));
2102 if (fFile != NULL) { // Is the file too big?
2104 fseek(fFile, 0, SEEK_END);
2105 size = ftell(fFile); // Get the size of the file
2106 if (size >= LOG_FILE_MAX_SIZE) {
2107 TCHAR *pszTemp = (TCHAR*) malloc(MAX_PATH);
2110 _tcscpy(pszTemp, pszLogFile);
2111 _tcscat(pszTemp, _T(".old"));
2112 _tremove(pszTemp); // Remove an old file, if exists
2113 _trename(pszLogFile, pszTemp); // rename the actual file
2114 fFile = _tfopen(pszLogFile, _T("w")); // create a new file
2118 } // if (pszLogFile != NULL)
2119 if (fFile == NULL) {
2123 // Write infos about the exception
2124 if (g_CallstackOutputType == ACOutput_XML)
2126 _ftprintf(fFile, _T("<EXCEPTION code=\"%8.8X\" addr=\"%8.8X\" "),
2127 ep->ExceptionRecord->ExceptionCode,
2128 ep->ExceptionRecord->ExceptionAddress);
2129 WriteDateTime(fFile, TRUE);
2130 _ftprintf(fFile, _T("code_desc=\"%s\" more_desc=\"%s\">\n"), GetExpectionCodeText(ep->ExceptionRecord->ExceptionCode),
2131 GetAdditionalExpectionCodeText(ep->ExceptionRecord));
2135 _ftprintf(fFile, _T("######## EXCEPTION: 0x%8.8X at address: 0x%8.8X"),
2136 ep->ExceptionRecord->ExceptionCode,
2137 ep->ExceptionRecord->ExceptionAddress);
2138 _ftprintf(fFile, _T(": %s %s\n"), GetExpectionCodeText(ep->ExceptionRecord->ExceptionCode),
2139 GetAdditionalExpectionCodeText(ep->ExceptionRecord));
2142 DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
2143 GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS );
2144 ShowStack( hThread, *(ep->ContextRecord), fFile);
2145 CloseHandle( hThread );
2147 if (g_CallstackOutputType == ACOutput_XML)
2148 _ftprintf(fFile, _T("</EXCEPTION>\n"));
2153 } // StackwalkFilter
2155 void ShowStack( HANDLE hThread, CONTEXT& c, LPCTSTR pszLogFile)
2157 FILE *fFile = stdout; // default to stdout
2159 if (pszLogFile != NULL) { // a filename is available
2161 fFile = _tfopen(pszLogFile, _T("a"));
2162 if (fFile != NULL) { // Is the file too big?
2164 fseek(fFile, 0, SEEK_END);
2165 size = ftell(fFile); // Get the size of the file
2166 if (size >= LOG_FILE_MAX_SIZE) {
2167 TCHAR *pszTemp = (TCHAR*) malloc(MAX_PATH);
2170 _tcscpy(pszTemp, pszLogFile);
2171 _tcscat(pszTemp, _T(".old"));
2172 _tremove(pszTemp); // Remove an old file, if exists
2173 _trename(pszLogFile, pszTemp); // rename the actual file
2174 fFile = _tfopen(pszLogFile, _T("w")); // open new file
2178 } // if (pszLogFile != NULL)
2179 if (fFile == NULL) {
2183 ShowStack( hThread, c, fFile);
2189 static void ShowStack( HANDLE hThread, CONTEXT& c, FILE *fLogFile) {
2190 ShowStackRM(hThread, c, fLogFile, NULL, GetCurrentProcess());
2193 static void ShowStackRM( HANDLE hThread, CONTEXT& c, FILE *fLogFile, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryFunction, HANDLE hSWProcess) {
2194 // normally, call ImageNtHeader() and use machine info from PE header
2195 // but we assume that it is an I386 image...
2196 DWORD imageType = IMAGE_FILE_MACHINE_I386;
2197 HANDLE hProcess = GetCurrentProcess(); // hProcess normally comes from outside but we only do the stackdump in our own process
2198 int frameNum; // counts walked frames
2199 DWORD64 offsetFromSymbol; // tells us how far from the symbol we were
2200 DWORD offsetFromLine; // tells us how far from the line we were
2201 DWORD symOptions; // symbol handler settings
2203 static IMAGEHLP_SYMBOL64 *pSym = NULL;
2204 char undName[MAXNAMELEN]; // undecorated name
2205 char undFullName[MAXNAMELEN]; // undecorated name with all shenanigans
2206 IMAGEHLP_MODULE64 Module;
2207 IMAGEHLP_LINE64 Line;
2210 std::string symSearchPath;
2212 static bool bFirstTime = TRUE;
2214 // If no logfile is present, outpur to "stdout"
2215 if (fLogFile == NULL) {
2219 STACKFRAME64 s; // in/out stackframe
2220 memset( &s, '\0', sizeof s );
2222 if ( (g_bInitialized == FALSE) && (bFirstTime == TRUE) ) {
2226 if (g_bInitialized == FALSE)
2228 // Could not init!!!!
2230 _ftprintf(fLogFile, _T("%lu: Stackwalker not initialized (or was not able to initialize)!\n"), g_dwShowCount);
2234 // Critical section begin...
2235 EnterCriticalSection(&g_csFileOpenClose);
2237 InterlockedIncrement((long*) &g_dwShowCount); // erhöhe counter
2240 // NOTE: normally, the exe directory and the current directory should be taken
2241 // from the target process. The current dir would be gotten through injection
2242 // of a remote thread; the exe fir through either ToolHelp32 or PSAPI.
2245 pSym = (IMAGEHLP_SYMBOL64 *) malloc( IMGSYMLEN + MAXNAMELEN );
2246 if (!pSym) goto cleanup; // not enough memory...
2249 if (g_CallstackOutputType != ACOutput_XML)
2251 _ftprintf(fLogFile, _T("%lu: "), g_dwShowCount);
2252 WriteDateTime(fLogFile);
2253 _ftprintf(fLogFile, _T("\n"));
2261 tt = (CHAR*) malloc(sizeof(CHAR) * TTBUFLEN); // Get the temporary buffer
2262 if (!tt) goto cleanup; // not enough memory...
2264 // build symbol search path from:
2266 // current directory
2267 if ( GetCurrentDirectoryA( TTBUFLEN, tt ) )
2268 symSearchPath += tt + std::string( ";" );
2269 // dir with executable
2270 if ( GetModuleFileNameA( 0, tt, TTBUFLEN ) )
2272 for ( p = tt + strlen( tt ) - 1; p >= tt; -- p )
2274 // locate the rightmost path separator
2275 if ( *p == '\\' || *p == '/' || *p == ':' )
2278 // if we found one, p is pointing at it; if not, tt only contains
2279 // an exe name (no path), and p points before its first byte
2280 if ( p != tt ) // path sep found?
2282 if ( *p == ':' ) // we leave colons in place
2284 *p = '\0'; // eliminate the exe name and last path sep
2285 symSearchPath += tt + std::string( ";" );
2288 // environment variable _NT_SYMBOL_PATH
2289 if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", tt, TTBUFLEN ) )
2290 symSearchPath += tt + std::string( ";" );
2291 // environment variable _NT_ALTERNATE_SYMBOL_PATH
2292 if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", tt, TTBUFLEN ) )
2293 symSearchPath += tt + std::string( ";" );
2294 // environment variable SYSTEMROOT
2295 if ( GetEnvironmentVariableA( "SYSTEMROOT", tt, TTBUFLEN ) )
2296 symSearchPath += tt + std::string( ";" );
2300 if ( symSearchPath.size() > 0 ) // if we added anything, we have a trailing semicolon
2301 symSearchPath = symSearchPath.substr( 0, symSearchPath.size() - 1 );
2303 // why oh why does SymInitialize() want a writeable string?
2304 strncpy( tt, symSearchPath.c_str(), TTBUFLEN );
2305 tt[TTBUFLEN - 1] = '\0'; // if strncpy() overruns, it doesn't add the null terminator
2307 // init symbol handler stuff (SymInitialize())
2308 if ( ! pSI( hProcess, tt, false ) )
2310 if (g_CallstackOutputType != ACOutput_XML)
2311 _ftprintf(fLogFile, _T("%lu: SymInitialize(): GetLastError = %lu\n"), g_dwShowCount, gle );
2317 symOptions = pSGO();
2318 symOptions |= SYMOPT_LOAD_LINES;
2319 symOptions &= ~SYMOPT_UNDNAME;
2320 symOptions &= ~SYMOPT_DEFERRED_LOADS;
2321 pSSO( symOptions ); // SymSetOptions()
2323 // Enumerate modules and tell dbghlp.dll about them.
2324 // On NT, this is not necessary, but it won't hurt.
2325 EnumAndLoadModuleSymbols( hProcess, GetCurrentProcessId(), fLogFile );
2329 } // bFirstTime = TRUE
2332 // init STACKFRAME for first call
2333 // Notes: AddrModeFlat is just an assumption. I hate VDM debugging.
2334 // Notes: will have to be #ifdef-ed for Alphas; MIPSes are dead anyway,
2335 // and good riddance.
2336 s.AddrPC.Offset = c.Eip;
2337 s.AddrPC.Mode = AddrModeFlat;
2338 s.AddrFrame.Offset = c.Ebp;
2339 s.AddrFrame.Mode = AddrModeFlat;
2340 s.AddrStack.Offset = c.Ebp;
2341 s.AddrStack.Mode = AddrModeFlat;
2343 memset( pSym, '\0', IMGSYMLEN + MAXNAMELEN );
2344 pSym->SizeOfStruct = IMGSYMLEN;
2345 pSym->MaxNameLength = MAXNAMELEN;
2347 memset( &Line, '\0', sizeof Line );
2348 Line.SizeOfStruct = sizeof Line;
2350 memset( &Module, '\0', sizeof Module );
2351 Module.SizeOfStruct = sizeof Module;
2353 for ( frameNum = 0; ; ++ frameNum )
2355 // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64())
2356 // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
2357 // assume that either you are done, or that the stack is so hosed that the next
2358 // deeper frame could not be found.
2359 // CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386!
2360 if ( ! pSW( imageType, hSWProcess, hThread, &s, NULL, ReadMemoryFunction, pSFTA, pSGMB, NULL ) )
2363 bXMLTagWrote = FALSE;
2365 if (g_CallstackOutputType == ACOutput_Advanced)
2366 _ftprintf(fLogFile, _T("\n%lu: %3d"), g_dwShowCount, frameNum);
2367 if ( s.AddrPC.Offset == 0 )
2369 // Special case: If we are here, we have no valid callstack entry!
2370 switch(g_CallstackOutputType)
2372 case ACOutput_Simple:
2373 _ftprintf(fLogFile, _T("%lu: (-nosymbols- PC == 0)\n"), g_dwShowCount);
2375 case ACOutput_Advanced:
2376 _ftprintf(fLogFile, _T(" (-nosymbols- PC == 0)\n"));
2380 _ftprintf(fLogFile, _T("<STACKENTRY decl=\"(-nosymbols- PC == 0)\"/>\n"));
2386 // we seem to have a valid PC
2389 offsetFromSymbol = 0;
2390 // show procedure info (SymGetSymFromAddr())
2391 if ( ! pSGSFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) )
2393 if (g_CallstackOutputType == ACOutput_Advanced)
2396 _ftprintf(fLogFile, _T(" SymGetSymFromAddr(): GetLastError = %lu\n"), gle );
2398 _ftprintf(fLogFile, _T("\n"));
2403 // UnDecorateSymbolName()
2404 pUDSN( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY );
2405 pUDSN( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE );
2406 if (g_CallstackOutputType == ACOutput_Advanced)
2408 if (strlen(undName) > 0)
2409 fprintf(fLogFile, " %s %+ld bytes\n", undName, (long) offsetFromSymbol );
2412 fprintf(fLogFile, " Sig: %s %+ld bytes\n", pSym->Name, (long) offsetFromSymbol );
2413 strcpy(undName, pSym->Name);
2415 fprintf(fLogFile, "%lu: Decl: %s\n", g_dwShowCount, undFullName );
2418 //if (g_CallstackOutputType == ACOutput_XML)
2419 // fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
2421 // show line number info, NT5.0-method (SymGetLineFromAddr())
2423 if ( pSGLFA != NULL )
2424 { // yes, we have SymGetLineFromAddr()
2425 if ( ! pSGLFA( hProcess, s.AddrPC.Offset, &offsetFromLine, &Line ) )
2427 if ( (gle != 487) && (frameNum > 0) ) // ignore error for first frame
2429 if (g_CallstackOutputType == ACOutput_XML)
2431 _ftprintf(fLogFile, _T("<STACKENTRY "));
2432 bXMLTagWrote = TRUE;
2433 fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
2434 _ftprintf(fLogFile, _T("srcfile=\"SymGetLineFromAddr(): GetLastError = %lu\" "), gle);
2437 _ftprintf(fLogFile, _T("%lu: SymGetLineFromAddr(): GetLastError = %lu\n"), g_dwShowCount, gle );
2442 switch(g_CallstackOutputType)
2444 case ACOutput_Advanced:
2445 fprintf(fLogFile, "%lu: Line: %s(%lu) %+ld bytes\n", g_dwShowCount,
2446 Line.FileName, Line.LineNumber, offsetFromLine );
2448 case ACOutput_Simple:
2449 fprintf(fLogFile, "%lu: %s(%lu) %+ld bytes (%s)\n", g_dwShowCount,
2450 Line.FileName, Line.LineNumber, offsetFromLine, undName);
2453 _ftprintf(fLogFile, _T("<STACKENTRY "));
2454 bXMLTagWrote = TRUE;
2455 fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
2456 fprintf(fLogFile, "srcfile=\"%s\" line=\"%lu\" line_offset=\"%+ld\" ",
2457 SimpleXMLEncode(Line.FileName).c_str(), Line.LineNumber, offsetFromLine, undName);
2461 } // yes, we have SymGetLineFromAddr()
2463 // show module info (SymGetModuleInfo())
2464 if ( (g_CallstackOutputType == ACOutput_Advanced) || (g_CallstackOutputType == ACOutput_XML) )
2466 if ( ! pSGMI( hProcess, s.AddrPC.Offset, &Module ) )
2468 if (g_CallstackOutputType == ACOutput_Advanced)
2469 _ftprintf(fLogFile, _T("%lu: SymGetModuleInfo): GetLastError = %lu\n"), g_dwShowCount, gle );
2472 { // got module info OK
2474 switch ( Module.SymType )
2477 strcpy( ty, "-nosymbols-" );
2480 strcpy( ty, "COFF" );
2486 strcpy( ty, "PDB" );
2489 strcpy( ty, "-exported-" );
2492 strcpy( ty, "-deferred-" );
2495 strcpy( ty, "SYM" );
2497 #if API_VERSION_NUMBER >= 9
2499 strcpy( ty, "DIA" );
2503 _snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
2507 if (g_CallstackOutputType == ACOutput_XML)
2509 // now, check if the XML-Entry is written...
2510 if (bXMLTagWrote == FALSE)
2512 _ftprintf(fLogFile, _T("<STACKENTRY "));
2513 bXMLTagWrote = TRUE;
2514 fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
2515 _ftprintf(fLogFile, _T("srcfile=\"\" "));
2516 bXMLTagWrote = TRUE;
2520 if (g_CallstackOutputType == ACOutput_Advanced)
2522 fprintf(fLogFile, "%lu: Mod: %s, base: %08lxh\n", g_dwShowCount,
2523 Module.ModuleName, Module.BaseOfImage );
2524 if (Module.SymType == SymNone) { // Gebe nur aus, wenn keine Symbole vorhanden sind!
2525 _ftprintf(fLogFile, _T("%lu: Offset: 0x%8.8x\n"), g_dwShowCount, s.AddrPC.Offset);
2526 fprintf(fLogFile, "%lu: Sym: type: %s, file: %s\n", g_dwShowCount,
2527 ty, Module.LoadedImageName );
2533 if (bXMLTagWrote == TRUE)
2534 fprintf(fLogFile, "module=\"%s\" base=\"%08lx\" ", Module.ModuleName, Module.BaseOfImage);
2536 } // got module info OK
2538 if ( (g_CallstackOutputType == ACOutput_XML) && (bXMLTagWrote == TRUE) )
2539 _ftprintf(fLogFile, _T("/>\n")); // terminate the XML node
2541 } // we seem to have a valid PC
2543 // no return address means no deeper stackframe
2544 if ( s.AddrReturn.Offset == 0 )
2546 // avoid misunderstandings in the printf() following the loop
2551 } // for ( frameNum )
2553 if ( (g_CallstackOutputType != ACOutput_XML) && (gle != 0) )
2554 _ftprintf(fLogFile, _T("\n%lu: StackWalk(): GetLastError = %lu\n"), g_dwShowCount, gle );
2557 //if (pSym) free( pSym );
2559 _ftprintf(fLogFile, _T("\n\n"));
2560 if (g_dwShowCount % 1000)
2564 LeaveCriticalSection(&g_csFileOpenClose);
2567 // Critical section end...
2570 #pragma warning(pop)