/*////////////////////////////////////////////////////////////////////////////
* Project:
* Memory_and_Exception_Trace
*
* ///////////////////////////////////////////////////////////////////////////
* File:
* Stackwalker.cpp
*
* Remarks:
* Dumps the stack of an thread if an exepction occurs
* Dumps memory leaks (unreleased allocations)
*
* Note:
* Usage as AllocChecking:
* (only available in Debug-Versions (if _DEBUG is defined)):
* If you change the source, then it can also be used in Release-Version
* if you create also the PDB-File
*
* At the beginning of the programm you have to call
* InitAllocCheck();
* The (optinal) parameters are:
* BOOL bSetUnhandledExeptionFilter
* If set to TRUE (default) it will set an unhandled exception filter
* ULONG ulShowStackAtAlloc: See below.
*
* or the more complicated way:
*
* InitAllocCheckWN(pszLogFileName, ulShowStackAtAlloc)';
* pszLogFileName: Name of the logfile
* ulShowStackAtAlloc: Level of detail at allocation:
* 0 = Do not write any output during
* runtime-alloc-call (default)
* 1 = Write only the alloc action (malloc,
* realloc, free)
* 2 = Write alloc action and callstack only
* for malloc/realloc
* 3 = Write alloc action and callstack for
* all actions
*
* At the end of the programm you have to call 'DeInitAllocCheck()',
* then all Memory leaks (for the C-Runtime heap) will be dumped with
* an stackdump starting with the allocation address.
* This is very helpfull if you make use of STL (which internal alloc memory
* and the buildin leak-detection will only give you the STL-Line/File)
*
* If the LogFileName could not be opened or created, then the info will be
* redirected to 'stdout'
* InitAllocCheck could only be called once!
*
* Warning:
* - Works with VS C++ 5/6/7
* - Tested OS: W-NT4, W-2000, W-XP, should also work with Win9x
* - For W-NT4 you have to install the new version of IMAGEHLP.DLL (5.0.2195.1)
* and DBGHELP.DLL 5.0.2195.1) from W2K (or install NT4 SP6a).
* If you do not install this DLLs you are not able to get the
* callstack, because the old version of imagehlp.dll are not
* able to read the new PDB-Files from MS VC++ 6.0
* If you link with COFF-Symbols (or without using PDB-Files)
* you can at least see the callstack (without line numbers) at
* runtime, but you are not able to see the callstacks at
* the output of the leaks
* - Generates some warnings if you compile with /Wp64 (Detect 64-Bit Portability Issues)
*
* Example:
* InitAllocCheck(); // Init alloc checker
*
* char *pTest1, *pTest2;
* pTest1 = malloc(100); // malloc memory for test
* pTest2 = malloc(200); // malloc memory for test
* free(pTest1); // free only one memory pointer
* DeInitAllocCheck(); // will report the memory leak and
* // close the file
*
* Usage as try/except (C/C++):
* InitAllocCheck has to be called first!
*
* InitAllocCheck(_T("C:\\AllocCheck.log")); // Init alloc checker
* __try
* {
* char *p = NULL;
* *p = 'A'; // BANG!
* }
* __except ( StackwalkFilter( GetExceptionInformation(),
* EXCEPTION_CONTINUE_SEARCH, _T("<LogFileName and Path>") ) )
* {
* printf( "Handled exception.\n" );
* }
*
*
* Example of Output for Memory-Leaks:
*
*
* Known bugs:
* - If the allocation-RequestID wrap, then allocations will get lost...
*
* Author:
* Jochen Kalmbach, Germany
*
*//////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <string>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <crtdbg.h>
#include <tchar.h>
#include "Stackwalker.h"
// If the following is defined, only the used memories are stored in the hash-table.
// If the memory is freed, it will be removed from the hash-table (to reduce memory)
// Consequences: At DeInitAllocHook, only Leaks will be reported
#define HASH_ENTRY_REMOVE_AT_FREE
// 0 = Do not write any output during runtime-alloc-call
// 1 = Write only the alloc action (malloc, realloc, free)
// 2 = Write alloc action and callstack only for malloc/realloc
// 3 = Write alloc action and callstack for all actions
static ULONG g_ulShowStackAtAlloc = 0;
// Size of Hash-Table
#define ALLOC_HASH_ENTRIES 1024
// Size of Callstack-trace in bytes (0x500 => appr. 5-9 functions, depending on parameter count for each function)
#define MAX_ESP_LEN_BUF 0x500
// Normally we can ignore allocations from the Runtime-System
#define IGNORE_CRT_ALLOC
// MaxSize: 128 KByte (only for StackwalkFilter)
#define LOG_FILE_MAX_SIZE 1024*128
#ifdef _IMAGEHLP_
#error "'imagehlp.h' should only included here, not before this point! Otherwise there are some problems!"
#endif
#pragma pack( push, before_imagehlp, 8 )
#include <imagehlp.h>
#pragma pack( pop, before_imagehlp )
#if API_VERSION_NUMBER < 7 // ImageHelp-Version is older.... so define it by mayself
// The following definition is only available with VC++ 6.0 or higher, so include it here
extern "C" {
//
// source file line data structure
//
typedef struct _IMAGEHLP_LINE
{
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE)
DWORD Key; // internal
DWORD LineNumber; // line number in file
PCHAR FileName; // full filename
DWORD Address; // first instruction of line
} IMAGEHLP_LINE, *PIMAGEHLP_LINE;
#define SYMOPT_LOAD_LINES 0x00000010
} // extern "C"
#endif
// Forward definitions of functions:
static void ShowStackRM( HANDLE hThread, CONTEXT& c, FILE *fLogFile, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryFunction, HANDLE hProcess);
static void ShowStack( HANDLE hThread, CONTEXT& c, FILE *fLogFile);
static void AllocHashOut(FILE*);
static ULONG AllocHashOutLeaks(FILE*);
// Globale Vars:
static TCHAR *g_pszAllocLogName = NULL;
static FILE *g_fFile = NULL;
// AllocCheckFileOpen
// Checks if the log-file is already opened
// if not, try to open file (append or create if not exists)
// if open failed, redirect output to stdout
static void AllocCheckFileOpen(void) {
// is the File already open? If not open it...
if (g_fFile == NULL)
if (g_pszAllocLogName != NULL)
g_fFile = _tfopen(g_pszAllocLogName, _T("a"));
if (g_fFile == NULL)
g_fFile = stdout;
}
// Write Date/Time to specified file (will also work after 2038)
static void WriteDateTime(FILE *fFile) {
TCHAR pszTemp[11], pszTemp2[11];
if (fFile != NULL) {
_tstrdate( pszTemp );
_tstrtime( pszTemp2 );
_ftprintf(fFile, _T("%s %s"), pszTemp, pszTemp2 ); // also ok after year 2038 (asctime is NOT ok)
}
} // WriteDateTime
/*******************************************************************************
* Hash-Tabelle
*******************************************************************************/
// Memory for the EIP-Address (is used by the ShowStack-method)
#define MAX_EIP_LEN_BUF 4
#define ALLOC_ENTRY_NOT_FOUND 0xFFFFFFFF
typedef struct AllocHashEntryType {
long lRequestID; // RequestID from CRT (if 0, then this entry is empty)
size_t nDataSize; // Size of the allocated memory
char cRemovedFlag; // 0 => memory was not yet released
struct AllocHashEntryType *Next;
// Callstack for EIP
DWORD dwEIPOffset;
DWORD dwEIPLen;
char pcEIPAddr[MAX_EIP_LEN_BUF];
// Callstack for ESP
DWORD dwESPOffset;
DWORD dwESPLen;
char pcESPAddr[MAX_ESP_LEN_BUF];
} AllocHashEntryType;
static AllocHashEntryType AllocHashTable[ALLOC_HASH_ENTRIES];
static ULONG AllocHashEntries = 0;
static ULONG AllocHashCollisions = 0;
static ULONG AllocHashFreed = 0;
static ULONG AllocHashMaxUsed = 0; // maximal number of concurrent entries
static ULONG AllocHashCurrentCount = 0;
static ULONG AllocHashMaxCollisions = 0;
static ULONG AllocHashCurrentCollisions = 0;
static void AllocHashInit(void) {
memset(AllocHashTable, 0, sizeof(AllocHashTable));
AllocHashEntries = 0;
AllocHashCollisions = 0;
AllocHashFreed = 0;
AllocHashCurrentCount = 0;
AllocHashMaxUsed = 0;
AllocHashMaxCollisions = 0;
AllocHashCurrentCollisions = 0;
return;
} // AllocHashInit
// AllocHashDeinit
// Returns the number of bytes, that are not freed (leaks)
static ULONG AllocHashDeinit(void) {
ULONG ulRet = 0;
AllocCheckFileOpen(); // open global log-file
_ftprintf(g_fFile, _T("\n##### Memory Report ########################################\n"));
WriteDateTime(g_fFile);
_ftprintf(g_fFile, _T("\n"));
#ifndef HASH_ENTRY_REMOVE_AT_FREE
// output the used memory
_ftprintf(g_fFile, _T("##### Memory used: #########################################\n"));
AllocHashOut(g_fFile);
#endif
// output the Memoty leaks
_ftprintf(g_fFile, _T("\n##### Leaks: ###############################################\n"));
ulRet = AllocHashOutLeaks(g_fFile);
// output some statistics from the hash-table
_ftprintf(g_fFile, _T("***** Hash-Table statistics:\n"));
_ftprintf(g_fFile, _T(" Table-Size: %i\n"), ALLOC_HASH_ENTRIES);
_ftprintf(g_fFile, _T(" Inserts: %i\n"), AllocHashEntries);
_ftprintf(g_fFile, _T(" Freed: %i\n"), AllocHashFreed);
_ftprintf(g_fFile, _T(" Sum Collisions: %i\n"), AllocHashCollisions);
_ftprintf(g_fFile, _T("\n"));
_ftprintf(g_fFile, _T(" Max used: %i\n"), AllocHashMaxUsed);
_ftprintf(g_fFile, _T(" Max Collisions: %i\n"), AllocHashMaxCollisions);
// Free Hash-Table
ULONG ulTemp;
AllocHashEntryType *pHashEntry, *pHashEntryOld;
// Now, free my own memory
for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
pHashEntry = &AllocHashTable[ulTemp];
while(pHashEntry != NULL) {
pHashEntryOld = pHashEntry;
pHashEntry = pHashEntry->Next;
if (pHashEntryOld != &AllocHashTable[ulTemp]) {
// now free the dynamically allocated memory
free(pHashEntryOld);
}
} // while
} // for
// empty the hash-table
memset(AllocHashTable, 0, sizeof(AllocHashTable));
return ulRet;
} // AllocHashDeinit
// AllocHashFunction
// The has-function (very simple)
static inline ULONG AllocHashFunction(long lRequestID) {
// I couldn�t find any better and faster
return lRequestID % ALLOC_HASH_ENTRIES;
} // AllocHashFunction
// AllocHashInsert
// lRequestID: Key-Word (RequestID from AllocHook)
// pContext: Context-Record, for retrieving Callstack (EIP and EBP is only needed)
// nDataSize: How many bytes
static void AllocHashInsert(long lRequestID, CONTEXT &Context, size_t nDataSize) {
ULONG HashIdx;
AllocHashEntryType *pHashEntry;
// change statistical data
AllocHashEntries++;
AllocHashCurrentCount++;
if (AllocHashCurrentCount > AllocHashMaxUsed)
AllocHashMaxUsed = AllocHashCurrentCount;
// generate hash-value
HashIdx = AllocHashFunction(lRequestID);
pHashEntry = &AllocHashTable[HashIdx];
if (pHashEntry->lRequestID == 0) {
// Entry is empty...
}
else {
// Entry is not empy! make a list of entries for this hash value...
// change statistical data
// if this happens often, you should increase the hah size or change the heash-function;
// to fasten the allocation time
AllocHashCollisions++;
AllocHashCurrentCollisions++;
if (AllocHashCurrentCollisions > AllocHashMaxCollisions)
AllocHashMaxCollisions = AllocHashCurrentCollisions;
while(pHashEntry->Next != NULL) {
pHashEntry = pHashEntry->Next;
}
pHashEntry->Next = (AllocHashEntryType*) calloc(sizeof(AllocHashEntryType), 1);
pHashEntry = pHashEntry->Next;
}
pHashEntry->lRequestID = lRequestID; // Key-Word
pHashEntry->nDataSize = nDataSize;
pHashEntry->Next = NULL;
// Get EIP and save it in the record
pHashEntry->dwEIPOffset = Context.Eip;
if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Eip, &(pHashEntry->pcEIPAddr), MAX_EIP_LEN_BUF, &(pHashEntry->dwEIPLen)) == 0) {
// Could not read memory... remove everything...
memset(pHashEntry->pcEIPAddr, 0, MAX_EIP_LEN_BUF);
pHashEntry->dwEIPLen = 0;
pHashEntry->dwEIPOffset = 0;
}
// Get ESP and save it in the record
pHashEntry->dwESPOffset = Context.Ebp;
if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Ebp, &(pHashEntry->pcESPAddr), MAX_ESP_LEN_BUF, &(pHashEntry->dwESPLen)) == 0) {
// Could not read memory... remove everything...
memset(pHashEntry->pcESPAddr, 0, MAX_ESP_LEN_BUF);
pHashEntry->dwESPLen = 0;
pHashEntry->dwESPOffset = 0;
// Check if I tried to read too much...
if (GetLastError() == ERROR_PARTIAL_COPY)
{
// ask how many I can read:
MEMORY_BASIC_INFORMATION MemBuffer;
DWORD dwRet = VirtualQuery((LPCVOID) Context.Ebp, &MemBuffer, sizeof(MemBuffer));
if (dwRet > 0)
{
// calculate the length
SSIZE_T len = ((DWORD) MemBuffer.BaseAddress + MemBuffer.RegionSize) - Context.Ebp;
if ( (len > 0) && (len < MAX_ESP_LEN_BUF) )
{
// try to read it again (with the shorter length)
if (ReadProcessMemory(GetCurrentProcess(), (LPCVOID) Context.Ebp, &(pHashEntry->pcESPAddr), len, &(pHashEntry->dwESPLen)) == 0)
{
// ok, now everything goes wrong... remove it...
memset(pHashEntry->pcESPAddr, 0, MAX_ESP_LEN_BUF);
pHashEntry->dwESPLen = 0;
pHashEntry->dwESPOffset = 0;
}
else
{
pHashEntry->dwESPOffset = Context.Ebp;
}
}
} // VirtualQuery was successfully
} // ERROR_PARTIAL_COPY
}
}
// AllocHashFind
// If ALLOC_ENTRY_NOT_FOUND is returned, the Key was not found!
// If the Key was found, a pointer to the entry is returned
static AllocHashEntryType *AllocHashFind(long lRequestID) {
ULONG HashIdx;
AllocHashEntryType *pHashEntry;
// get the Hash-Value
HashIdx = AllocHashFunction(lRequestID);
// Just do some simple checks:
_ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
pHashEntry = &AllocHashTable[HashIdx];
while(pHashEntry != NULL) {
if (pHashEntry->lRequestID == lRequestID) {
return pHashEntry;
}
pHashEntry = pHashEntry->Next;
}
// entry was not found!
return (AllocHashEntryType*) ALLOC_ENTRY_NOT_FOUND;
} // AllocHashFind
// AllocHashRemove
// Return: FALSE (0) : Key was found and removed/marked
// TRUE (!=0): Key was not found
static BOOL AllocHashRemove(long lRequestID) {
ULONG HashIdx;
AllocHashEntryType *pHashEntry, *pHashEntryLast;
// get the Hash-Value
HashIdx = AllocHashFunction(lRequestID);
// Just do some simple checks:
_ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
pHashEntryLast = NULL;
pHashEntry = &AllocHashTable[HashIdx];
while(pHashEntry != NULL) {
if (pHashEntry->lRequestID == lRequestID) {
#ifdef HASH_ENTRY_REMOVE_AT_FREE
AllocHashFreed++;
AllocHashCurrentCount--;
// release my memory
if (pHashEntryLast == NULL) {
// It is an entry in the table, so do not release this memory
if (pHashEntry->Next == NULL) {
// It was the last entry, so empty the table entry
memset(&AllocHashTable[HashIdx], 0, sizeof(AllocHashTable[HashIdx]));
}
else {
// There are some more entries, so shorten the list
*pHashEntry = *(pHashEntry->Next);
// TODO: Do I need to free the memory here !? I think I should do this...
}
return TRUE;
}
else {
// now, I am in an dynamic allocated entry
// it was a collision, so decrease the current collision count
AllocHashCurrentCollisions--;
pHashEntryLast->Next = pHashEntry->Next;
free(pHashEntry);
return TRUE;
}
#else
// increase the Remove-Count and let the objet stay in memory
pHashEntry->cRemovedFlag++;
return TRUE;
#endif
}
pHashEntryLast = pHashEntry;
pHashEntry = pHashEntry->Next;
}
// if we are here, we could not find the RequestID
return FALSE;
}
// ReadProcMemoryFromHash
// Callback-Funtion for StackWalk for my own CallStack from the Hash-Table-Entries
#if API_VERSION_NUMBER >= 9
static BOOL __stdcall ReadProcMemoryFromHash(HANDLE hRequestID, DWORD lpBaseAddress, PVOID lpBuffer, DWORD nSize, PDWORD lpNumberOfBytesRead) {
#else
static BOOL __stdcall ReadProcMemoryFromHash(HANDLE hRequestID, LPCVOID lpBaseAddress, PVOID lpBuffer, DWORD nSize, PDWORD lpNumberOfBytesRead) {
#endif
// Try to find the RequestID
AllocHashEntryType *pHashEntry;
*lpNumberOfBytesRead = 0;
pHashEntry = AllocHashFind((LONG) hRequestID);
if (pHashEntry == (AllocHashEntryType*) ALLOC_ENTRY_NOT_FOUND) {
// Not found, so I cannot return any memory
*lpNumberOfBytesRead = 0;
return FALSE;
}
if ( ((DWORD) lpBaseAddress >= pHashEntry->dwESPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwESPOffset+pHashEntry->dwESPLen)) ) {
// Memory is located in ESP:
// Calculate the offset
DWORD dwOffset = (DWORD) lpBaseAddress - pHashEntry->dwESPOffset;
DWORD dwSize = __min(nSize, MAX_ESP_LEN_BUF-dwOffset);
memcpy(lpBuffer, &(pHashEntry->pcESPAddr[dwOffset]), dwSize);
*lpNumberOfBytesRead = dwSize;
if (dwSize != nSize)
return FALSE;
}
if ( ((DWORD) lpBaseAddress >= pHashEntry->dwEIPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwEIPOffset+pHashEntry->dwEIPLen)) ) {
// Memory is located in EIP:
// Calculate the offset
DWORD dwOffset = (DWORD) lpBaseAddress - pHashEntry->dwEIPOffset;
DWORD dwSize = __min(nSize, MAX_ESP_LEN_BUF-dwOffset);
memcpy(lpBuffer, &(pHashEntry->pcEIPAddr[dwOffset]), dwSize);
*lpNumberOfBytesRead = dwSize;
if (dwSize != nSize)
return FALSE;
}
if (*lpNumberOfBytesRead == 0) // Memory could not be found
return FALSE;
return TRUE;
}
// AllocHashOutLeaks
// Write all Memory (with callstack) which was not freed yet
// Returns the number of bytes, that are not freed (leaks)
ULONG AllocHashOutLeaks(FILE *fFile) {
ULONG ulTemp;
AllocHashEntryType *pHashEntry;
ULONG ulCount = 0;
ULONG ulLeaksByte = 0;
// Move throu every entry
for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
pHashEntry = &AllocHashTable[ulTemp];
if (pHashEntry->lRequestID != 0) {
while(pHashEntry != NULL) {
if ( (pHashEntry->cRemovedFlag <= 0) || (pHashEntry->cRemovedFlag > 1) ) {
ulCount++;
_ftprintf(fFile, _T("RequestID: %12i, Removed: %i, Size: %12i\n"), pHashEntry->lRequestID, pHashEntry->cRemovedFlag, pHashEntry->nDataSize);
CONTEXT c;
memset( &c, '\0', sizeof c );
c.Eip = pHashEntry->dwEIPOffset;
c.Ebp = pHashEntry->dwESPOffset;
ShowStackRM( NULL, c, fFile, &ReadProcMemoryFromHash, (HANDLE) pHashEntry->lRequestID);
// Count the number of leaky bytes
if (pHashEntry->nDataSize > 0)
ulLeaksByte += pHashEntry->nDataSize;
else
ulLeaksByte++; // If memory was allocated with zero bytes, then just increase the counter 1
}
pHashEntry = pHashEntry->Next;
}
}
}
_ftprintf(fFile, _T("\n**** Number of leaks: %i\n"), ulCount);
return ulLeaksByte;
} // AllocHashOutLeaks
// Write all used memory to a file
void AllocHashOut(FILE *fFile) {
ULONG ulTemp;
AllocHashEntryType *pHashEntry;
for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
pHashEntry = &AllocHashTable[ulTemp];
if (pHashEntry->lRequestID != 0) {
while(pHashEntry != NULL) {
_ftprintf(fFile, _T("RequestID: %12i, Removed: %i, Size: %12i\n"), pHashEntry->lRequestID, pHashEntry->cRemovedFlag, pHashEntry->nDataSize);
pHashEntry = pHashEntry->Next;
}
}
}
} // AllocHashOut
/*******************************************************************************
* Ende der Hash-Tabelle
*******************************************************************************/
// The follwoing is copied from dbgint.h:
// <CRT_INTERNALS>
/*
* For diagnostic purpose, blocks are allocated with extra information and
* stored in a doubly-linked list. This makes all blocks registered with
* how big they are, when they were allocated, and what they are used for.
*/
#define nNoMansLandSize 4
typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
#ifdef _WIN64
/* These items are reversed on Win64 to eliminate gaps in the struct
* and ensure that sizeof(struct)%16 == 0, so 16-byte alignment is
* maintained in the debug heap.
*/
int nBlockUse;
size_t nDataSize;
#else /* _WIN64 */
size_t nDataSize;
int nBlockUse;
#endif /* _WIN64 */
long lRequest;
unsigned char gap[nNoMansLandSize];
/* followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
} _CrtMemBlockHeader;
#define pbData(pblock) ((unsigned char *)((_CrtMemBlockHeader *)pblock + 1))
#define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1)
// </CRT_INTERNALS>
// Global data:
static BOOL g_bInitialized = FALSE;
static HINSTANCE g_hImagehlpDll = NULL;
static DWORD g_dwShowCount = 0; // increase at every ShowStack-Call
static CRITICAL_SECTION g_csFileOpenClose = {0};
// Is used for syncronising call to MyAllocHook (to prevent reentrant calls)
static LONG g_lMallocCalled = 0;
// Deaktivate AllocHook, by increasing the Syncronisation-Counter
static void DeactivateMallocStackwalker(void) {
InterlockedIncrement(&g_lMallocCalled);
}
// MyAllocHook is Single-Threaded, that means the the calls are serialized in the calling function!
static int MyAllocHook(int nAllocType, void *pvData,
size_t nSize, int nBlockUse, long lRequest,
const unsigned char * szFileName, int nLine ) {
static TCHAR *operation[] = { _T(""), _T("ALLOCATIONG"), _T("RE-ALLOCATING"), _T("FREEING") };
static TCHAR *blockType[] = { _T("Free"), _T("Normal"), _T("CRT"), _T("Ignore"), _T("Client") };
#ifdef IGNORE_CRT_ALLOC
if (nBlockUse == _CRT_BLOCK) // Ignore internal C runtime library allocations
return TRUE;
#endif
// Prevent from reentrat calls
if (InterlockedIncrement(&g_lMallocCalled) > 1) { // I was already called
InterlockedDecrement(&g_lMallocCalled);
return TRUE;
}
if (g_ulShowStackAtAlloc > 0) {
AllocCheckFileOpen(); // Open logfile
}
_ASSERT( ( nAllocType > 0 ) && ( nAllocType < 4 ) );
_ASSERT( ( nBlockUse >= 0 ) && ( nBlockUse < 5 ) );
if (nAllocType == 3) { // freeing
// Try to get the header information
if (_CrtIsValidHeapPointer(pvData)) { // it is a valid Heap-Pointer
// get the ID
_CrtMemBlockHeader *pHead;
// get a pointer to memory block header
pHead = pHdr(pvData);
nSize = pHead->nDataSize;
lRequest = pHead->lRequest; // This is the ID!
}
}
if (g_ulShowStackAtAlloc > 0) {
_ftprintf( g_fFile, _T("##### Memory operation: %s a %d-byte '%s' block (# %ld)"),
operation[nAllocType], nSize, blockType[nBlockUse], lRequest );
if ( pvData != NULL )
_ftprintf( g_fFile, _T(" at 0x%X"), pvData );
_ftprintf(g_fFile, _T("\n"));
}
if (nAllocType == 3) { // freeing:
if (lRequest != 0) { // RequestID was found
BOOL bRet;
// Try to find the RequestID in the Hash-Table, mark it that it was freed
bRet = AllocHashRemove(lRequest);
if(g_ulShowStackAtAlloc > 0) {
if (bRet == FALSE) {
// RequestID not found!
_ftprintf(g_fFile, _T("###### RequestID not found in hash table for FREEING (%i)!\n"), lRequest);
}
} // g_ulShowStackAtAlloc > 0
}
else {
if(g_ulShowStackAtAlloc > 0) {
// No valid RequestID found, display error
_ftprintf(g_fFile, _T("###### No valid RequestID for FREEING! (0x%X)\n"), pvData);
}
}
} // freeing
if (nAllocType == 2) { // re-allocating
// Try to get the header information
if (_CrtIsValidHeapPointer(pvData)) { // it is a valid Heap-Pointer
BOOL bRet;
LONG lReallocRequest;
// get the ID
_CrtMemBlockHeader *pHead;
// get a pointer to memory block header
pHead = pHdr(pvData);
// Try to find the RequestID in the Hash-Table, mark it that it was freed
lReallocRequest = pHead->lRequest;
bRet = AllocHashRemove(lReallocRequest);
if (g_ulShowStackAtAlloc > 0) {
if (bRet == FALSE) {
// RequestID not found!
_ftprintf(g_fFile, _T("###### RequestID not found in hash table for RE-ALLOCATING (%i)!\n"), lReallocRequest);
}
else {
_ftprintf(g_fFile, _T("##### Implicit freeing because of re-allocation (# old: %ld, new: %ld)\n"), lReallocRequest, lRequest);
}
} // g_ulShowStackAtAlloc > 0
} // ValidHeapPointer
} // re-allocating
if ( (g_ulShowStackAtAlloc < 3) && (nAllocType == 3) ) {
InterlockedDecrement(&g_lMallocCalled);
return TRUE;
}
// Get the context DIESES of this Thread
HANDLE hThread;
if (DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS ) == 0) {
// Something was wrong...
_ftprintf(g_fFile, _T("###### Could not call 'DuplicateHandle' successfully\n"));
InterlockedDecrement(&g_lMallocCalled);
return TRUE;
}
CONTEXT c;
memset( &c, '\0', sizeof c );
c.ContextFlags = CONTEXT_FULL;
// init CONTEXT record so we know where to start the stackwalk
if ( GetThreadContext( hThread, &c ) == 0) {
if(g_ulShowStackAtAlloc > 1) {
_ftprintf(g_fFile, _T("###### Could not call 'GetThreadContext' successfully\n"));
}
InterlockedDecrement(&g_lMallocCalled);
return TRUE; // could not get context
}
if(g_ulShowStackAtAlloc > 1) {
if(g_ulShowStackAtAlloc > 2) {
// output the callstack
ShowStack( hThread, c, g_fFile);
}
else {
// Output only (re)allocs
if (nAllocType != 3) {
ShowStack( hThread, c, g_fFile);
}
}
} // g_ulShowStackAtAlloc > 1
CloseHandle( hThread );
// Only isert in the Hash-Table if it is not a "freeing"
if (nAllocType != 3) {
if(lRequest != 0) // Always a valid RequestID should be provided (see comments in the header)
AllocHashInsert(lRequest, c, nSize);
}
InterlockedDecrement(&g_lMallocCalled);
return TRUE; // allow the memory operation to proceed
}
#define gle (GetLastError())
#define lenof(a) (sizeof(a) / sizeof((a)[0]))
#define MAXNAMELEN 1024 // max name length for found symbols
#define IMGSYMLEN ( sizeof IMAGEHLP_SYMBOL )
#define TTBUFLEN 8096 // for a temp buffer (2^13)
// SymCleanup()
typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess );
tSC pSC = NULL;
// SymFunctionTableAccess()
typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD AddrBase );
tSFTA pSFTA = NULL;
// SymGetLineFromAddr()
typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD dwAddr,
OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE Line );
tSGLFA pSGLFA = NULL;
// SymGetModuleBase()
typedef DWORD (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD dwAddr );
tSGMB pSGMB = NULL;
// SymGetModuleInfo()
typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD dwAddr, OUT PIMAGEHLP_MODULE ModuleInfo );
tSGMI pSGMI = NULL;
// SymGetOptions()
typedef DWORD (__stdcall *tSGO)( VOID );
tSGO pSGO = NULL;
// SymGetSymFromAddr()
typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD dwAddr,
OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_SYMBOL Symbol );
tSGSFA pSGSFA = NULL;
// SymInitialize()
typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess );
tSI pSI = NULL;
// SymLoadModule()
typedef DWORD (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile,
IN PSTR ImageName, IN PSTR ModuleName, IN DWORD BaseOfDll, IN DWORD SizeOfDll );
tSLM pSLM = NULL;
// SymSetOptions()
typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions );
tSSO pSSO = NULL;
// StackWalk()
typedef BOOL (__stdcall *tSW)( DWORD MachineType, HANDLE hProcess,
HANDLE hThread, LPSTACKFRAME StackFrame, PVOID ContextRecord,
PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine,
PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine,
PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine,
PTRANSLATE_ADDRESS_ROUTINE TranslateAddress );
tSW pSW = NULL;
// UnDecorateSymbolName()
typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName,
DWORD UndecoratedLength, DWORD Flags );
tUDSN pUDSN = NULL;
static void enumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid, FILE *fLogFile );
static int InitStackWalk(void)
{
if (g_bInitialized != FALSE)
return 0; // already initialized
// we load imagehlp.dll dynamically because the NT4-version does not
// offer all the functions that are in the NT5 lib
g_hImagehlpDll = LoadLibrary( _T("imagehlp.dll") );
if ( g_hImagehlpDll == NULL )
{
printf( "LoadLibrary( \"imagehlp.dll\" ): GetLastError = %lu\n", gle );
g_bInitialized = FALSE;
return 1;
}
pSC = (tSC) GetProcAddress( g_hImagehlpDll, "SymCleanup" );
pSFTA = (tSFTA) GetProcAddress( g_hImagehlpDll, "SymFunctionTableAccess" );
pSGLFA = (tSGLFA) GetProcAddress( g_hImagehlpDll, "SymGetLineFromAddr" );
pSGMB = (tSGMB) GetProcAddress( g_hImagehlpDll, "SymGetModuleBase" );
pSGMI = (tSGMI) GetProcAddress( g_hImagehlpDll, "SymGetModuleInfo" );
pSGO = (tSGO) GetProcAddress( g_hImagehlpDll, "SymGetOptions" );
pSGSFA = (tSGSFA) GetProcAddress( g_hImagehlpDll, "SymGetSymFromAddr" );
pSI = (tSI) GetProcAddress( g_hImagehlpDll, "SymInitialize" );
pSSO = (tSSO) GetProcAddress( g_hImagehlpDll, "SymSetOptions" );
pSW = (tSW) GetProcAddress( g_hImagehlpDll, "StackWalk" );
pUDSN = (tUDSN) GetProcAddress( g_hImagehlpDll, "UnDecorateSymbolName" );
pSLM = (tSLM) GetProcAddress( g_hImagehlpDll, "SymLoadModule" );
if ( pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL ||
pSGO == NULL || pSGSFA == NULL || pSI == NULL || pSSO == NULL ||
pSW == NULL || pUDSN == NULL || pSLM == NULL )
{
printf( "GetProcAddress(): some required function not found.\n" );
FreeLibrary( g_hImagehlpDll );
g_bInitialized = FALSE;
return 1;
}
g_bInitialized = TRUE;
InitializeCriticalSection(&g_csFileOpenClose);
return 0;
}
// This function if NOT multi-threading capable
// It should only be called from the main-Function!
int InitAllocCheckWN(PCTSTR pszFileName, ULONG ulShowStackAtAlloc) {
if (g_bInitialized) {
return 2; // already initialized!
}
if (ulShowStackAtAlloc <= 3)
g_ulShowStackAtAlloc = ulShowStackAtAlloc;
else
g_ulShowStackAtAlloc = 3;
if (pszFileName != NULL)
g_pszAllocLogName = _tcsdup(pszFileName);
else
g_pszAllocLogName = NULL;
#ifdef _DEBUG
AllocHashInit();
_CrtSetAllocHook(MyAllocHook);
#endif
return InitStackWalk();
} // InitAllocCheckWN
static TCHAR s_szExceptionLogFileName[_MAX_PATH] = _T("\\exceptions.log"); // default
static BOOL s_bUnhandledExeptionFilterSet = FALSE;
static LONG __stdcall CrashHandlerExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
{
LONG lRet;
lRet = StackwalkFilter(pExPtrs, /*EXCEPTION_CONTINUE_SEARCH*/EXCEPTION_EXECUTE_HANDLER, s_szExceptionLogFileName);
TCHAR lString[500];
_stprintf(lString,
_T("*** Unhandled Exception!\n")
_T(" ExpCode: 0x%8.8X\n")
_T(" ExpFlags: %d\n")
_T(" ExpAddress: 0x%8.8X\n")
_T(" Please report!"),
pExPtrs->ExceptionRecord->ExceptionCode,
pExPtrs->ExceptionRecord->ExceptionFlags,
pExPtrs->ExceptionRecord->ExceptionAddress);
FatalAppExit(-1,lString);
return lRet;
}
int InitAllocCheck(BOOL bSetUnhandledExeptionFilter, ULONG ulShowStackAtAlloc) // will create the filename by itself
{
TCHAR szModName[_MAX_PATH];
if (GetModuleFileName(NULL, szModName, sizeof(szModName)/sizeof(TCHAR)) != 0)
{
strcpy(s_szExceptionLogFileName, szModName);
strcat(s_szExceptionLogFileName, _T(".exp.log"));
strcat(szModName, _T(".mem.log"));
}
else
{
strcpy(szModName, _T("\\mem-leaks.log")); // default
}
if (bSetUnhandledExeptionFilter != FALSE)
{
// set global exception handler (for handling all unhandled exceptions)
SetUnhandledExceptionFilter(CrashHandlerExceptionFilter);
s_bUnhandledExeptionFilterSet = TRUE;
}
return InitAllocCheckWN(szModName, 0);
}
// This function if NOT multi-threading capable
// It should only be called from the main-Function!
// Returns the number of bytes that are not freed (leaks)
ULONG DeInitAllocCheck(void) {
ULONG ulRet = 0;
if (g_bInitialized) {
#ifdef _DEBUG
DeactivateMallocStackwalker(); // No deactivate MyAllocHook, because StackWalker will allocate some memory)
ulRet = AllocHashDeinit(); // output the not freed memory
#endif
EnterCriticalSection(&g_csFileOpenClose); // wait until a running stack dump was created
g_bInitialized = FALSE;
// de-init symbol handler etc. (SymCleanup())
if (pSC != NULL)
pSC( GetCurrentProcess() );
FreeLibrary( g_hImagehlpDll );
LeaveCriticalSection(&g_csFileOpenClose);
if (g_pszAllocLogName != NULL) {
free(g_pszAllocLogName);
g_pszAllocLogName = NULL;
}
if (g_fFile != NULL) {
fclose(g_fFile);
g_fFile = NULL;
}
}
if (s_bUnhandledExeptionFilterSet != TRUE)
{
SetUnhandledExceptionFilter(NULL);
s_bUnhandledExeptionFilterSet = FALSE;
}
return ulRet;
} // DeInitAllocCheck
static TCHAR *GetExpectionCodeText(DWORD dwExceptionCode) {
switch(dwExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION: return _T("ACCESS VIOLATION");
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return _T("ARRAY BOUNDS EXCEEDED");
case EXCEPTION_BREAKPOINT: return _T("BREAKPOINT");
case EXCEPTION_DATATYPE_MISALIGNMENT: return _T("DATATYPE MISALIGNMENT");
case EXCEPTION_FLT_DENORMAL_OPERAND: return _T("FLT DENORMAL OPERAND");
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return _T("FLT DIVIDE BY ZERO");
case EXCEPTION_FLT_INEXACT_RESULT: return _T("FLT INEXACT RESULT");
case EXCEPTION_FLT_INVALID_OPERATION: return _T("FLT INVALID OPERATION");
case EXCEPTION_FLT_OVERFLOW: return _T("FLT OVERFLOW");
case EXCEPTION_FLT_STACK_CHECK: return _T("FLT STACK CHECK");
case EXCEPTION_FLT_UNDERFLOW: return _T("FLT UNDERFLOW");
case EXCEPTION_ILLEGAL_INSTRUCTION: return _T("ILLEGAL INSTRUCTION");
case EXCEPTION_IN_PAGE_ERROR: return _T("IN PAGE ERROR");
case EXCEPTION_INT_DIVIDE_BY_ZERO: return _T("INT DIVIDE BY ZERO");
case EXCEPTION_INT_OVERFLOW: return _T("INT OVERFLOW");
case EXCEPTION_INVALID_DISPOSITION: return _T("INVALID DISPOSITION");
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return _T("NONCONTINUABLE EXCEPTION");
case EXCEPTION_PRIV_INSTRUCTION: return _T("PRIV INSTRUCTION");
case EXCEPTION_SINGLE_STEP: return _T("SINGLE STEP");
case EXCEPTION_STACK_OVERFLOW: return _T("STACK OVERFLOW");
case DBG_CONTROL_C : return _T("DBG CONTROL C ");
default:
return _T("<unkown exception>");
}
} // GetExpectionCodeText
// Function is not multi-threading safe, because of static char!
static TCHAR *GetAdditionalExpectionCodeText(PEXCEPTION_RECORD pExceptionRecord) {
static TCHAR szTemp[100];
switch(pExceptionRecord->ExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION:
if (pExceptionRecord->NumberParameters == 2) {
switch(pExceptionRecord->ExceptionInformation[0]) {
case 0: // read attempt
_stprintf(szTemp, _T(" read attempt to address 0x%8.8X "), pExceptionRecord->ExceptionInformation[1]);
return szTemp;
case 1: // write attempt
_stprintf(szTemp, _T(" write attempt to address 0x%8.8X "), pExceptionRecord->ExceptionInformation[1]);
return szTemp;
default:
return _T("");
}
} // if (pExceptionRecord->NumberParameters == 2)
return _T("");
default:
return _T("");
} // switch(pExceptionRecord->ExceptionCode)
} // GetAdditionalExpectionCodeText
// if you use C++ exception handling: install a translator function
// with set_se_translator(). In the context of that function (but *not*
// afterwards), you can either do your stack dump, or save the CONTEXT
// record as a local copy. Note that you must do the stack sump at the
// earliest opportunity, to avoid the interesting stackframes being gone
// by the time you do the dump.
// status:
// - EXCEPTION_CONTINUE_SEARCH: exception wird weitergereicht
// - EXCEPTION_CONTINUE_EXECUTION:
// - EXCEPTION_EXECUTE_HANDLER:
DWORD StackwalkFilter( EXCEPTION_POINTERS *ep, DWORD status, PCTSTR pszLogFile)
{
HANDLE hThread;
FILE *fFile = stdout; // default to stdout
if (pszLogFile != NULL) { // a filename is provided
// Open the logfile
fFile = _tfopen(pszLogFile, _T("a"));
if (fFile != NULL) { // Is the file too big?
long size;
fseek(fFile, 0, SEEK_END);
size = ftell(fFile); // Get the size of the file
if (size >= LOG_FILE_MAX_SIZE) {
TCHAR *pszTemp = (TCHAR*) malloc(MAX_PATH);
// It is too big...
fclose(fFile);
_tcscpy(pszTemp, pszLogFile);
_tcscat(pszTemp, _T(".old"));
_tremove(pszTemp); // Remove an old file, if exists
_trename(pszLogFile, pszTemp); // rename the actual file
fFile = _tfopen(pszLogFile, _T("w")); // create a new file
free(pszTemp);
}
}
} // if (pszLogFile != NULL)
if (fFile == NULL) {
fFile = stdout;
}
// Write infos about the exception
_ftprintf(fFile, _T("######## EXCEPTION: 0x%8.8X at address: 0x%8.8X"),
ep->ExceptionRecord->ExceptionCode,
ep->ExceptionRecord->ExceptionAddress);
_ftprintf(fFile, _T(": %s %s\n"), GetExpectionCodeText(ep->ExceptionRecord->ExceptionCode),
GetAdditionalExpectionCodeText(ep->ExceptionRecord));
DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS );
ShowStack( hThread, *(ep->ContextRecord), fFile);
CloseHandle( hThread );
fclose(fFile);
return status;
} // StackwalkFilter
void ShowStack( HANDLE hThread, CONTEXT& c, PCTSTR pszLogFile)
{
FILE *fFile = stdout; // default to stdout
if (pszLogFile != NULL) { // a filename is available
// Open the logfile
fFile = _tfopen(pszLogFile, _T("a"));
if (fFile != NULL) { // Is the file too big?
long size;
fseek(fFile, 0, SEEK_END);
size = ftell(fFile); // Get the size of the file
if (size >= LOG_FILE_MAX_SIZE) {
TCHAR *pszTemp = (TCHAR*) malloc(MAX_PATH);
// It is too big...
fclose(fFile);
_tcscpy(pszTemp, pszLogFile);
_tcscat(pszTemp, _T(".old"));
_tremove(pszTemp); // Remove an old file, if exists
_trename(pszLogFile, pszTemp); // rename the actual file
fFile = _tfopen(pszLogFile, _T("w")); // open new file
free(pszTemp);
}
}
} // if (pszLogFile != NULL)
if (fFile == NULL) {
fFile = stdout;
}
ShowStack( hThread, c, fFile);
fclose(fFile);
}
static void ShowStack( HANDLE hThread, CONTEXT& c, FILE *fLogFile) {
ShowStackRM(hThread, c, fLogFile, NULL, GetCurrentProcess());
}
static void ShowStackRM( HANDLE hThread, CONTEXT& c, FILE *fLogFile, PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryFunction, HANDLE hSWProcess) {
// normally, call ImageNtHeader() and use machine info from PE header
DWORD imageType = IMAGE_FILE_MACHINE_I386;
HANDLE hProcess = GetCurrentProcess(); // hProcess normally comes from outside
int frameNum; // counts walked frames
DWORD offsetFromSymbol; // tells us how far from the symbol we were
DWORD symOptions; // symbol handler settings
static IMAGEHLP_SYMBOL *pSym = NULL;
char undName[MAXNAMELEN]; // undecorated name
char undFullName[MAXNAMELEN]; // undecorated name with all shenanigans
IMAGEHLP_MODULE Module;
IMAGEHLP_LINE Line;
std::string symSearchPath;
static bFirstTime = TRUE;
// If no logfile is present, outpur to "stdout"
if (fLogFile == NULL) {
fLogFile = stdout;
}
STACKFRAME s; // in/out stackframe
memset( &s, '\0', sizeof s );
if ( (g_bInitialized == FALSE) && (bFirstTime == TRUE) ) {
InitStackWalk();
}
if (g_bInitialized == FALSE)
{
// Could not init!!!!
bFirstTime = FALSE;
_ftprintf(fLogFile, _T("%lu: Stackwalker not initialized (or was not able to initialize)!\n"), g_dwShowCount);
return;
}
// Critical section begin...
EnterCriticalSection(&g_csFileOpenClose);
InterlockedIncrement((long*) &g_dwShowCount); // erh�he counter
// NOTE: normally, the exe directory and the current directory should be taken
// from the target process. The current dir would be gotten through injection
// of a remote thread; the exe fir through either ToolHelp32 or PSAPI.
if (pSym == NULL) {
pSym = (IMAGEHLP_SYMBOL *) malloc( IMGSYMLEN + MAXNAMELEN );
if (!pSym) goto cleanup; // not enough memory...
}
_ftprintf(fLogFile, _T("%lu: "), g_dwShowCount);
WriteDateTime(fLogFile);
_ftprintf(fLogFile, _T("\n"));
if (bFirstTime) {
CHAR *tt, *p;
tt = (CHAR*) malloc(sizeof(CHAR) * TTBUFLEN); // Get the temporary buffer
if (!tt) goto cleanup; // not enough memory...
// build symbol search path from:
symSearchPath = "";
// current directory
if ( GetCurrentDirectoryA( TTBUFLEN, tt ) )
symSearchPath += tt + std::string( ";" );
// dir with executable
if ( GetModuleFileNameA( 0, tt, TTBUFLEN ) )
{
for ( p = tt + strlen( tt ) - 1; p >= tt; -- p )
{
// locate the rightmost path separator
if ( *p == '\\' || *p == '/' || *p == ':' )
break;
}
// if we found one, p is pointing at it; if not, tt only contains
// an exe name (no path), and p points before its first byte
if ( p != tt ) // path sep found?
{
if ( *p == ':' ) // we leave colons in place
++ p;
*p = '\0'; // eliminate the exe name and last path sep
symSearchPath += tt + std::string( ";" );
}
}
// environment variable _NT_SYMBOL_PATH
if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", tt, TTBUFLEN ) )
symSearchPath += tt + std::string( ";" );
// environment variable _NT_ALTERNATE_SYMBOL_PATH
if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", tt, TTBUFLEN ) )
symSearchPath += tt + std::string( ";" );
// environment variable SYSTEMROOT
if ( GetEnvironmentVariableA( "SYSTEMROOT", tt, TTBUFLEN ) )
symSearchPath += tt + std::string( ";" );
if ( symSearchPath.size() > 0 ) // if we added anything, we have a trailing semicolon
symSearchPath = symSearchPath.substr( 0, symSearchPath.size() - 1 );
// why oh why does SymInitialize() want a writeable string?
strncpy( tt, symSearchPath.c_str(), TTBUFLEN );
tt[TTBUFLEN - 1] = '\0'; // if strncpy() overruns, it doesn't add the null terminator
// init symbol handler stuff (SymInitialize())
if ( ! pSI( hProcess, tt, false ) )
{
_ftprintf(fLogFile, _T("%lu: SymInitialize(): GetLastError = %lu\n"), g_dwShowCount, gle );
if (tt) free( tt );
goto cleanup;
}
// SymGetOptions()
symOptions = pSGO();
symOptions |= SYMOPT_LOAD_LINES;
symOptions &= ~SYMOPT_UNDNAME;
symOptions &= ~SYMOPT_DEFERRED_LOADS;
pSSO( symOptions ); // SymSetOptions()
// Enumerate modules and tell imagehlp.dll about them.
// On NT, this is not necessary, but it won't hurt.
enumAndLoadModuleSymbols( hProcess, GetCurrentProcessId(), fLogFile );
if (tt)
free( tt );
} // bFirstTime = TRUE
bFirstTime = FALSE;
// init STACKFRAME for first call
// Notes: AddrModeFlat is just an assumption. I hate VDM debugging.
// Notes: will have to be #ifdef-ed for Alphas; MIPSes are dead anyway,
// and good riddance.
s.AddrPC.Offset = c.Eip;
s.AddrPC.Mode = AddrModeFlat;
s.AddrFrame.Offset = c.Ebp;
s.AddrFrame.Mode = AddrModeFlat;
memset( pSym, '\0', IMGSYMLEN + MAXNAMELEN );
pSym->SizeOfStruct = IMGSYMLEN;
pSym->MaxNameLength = MAXNAMELEN;
memset( &Line, '\0', sizeof Line );
Line.SizeOfStruct = sizeof Line;
memset( &Module, '\0', sizeof Module );
Module.SizeOfStruct = sizeof Module;
offsetFromSymbol = 0;
for ( frameNum = 0; ; ++ frameNum )
{
// get next stack frame (StackWalk(), SymFunctionTableAccess(), SymGetModuleBase())
// if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
// assume that either you are done, or that the stack is so hosed that the next
// deeper frame could not be found.
// CONTEXT need not to be suplied if imageTyp is IMAGE_FILE_MACHINE_I386!
if ( ! pSW( imageType, hSWProcess, hThread, &s, NULL, ReadMemoryFunction, pSFTA, pSGMB, NULL ) )
break;
// display its contents
/*_ftprintf(fLogFile, _T("\n%lu: %3d %c%c %08lx %08lx %08lx %08lx "), g_dwShowCount,
frameNum, s.Far? 'F': '.', s.Virtual? 'V': '.',
s.AddrPC.Offset, s.AddrReturn.Offset,
s.AddrFrame.Offset, s.AddrStack.Offset );*/
_ftprintf(fLogFile, _T("\n%lu: %3d"), g_dwShowCount, frameNum);
if ( s.AddrPC.Offset == 0 )
{
_ftprintf(fLogFile, _T(" (-nosymbols- PC == 0)\n"));
}
else
{ // we seem to have a valid PC
// show procedure info (SymGetSymFromAddr())
if ( ! pSGSFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) )
{
if ( gle != 487 )
_ftprintf(fLogFile, _T(" SymGetSymFromAddr(): GetLastError = %lu\n"), gle );
else
_ftprintf(fLogFile, _T("\n"));
}
else
{
// UnDecorateSymbolName()
undName[0] = 0;
pUDSN( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY );
undFullName[0] = 0;
pUDSN( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE );
if (strlen(undName) > 0)
fprintf(fLogFile, " %s %+ld bytes\n", undName, (long) offsetFromSymbol );
else
fprintf(fLogFile, " Sig: %s %+ld bytes\n", pSym->Name, (long) offsetFromSymbol );
fprintf(fLogFile, "%lu: Decl: %s\n", g_dwShowCount, undFullName );
}
// show line number info, NT5.0-method (SymGetLineFromAddr())
if ( pSGLFA != NULL )
{ // yes, we have SymGetLineFromAddr()
if ( ! pSGLFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, &Line ) )
{
if ( gle != 487 )
_ftprintf(fLogFile, _T("%lu: SymGetLineFromAddr(): GetLastError = %lu\n"), g_dwShowCount, gle );
}
else
{
fprintf(fLogFile, "%lu: Line: %s(%lu) %+ld bytes\n", g_dwShowCount,
Line.FileName, Line.LineNumber, offsetFromSymbol );
}
} // yes, we have SymGetLineFromAddr()
// show module info (SymGetModuleInfo())
if ( ! pSGMI( hProcess, s.AddrPC.Offset, &Module ) )
{
_ftprintf(fLogFile, _T("%lu: SymGetModuleInfo): GetLastError = %lu\n"), g_dwShowCount, gle );
}
else
{ // got module info OK
char ty[80];
switch ( Module.SymType )
{
case SymNone:
strcpy( ty, "-nosymbols-" );
break;
case SymCoff:
strcpy( ty, "COFF" );
break;
case SymCv:
strcpy( ty, "CV" );
break;
case SymPdb:
strcpy( ty, "PDB" );
break;
case SymExport:
strcpy( ty, "-exported-" );
break;
case SymDeferred:
strcpy( ty, "-deferred-" );
break;
case SymSym:
strcpy( ty, "SYM" );
break;
default:
_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
break;
}
//fprintf(fLogFile, "%lu: Mod: %s[%s], base: %08lxh\n", g_dwShowCount,
// Module.ModuleName, Module.ImageName, Module.BaseOfImage );
fprintf(fLogFile, "%lu: Mod: %s, base: %08lxh\n", g_dwShowCount,
Module.ModuleName, Module.BaseOfImage );
if (Module.SymType == SymNone) { // Gebe nur aus, wenn keine Symbole vorhanden sind!
_ftprintf(fLogFile, _T("%lu: Offset: 0x%8.8x\n"), g_dwShowCount, s.AddrPC.Offset);
fprintf(fLogFile, "%lu: Sym: type: %s, file: %s\n", g_dwShowCount,
ty, Module.LoadedImageName );
}
} // got module info OK
} // we seem to have a valid PC
// no return address means no deeper stackframe
if ( s.AddrReturn.Offset == 0 )
{
// avoid misunderstandings in the printf() following the loop
SetLastError( 0 );
break;
}
} // for ( frameNum )
if ( gle != 0 )
_ftprintf(fLogFile, _T("\n%lu: StackWalk(): GetLastError = %lu\n"), g_dwShowCount, gle );
cleanup:
// de-init symbol handler etc. (SymCleanup())
//pSC( hProcess );
//if (pSym) free( pSym );
if (fLogFile) {
_ftprintf(fLogFile, _T("\n\n"));
if (g_dwShowCount % 1000)
fflush(fLogFile);
}
LeaveCriticalSection(&g_csFileOpenClose);
// Critical section end...
} // ShowStackRM
struct ModuleEntry
{
std::string imageName;
std::string moduleName;
DWORD baseAddress;
DWORD size;
};
typedef std::vector< ModuleEntry > ModuleList;
typedef ModuleList::iterator ModuleListIter;
static bool fillModuleList( ModuleList& modules, DWORD pid, HANDLE hProcess, FILE *fLogFile );
static bool fillModuleListTH32( ModuleList& modules, DWORD pid, FILE *fLogFile );
static bool fillModuleListPSAPI( ModuleList& modules, DWORD pid, HANDLE hProcess, FILE *fLogFile );
static void enumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid, FILE *fLogFile )
{
static ModuleList modules;
static ModuleListIter it;
char *img, *mod;
// fill in module list
fillModuleList( modules, pid, hProcess, fLogFile );
for ( it = modules.begin(); it != modules.end(); ++ it )
{
// unfortunately, SymLoadModule() wants writeable strings
img = (char*) malloc(sizeof(char) * ((*it).imageName.size() + 1));
strcpy( img, (*it).imageName.c_str() );
mod = (char*) malloc(sizeof(char) * ((*it).moduleName.size() + 1));
strcpy( mod, (*it).moduleName.c_str() );
/*if ( pSLM( hProcess, 0, img, mod, (*it).baseAddress, (*it).size ) == 0 )
_ftprintf(fLogFile, _T("%lu: Error %lu loading symbols for \"%s\"\n"), g_dwShowCount,
gle, (*it).moduleName.c_str() );
else
_ftprintf(fLogFile, _T("%lu: Symbols loaded: \"%s\"\n"), g_dwShowCount, (*it).moduleName.c_str() );*/
pSLM( hProcess, 0, img, mod, (*it).baseAddress, (*it).size );
free(img);
free(mod);
}
} // enumAndLoadModuleSymbols
static bool fillModuleList( ModuleList& modules, DWORD pid, HANDLE hProcess, FILE *fLogFile )
{
// try toolhelp32 first
if ( fillModuleListTH32( modules, pid, fLogFile ) )
return true;
// nope? try psapi, then
return fillModuleListPSAPI( modules, pid, hProcess, fLogFile );
} // fillModuleList
// miscellaneous toolhelp32 declarations; we cannot #include the header
// because not all systems may have it
#define MAX_MODULE_NAME32 255
#define TH32CS_SNAPMODULE 0x00000008
#pragma pack( push, 8 )
typedef struct tagMODULEENTRY32
{
DWORD dwSize;
DWORD th32ModuleID; // This module
DWORD th32ProcessID; // owning process
DWORD GlblcntUsage; // Global usage count on the module
DWORD ProccntUsage; // Module usage count in th32ProcessID's context
BYTE * modBaseAddr; // Base address of module in th32ProcessID's context
DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr
HMODULE hModule; // The hModule of this module in th32ProcessID's context
char szModule[MAX_MODULE_NAME32 + 1];
char szExePath[MAX_PATH];
} MODULEENTRY32;
typedef MODULEENTRY32 * PMODULEENTRY32;
typedef MODULEENTRY32 * LPMODULEENTRY32;
#pragma pack( pop )
static bool fillModuleListTH32( ModuleList& modules, DWORD pid, FILE *fLogFile )
{
// CreateToolhelp32Snapshot()
typedef HANDLE (__stdcall *tCT32S)( DWORD dwFlags, DWORD th32ProcessID );
// Module32First()
typedef BOOL (__stdcall *tM32F)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );
// Module32Next()
typedef BOOL (__stdcall *tM32N)( HANDLE hSnapshot, LPMODULEENTRY32 lpme );
// I think the DLL is called tlhelp32.dll on Win9X, so we try both
const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") };
HINSTANCE hToolhelp;
tCT32S pCT32S;
tM32F pM32F;
tM32N pM32N;
HANDLE hSnap;
MODULEENTRY32 me = { sizeof me };
bool keepGoing;
ModuleEntry e;
int i;
for ( i = 0; i < lenof( dllname ); ++ i )
{
hToolhelp = LoadLibrary( dllname[i] );
if ( hToolhelp == 0 )
continue;
pCT32S = (tCT32S) GetProcAddress( hToolhelp, "CreateToolhelp32Snapshot" );
pM32F = (tM32F) GetProcAddress( hToolhelp, "Module32First" );
pM32N = (tM32N) GetProcAddress( hToolhelp, "Module32Next" );
if ( pCT32S != 0 && pM32F != 0 && pM32N != 0 )
break; // found the functions!
FreeLibrary( hToolhelp );
hToolhelp = 0;
}
if ( hToolhelp == 0 ) // nothing found?
return false;
hSnap = pCT32S( TH32CS_SNAPMODULE, pid );
if ( hSnap == (HANDLE) -1 )
return false;
keepGoing = !!pM32F( hSnap, &me );
while ( keepGoing )
{
// here, we have a filled-in MODULEENTRY32
//_ftprintf(fLogFile, _T("%lu: %08lXh %6lu %-15.15s %s\n"), g_dwShowCount, me.modBaseAddr, me.modBaseSize, me.szModule, me.szExePath );
e.imageName = me.szExePath;
e.moduleName = me.szModule;
e.baseAddress = (DWORD) me.modBaseAddr;
e.size = me.modBaseSize;
modules.push_back( e );
keepGoing = !!pM32N( hSnap, &me );
}
CloseHandle( hSnap );
FreeLibrary( hToolhelp );
return modules.size() != 0;
} // fillModuleListTH32
// miscellaneous psapi declarations; we cannot #include the header
// because not all systems may have it
typedef struct _MODULEINFO {
LPVOID lpBaseOfDll;
DWORD SizeOfImage;
LPVOID EntryPoint;
} MODULEINFO, *LPMODULEINFO;
static bool fillModuleListPSAPI( ModuleList& modules, DWORD pid, HANDLE hProcess, FILE *fLogFile )
{
// EnumProcessModules()
typedef BOOL (__stdcall *tEPM)( HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded );
// GetModuleFileNameEx()
typedef DWORD (__stdcall *tGMFNE)( HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
// GetModuleBaseName() -- redundant, as GMFNE() has the same prototype, but who cares?
typedef DWORD (__stdcall *tGMBN)( HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize );
// GetModuleInformation()
typedef BOOL (__stdcall *tGMI)( HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize );
HINSTANCE hPsapi;
tEPM pEPM;
tGMFNE pGMFNE;
tGMBN pGMBN;
tGMI pGMI;
DWORD i;
ModuleEntry e;
DWORD cbNeeded;
MODULEINFO mi;
HMODULE *hMods = 0;
char *tt = 0;
hPsapi = LoadLibrary( _T("psapi.dll") );
if ( hPsapi == 0 )
return false;
modules.clear();
pEPM = (tEPM) GetProcAddress( hPsapi, "EnumProcessModules" );
pGMFNE = (tGMFNE) GetProcAddress( hPsapi, "GetModuleFileNameExA" );
pGMBN = (tGMFNE) GetProcAddress( hPsapi, "GetModuleBaseNameA" );
pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
if ( pEPM == 0 || pGMFNE == 0 || pGMBN == 0 || pGMI == 0 )
{
// yuck. Some API is missing.
FreeLibrary( hPsapi );
return false;
}
hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof HMODULE));
tt = (char*) malloc(sizeof(char) * TTBUFLEN);
// not that this is a sample. Which means I can get away with
// not checking for errors, but you cannot. :)
if ( ! pEPM( hProcess, hMods, TTBUFLEN, &cbNeeded ) )
{
_ftprintf(fLogFile, _T("%lu: EPM failed, GetLastError = %lu\n"), g_dwShowCount, gle );
goto cleanup;
}
if ( cbNeeded > TTBUFLEN )
{
_ftprintf(fLogFile, _T("%lu: More than %lu module handles. Huh?\n"), g_dwShowCount, lenof( hMods ) );
goto cleanup;
}
for ( i = 0; i < cbNeeded / sizeof hMods[0]; ++ i )
{
// for each module, get:
// base address, size
pGMI( hProcess, hMods[i], &mi, sizeof mi );
e.baseAddress = (DWORD) mi.lpBaseOfDll;
e.size = mi.SizeOfImage;
// image file name
tt[0] = '\0';
pGMFNE( hProcess, hMods[i], tt, TTBUFLEN );
e.imageName = tt;
// module name
tt[0] = '\0';
pGMBN( hProcess, hMods[i], tt, TTBUFLEN );
e.moduleName = tt;
//_ftprintf(fLogFile, _T("%lu: %08lXh %6lu %-15.15s %s\n"), g_dwShowCount, e.baseAddress,
// e.size, e.moduleName.c_str(), e.imageName.c_str() );
modules.push_back( e );
}
cleanup:
if ( hPsapi )
FreeLibrary( hPsapi );
free(tt);
free(hMods);
return modules.size() != 0;
} // fillModuleListPSAPI