2007-01-08 09:41:15 +00:00
|
|
|
|
/*////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
* Project:
|
|
|
|
|
* Memory_and_Exception_Trace
|
|
|
|
|
*
|
|
|
|
|
* ///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
* File:
|
|
|
|
|
* Stackwalker.cpp
|
|
|
|
|
*
|
|
|
|
|
* Remarks:
|
|
|
|
|
* Dumps memory leaks (unreleased allocations) for CRT-Allocs and COM-Allocs
|
|
|
|
|
* Dumps the stack of an thread if an exepction occurs
|
|
|
|
|
*
|
|
|
|
|
* Known bugs:
|
|
|
|
|
* - If the allocation-RequestID wrap, then allocations will get lost...
|
|
|
|
|
*
|
|
|
|
|
* Author:
|
|
|
|
|
* Jochen Kalmbach, Germany
|
|
|
|
|
* (c) 2002-2005 (Freeware)
|
|
|
|
|
* http://www.codeproject.com/tools/leakfinder.asp
|
|
|
|
|
*
|
|
|
|
|
* License (The zlib/libpng License, http://www.opensource.org/licenses/zlib-license.php):
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2005 Jochen Kalmbach
|
|
|
|
|
*
|
|
|
|
|
* This software is provided 'as-is', without any express or implied warranty.
|
|
|
|
|
* In no event will the authors be held liable for any damages arising from the
|
|
|
|
|
* use of this software.
|
|
|
|
|
*
|
|
|
|
|
* Permission is granted to anyone to use this software for any purpose, including
|
|
|
|
|
* commercial applications, and to alter it and redistribute it freely, subject to
|
|
|
|
|
* the following restrictions:
|
|
|
|
|
*
|
|
|
|
|
* 1. The origin of this software must not be misrepresented; you must not claim
|
|
|
|
|
* that you wrote the original software. If you use this software in a product,
|
|
|
|
|
* an acknowledgment in the product documentation would be appreciated but is
|
|
|
|
|
* not required.
|
|
|
|
|
*
|
|
|
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
|
|
|
* misrepresented as being the original software.
|
|
|
|
|
*
|
|
|
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
|
|
|
*
|
|
|
|
|
*//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
#include <windows.h>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <vector>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <crtdbg.h>
|
|
|
|
|
#include <tchar.h>
|
|
|
|
|
|
|
|
|
|
#include "memleak.h"
|
2011-02-19 21:06:40 +00:00
|
|
|
|
#include "gfilutil.h"
|
2007-01-08 09:41:15 +00:00
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// the form of the output file
|
|
|
|
|
static eAllocCheckOutput g_CallstackOutputType = ACOutput_Simple;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Size of Hash-Table (this should be a prime number to avoid collisions)
|
|
|
|
|
#define ALLOC_HASH_ENTRIES 1023
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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: 1 MByte (only for StackwalkFilter)
|
|
|
|
|
#define LOG_FILE_MAX_SIZE 1024*1024
|
|
|
|
|
|
|
|
|
|
// If the following is defined, then COM-Leaks will also be tracked
|
|
|
|
|
#define WITH_IMALLOC_SPY
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// #############################################################################################
|
|
|
|
|
#ifdef WITH_IMALLOC_SPY
|
|
|
|
|
//forwards:
|
|
|
|
|
void IMallocHashInsert(void *pData, CONTEXT &Context, size_t nDataSize);
|
|
|
|
|
BOOL IMallocHashRemove(void *pData);
|
|
|
|
|
|
|
|
|
|
// IMallocSpy-Interface
|
|
|
|
|
class CMallocSpy : public IMallocSpy
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
CMallocSpy(void) {
|
|
|
|
|
m_cbRequest = 0;
|
|
|
|
|
}
|
|
|
|
|
~CMallocSpy(void) {
|
|
|
|
|
}
|
|
|
|
|
// IUnknown methods
|
|
|
|
|
STDMETHOD(QueryInterface) (REFIID riid, LPVOID *ppUnk) {
|
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
|
if (IsEqualIID(riid, IID_IUnknown)) {
|
|
|
|
|
*ppUnk = (IUnknown *) this;
|
|
|
|
|
}
|
|
|
|
|
else if (IsEqualIID(riid, IID_IMallocSpy)) {
|
|
|
|
|
*ppUnk = (IMalloc *) this;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
*ppUnk = NULL;
|
|
|
|
|
hr = E_NOINTERFACE;
|
|
|
|
|
}
|
|
|
|
|
AddRef();
|
|
|
|
|
return hr;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(ULONG, AddRef) (void) {
|
|
|
|
|
return InterlockedIncrement(&m_cRef);
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(ULONG, Release) (void) {
|
|
|
|
|
LONG cRef;
|
|
|
|
|
cRef = InterlockedDecrement(&m_cRef);
|
|
|
|
|
if (cRef == 0)
|
|
|
|
|
{
|
|
|
|
|
delete this;
|
|
|
|
|
}
|
|
|
|
|
return cRef;
|
|
|
|
|
}
|
|
|
|
|
// IMallocSpy methods
|
|
|
|
|
STDMETHOD_(ULONG, PreAlloc) (ULONG cbRequest) {
|
|
|
|
|
m_cbRequest = cbRequest;
|
|
|
|
|
return cbRequest;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void *, PostAlloc) (void *pActual) {
|
|
|
|
|
HANDLE hThread;
|
|
|
|
|
if (DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
|
|
|
|
|
GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS ) != 0) {
|
|
|
|
|
// Ok
|
|
|
|
|
CONTEXT c;
|
|
|
|
|
memset( &c, '\0', sizeof c );
|
|
|
|
|
c.ContextFlags = CONTEXT_FULL;
|
|
|
|
|
#if 0
|
|
|
|
|
if ( GetThreadContext(hThread, &c) != 0) {
|
|
|
|
|
#else
|
|
|
|
|
__asm
|
|
|
|
|
{
|
|
|
|
|
call x
|
|
|
|
|
x: pop eax
|
|
|
|
|
mov c.Eip, eax
|
|
|
|
|
mov c.Ebp, ebp
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
#endif
|
|
|
|
|
// Ok
|
|
|
|
|
IMallocHashInsert(pActual, c, m_cbRequest);
|
|
|
|
|
}
|
|
|
|
|
CloseHandle(hThread);
|
|
|
|
|
}
|
|
|
|
|
return pActual;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void *, PreFree) (void *pRequest, BOOL /* fSpyed */) {
|
|
|
|
|
IMallocHashRemove(pRequest);
|
|
|
|
|
return pRequest;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void, PostFree) (BOOL /* fSpyed */) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(ULONG, PreRealloc) (void *pRequest, ULONG cbRequest,
|
|
|
|
|
void **ppNewRequest, BOOL /* fSpyed */) {
|
|
|
|
|
IMallocHashRemove(pRequest);
|
|
|
|
|
m_cbRequest = cbRequest;
|
|
|
|
|
*ppNewRequest = pRequest; // Bug fixed. Thanx to Christoph Weber
|
|
|
|
|
return cbRequest;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void *, PostRealloc) (void *pActual, BOOL /* fSpyed */) {
|
|
|
|
|
HANDLE hThread;
|
|
|
|
|
if (DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
|
|
|
|
|
GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS ) != 0) {
|
|
|
|
|
// Ok
|
|
|
|
|
CONTEXT c;
|
|
|
|
|
memset( &c, '\0', sizeof c );
|
|
|
|
|
c.ContextFlags = CONTEXT_FULL;
|
|
|
|
|
#if 0
|
|
|
|
|
if ( GetThreadContext(hThread, &c) != 0) {
|
|
|
|
|
#else
|
|
|
|
|
__asm
|
|
|
|
|
{
|
|
|
|
|
call x
|
|
|
|
|
x: pop eax
|
|
|
|
|
mov c.Eip, eax
|
|
|
|
|
mov c.Ebp, ebp
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
#endif
|
|
|
|
|
// Ok
|
|
|
|
|
IMallocHashInsert(pActual, c, m_cbRequest);
|
|
|
|
|
}
|
|
|
|
|
CloseHandle(hThread);
|
|
|
|
|
}
|
|
|
|
|
return pActual;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void *, PreGetSize) (void *pRequest, BOOL /* fSpyed */) {
|
|
|
|
|
return pRequest;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(ULONG, PostGetSize) (ULONG cbActual, BOOL /* fSpyed */) {
|
|
|
|
|
return cbActual;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void *, PreDidAlloc) (void *pRequest, BOOL /* fSpyed */) {
|
|
|
|
|
return pRequest;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(BOOL, PostDidAlloc) (void * /* pRequest */, BOOL /* fSpyed */, BOOL fActual) {
|
|
|
|
|
return fActual;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void, PreHeapMinimize) (void) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
STDMETHOD_(void, PostHeapMinimize) (void) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
LONG m_cRef;
|
|
|
|
|
ULONG m_cbRequest;
|
|
|
|
|
};
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// #############################################################################################
|
|
|
|
|
// Here I have included the API-Version 9 declarations, so it will also compile on systems, where the new PSDK is not installed
|
|
|
|
|
// Normally we just need to include the "dbghelp.h" file
|
|
|
|
|
#include <imagehlp.h>
|
|
|
|
|
#if API_VERSION_NUMBER < 9
|
|
|
|
|
typedef
|
|
|
|
|
BOOL
|
|
|
|
|
(__stdcall *PREAD_PROCESS_MEMORY_ROUTINE64)(
|
|
|
|
|
HANDLE hProcess,
|
|
|
|
|
DWORD64 qwBaseAddress,
|
|
|
|
|
PVOID lpBuffer,
|
|
|
|
|
DWORD nSize,
|
|
|
|
|
LPDWORD lpNumberOfBytesRead
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
typedef struct _IMAGEHLP_LINE64 {
|
|
|
|
|
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64)
|
|
|
|
|
PVOID Key; // internal
|
|
|
|
|
DWORD LineNumber; // line number in file
|
|
|
|
|
PCHAR FileName; // full filename
|
|
|
|
|
DWORD64 Address; // first instruction of line
|
|
|
|
|
} IMAGEHLP_LINE64, *PIMAGEHLP_LINE64;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct _IMAGEHLP_MODULE64 {
|
|
|
|
|
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
|
|
|
|
|
DWORD64 BaseOfImage; // base load address of module
|
|
|
|
|
DWORD ImageSize; // virtual size of the loaded module
|
|
|
|
|
DWORD TimeDateStamp; // date/time stamp from pe header
|
|
|
|
|
DWORD CheckSum; // checksum from the pe header
|
|
|
|
|
DWORD NumSyms; // number of symbols in the symbol table
|
|
|
|
|
SYM_TYPE SymType; // type of symbols loaded
|
|
|
|
|
CHAR ModuleName[32]; // module name
|
|
|
|
|
CHAR ImageName[256]; // image name
|
|
|
|
|
CHAR LoadedImageName[256]; // symbol file name
|
|
|
|
|
} IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64;
|
|
|
|
|
|
|
|
|
|
typedef struct _IMAGEHLP_SYMBOL64 {
|
|
|
|
|
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64)
|
|
|
|
|
DWORD64 Address; // virtual address including dll base address
|
|
|
|
|
DWORD Size; // estimated size of symbol, can be zero
|
|
|
|
|
DWORD Flags; // info about the symbols, see the SYMF defines
|
|
|
|
|
DWORD MaxNameLength; // maximum size of symbol name in 'Name'
|
|
|
|
|
CHAR Name[1]; // symbol name (null terminated string)
|
|
|
|
|
} IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64;
|
|
|
|
|
|
|
|
|
|
typedef struct _tagADDRESS64 {
|
|
|
|
|
DWORD64 Offset;
|
|
|
|
|
WORD Segment;
|
|
|
|
|
ADDRESS_MODE Mode;
|
|
|
|
|
} ADDRESS64, *LPADDRESS64;
|
|
|
|
|
|
|
|
|
|
typedef struct _KDHELP64 {
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// address of kernel thread object, as provided in the
|
|
|
|
|
// WAIT_STATE_CHANGE packet.
|
|
|
|
|
//
|
|
|
|
|
DWORD64 Thread;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// offset in thread object to pointer to the current callback frame
|
|
|
|
|
// in kernel stack.
|
|
|
|
|
//
|
|
|
|
|
DWORD ThCallbackStack;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// offset in thread object to pointer to the current callback backing
|
|
|
|
|
// store frame in kernel stack.
|
|
|
|
|
//
|
|
|
|
|
DWORD ThCallbackBStore;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// offsets to values in frame:
|
|
|
|
|
//
|
|
|
|
|
// address of next callback frame
|
|
|
|
|
DWORD NextCallback;
|
|
|
|
|
|
|
|
|
|
// address of saved frame pointer (if applicable)
|
|
|
|
|
DWORD FramePointer;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Address of the kernel function that calls out to user mode
|
|
|
|
|
//
|
|
|
|
|
DWORD64 KiCallUserMode;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Address of the user mode dispatcher function
|
|
|
|
|
//
|
|
|
|
|
DWORD64 KeUserCallbackDispatcher;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Lowest kernel mode address
|
|
|
|
|
//
|
|
|
|
|
DWORD64 SystemRangeStart;
|
|
|
|
|
|
|
|
|
|
DWORD64 Reserved[8];
|
|
|
|
|
|
|
|
|
|
} KDHELP64, *PKDHELP64;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct _tagSTACKFRAME64 {
|
|
|
|
|
ADDRESS64 AddrPC; // program counter
|
|
|
|
|
ADDRESS64 AddrReturn; // return address
|
|
|
|
|
ADDRESS64 AddrFrame; // frame pointer
|
|
|
|
|
ADDRESS64 AddrStack; // stack pointer
|
|
|
|
|
ADDRESS64 AddrBStore; // backing store pointer
|
|
|
|
|
PVOID FuncTableEntry; // pointer to pdata/fpo or NULL
|
|
|
|
|
DWORD64 Params[4]; // possible arguments to the function
|
|
|
|
|
BOOL Far; // WOW far call
|
|
|
|
|
BOOL Virtual; // is this a virtual frame?
|
|
|
|
|
DWORD64 Reserved[3];
|
|
|
|
|
KDHELP64 KdHelp;
|
|
|
|
|
} STACKFRAME64, *LPSTACKFRAME64;
|
|
|
|
|
|
|
|
|
|
typedef
|
|
|
|
|
PVOID
|
|
|
|
|
(__stdcall *PFUNCTION_TABLE_ACCESS_ROUTINE64)(
|
|
|
|
|
HANDLE hProcess,
|
|
|
|
|
DWORD64 AddrBase
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
typedef
|
|
|
|
|
DWORD64
|
|
|
|
|
(__stdcall *PGET_MODULE_BASE_ROUTINE64)(
|
|
|
|
|
HANDLE hProcess,
|
|
|
|
|
DWORD64 Address
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
typedef
|
|
|
|
|
DWORD64
|
|
|
|
|
(__stdcall *PTRANSLATE_ADDRESS_ROUTINE64)(
|
|
|
|
|
HANDLE hProcess,
|
|
|
|
|
HANDLE hThread,
|
|
|
|
|
LPADDRESS64 lpaddr
|
|
|
|
|
);
|
|
|
|
|
#endif
|
|
|
|
|
// #############################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Forward definitions of functions:
|
|
|
|
|
static void ShowStackRM( HANDLE hThread, CONTEXT& c, FILE *fLogFile, PREAD_PROCESS_MEMORY_ROUTINE64 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(bool bAppend = true) {
|
|
|
|
|
// is the File already open? If not open it...
|
|
|
|
|
if (g_fFile == NULL)
|
|
|
|
|
if (g_pszAllocLogName != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (bAppend == false)
|
|
|
|
|
g_fFile = _tfopen(g_pszAllocLogName, _T("w"));
|
|
|
|
|
else
|
|
|
|
|
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, BOOL asXMLAttrs = FALSE) {
|
|
|
|
|
TCHAR pszTemp[11], pszTemp2[11];
|
|
|
|
|
|
|
|
|
|
if (fFile != NULL) {
|
|
|
|
|
_tstrdate( pszTemp );
|
|
|
|
|
_tstrtime( pszTemp2 );
|
|
|
|
|
if (asXMLAttrs == FALSE)
|
|
|
|
|
_ftprintf(fFile, _T("%s %s"), pszTemp, pszTemp2 ); // also ok after year 2038 (asctime is NOT ok)
|
|
|
|
|
else
|
|
|
|
|
_ftprintf(fFile, _T("date=\"%s\" time=\"%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;
|
|
|
|
|
|
|
|
|
|
// ##########################################################################################
|
|
|
|
|
#ifdef WITH_IMALLOC_SPY
|
|
|
|
|
// eigene Tabelle f<>r die IMallocs:
|
|
|
|
|
typedef struct IMallocHashEntryType {
|
|
|
|
|
void *pData; // Key-Word
|
|
|
|
|
size_t nDataSize; // gr<67><72>e des Datenblocks (optional)
|
|
|
|
|
char cRemovedFlag; // 0 => nicht wurde noch nicht freigegeben
|
|
|
|
|
struct IMallocHashEntryType *Next;
|
|
|
|
|
// Callstack f<>r EIP
|
|
|
|
|
DWORD dwEIPOffset;
|
|
|
|
|
DWORD dwEIPLen;
|
|
|
|
|
char pcEIPAddr[MAX_EIP_LEN_BUF];
|
|
|
|
|
// Callstack f<>r ESP
|
|
|
|
|
DWORD dwESPOffset;
|
|
|
|
|
DWORD dwESPLen;
|
|
|
|
|
char pcESPAddr[MAX_ESP_LEN_BUF];
|
|
|
|
|
} IMallocHashEntryType;
|
|
|
|
|
|
|
|
|
|
static IMallocHashEntryType IMallocHashTable[ALLOC_HASH_ENTRIES];
|
|
|
|
|
|
|
|
|
|
static ULONG IMallocHashEntries = 0;
|
|
|
|
|
static ULONG IMallocHashCollisions = 0;
|
|
|
|
|
static ULONG IMallocHashFreed = 0;
|
|
|
|
|
static ULONG IMallocHashMaxUsed = 0; // maximal number of concurrent entries
|
|
|
|
|
static ULONG IMallocHashCurrentCount = 0;
|
|
|
|
|
|
|
|
|
|
static ULONG IMallocHashMaxCollisions = 0;
|
|
|
|
|
static ULONG IMallocHashCurrentCollisions = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//static void AllocHashOut(FILE*);
|
|
|
|
|
static ULONG IMallocHashOutLeaks(FILE*);
|
|
|
|
|
|
|
|
|
|
// AllocHashFunction
|
|
|
|
|
// Die eigentliche Hash-Funktion (hier ganz simpel)
|
|
|
|
|
static ULONG IMallocHashFunction(void *pData) {
|
|
|
|
|
ULONG ulTemp;
|
|
|
|
|
DWORD dwPointer = (DWORD) pData;
|
|
|
|
|
|
|
|
|
|
// relativ simpler Mechanismus f<>r die Hash-Funktion,
|
|
|
|
|
// mir ist nur nix besseres eingefallen...
|
|
|
|
|
ulTemp = dwPointer % ALLOC_HASH_ENTRIES;
|
|
|
|
|
|
|
|
|
|
_ASSERTE( (ulTemp >= 0) && (ulTemp < ALLOC_HASH_ENTRIES) );
|
|
|
|
|
|
|
|
|
|
return ulTemp;
|
|
|
|
|
} // AllocHashFunction
|
|
|
|
|
|
|
|
|
|
// IMallocHashInsert
|
|
|
|
|
// pData: Key-Word (Pointer to address)
|
|
|
|
|
// pContext: Context-Record, for retrieving Callstack (EIP and EBP is only needed)
|
|
|
|
|
// nDataSize: How many bytes
|
|
|
|
|
void IMallocHashInsert(void *pData, CONTEXT &Context, size_t nDataSize) {
|
|
|
|
|
ULONG HashIdx;
|
|
|
|
|
IMallocHashEntryType *pHashEntry;
|
|
|
|
|
|
|
|
|
|
// ermittle Statistische Werte
|
|
|
|
|
IMallocHashEntries++;
|
|
|
|
|
IMallocHashCurrentCount++;
|
|
|
|
|
if (IMallocHashCurrentCount > IMallocHashMaxUsed)
|
|
|
|
|
IMallocHashMaxUsed = IMallocHashCurrentCount;
|
|
|
|
|
|
|
|
|
|
// ermittle den Hash-Wert
|
|
|
|
|
HashIdx = IMallocHashFunction(pData);
|
|
|
|
|
|
|
|
|
|
// Eintrag darf nicht gr<67><72>er als die Hash-Tabelle sein
|
|
|
|
|
_ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
|
|
|
|
|
|
|
|
|
|
pHashEntry = &IMallocHashTable[HashIdx];
|
|
|
|
|
if (pHashEntry->pData == 0) {
|
|
|
|
|
// es ist noch kein Eintrag da
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
//Statistische Daten:
|
|
|
|
|
IMallocHashCollisions++;
|
|
|
|
|
IMallocHashCurrentCollisions++;
|
|
|
|
|
if (IMallocHashCurrentCollisions > IMallocHashMaxCollisions)
|
|
|
|
|
IMallocHashMaxCollisions = IMallocHashCurrentCollisions;
|
|
|
|
|
|
|
|
|
|
// Eintrag ist schon belegt, verkette die Eintr<74>ge
|
|
|
|
|
// wenn dies oft vorkommt, sollte man entweder die Tabelle vergr<67><72>ern oder eine
|
|
|
|
|
// andere Hash-Funktion w<>hlen
|
|
|
|
|
while(pHashEntry->Next != NULL) {
|
|
|
|
|
pHashEntry = pHashEntry->Next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pHashEntry->Next = (IMallocHashEntryType*) _calloc_dbg(sizeof(IMallocHashEntryType), 1, _CRT_BLOCK, __FILE__, __LINE__);
|
|
|
|
|
pHashEntry = pHashEntry->Next;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
pHashEntry->pData = pData; // 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
|
|
|
|
|
DWORD 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IMallocHashFind
|
|
|
|
|
// Wird ALLOC_ENTRY_NOT_FOUND zur<75>ckgegeben, so wurde der Key nicht
|
|
|
|
|
// gefunden, ansonsten wird ein Zeiger auf den Hash-Eintrag zur<75>ckgegeben
|
|
|
|
|
// ACHTUNG: In einem preemptiven Tasking-System kann hier nicht
|
|
|
|
|
// garantiert werden, ob der Zeiger noch g<>ltig ist, wenn er
|
|
|
|
|
// zur<75>ckgegeben wird, da er von einem anderen Thread schon
|
|
|
|
|
// freigegeben sein k<>nnte.
|
|
|
|
|
// Die synchronisation mu<6D> eine Ebene h<>her erfolgen
|
|
|
|
|
static IMallocHashEntryType *IMallocHashFind(void *pData) {
|
|
|
|
|
ULONG HashIdx;
|
|
|
|
|
IMallocHashEntryType *pHashEntry;
|
|
|
|
|
|
|
|
|
|
// ermittle den Hash-Wert
|
|
|
|
|
HashIdx = IMallocHashFunction(pData);
|
|
|
|
|
|
|
|
|
|
// Eintrag darf nicht gr<67><72>er als die Hash-Tabelle sein
|
|
|
|
|
_ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
|
|
|
|
|
|
|
|
|
|
pHashEntry = &IMallocHashTable[HashIdx];
|
|
|
|
|
while(pHashEntry != NULL) {
|
|
|
|
|
if (pHashEntry->pData == pData) {
|
|
|
|
|
return pHashEntry;
|
|
|
|
|
}
|
|
|
|
|
pHashEntry = pHashEntry->Next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wenn hier angelangt, dann wurde der Eintrag nicht gefunden!
|
|
|
|
|
return (IMallocHashEntryType*) ALLOC_ENTRY_NOT_FOUND;
|
|
|
|
|
} // AllocHashFind
|
|
|
|
|
|
|
|
|
|
// IMallocHashRemove
|
|
|
|
|
// Return: FALSE (0) : Key wurde gefunden und entfernt/markiert
|
|
|
|
|
// TRUE (!=0): Key wurde nicht gefunden!
|
|
|
|
|
BOOL IMallocHashRemove(void *pData) {
|
|
|
|
|
ULONG HashIdx;
|
|
|
|
|
IMallocHashEntryType *pHashEntry, *pHashEntryLast;
|
|
|
|
|
|
|
|
|
|
// ermittle den Hash-Wert
|
|
|
|
|
HashIdx = IMallocHashFunction(pData);
|
|
|
|
|
|
|
|
|
|
// Eintrag darf nicht gr<67><72>er als die Hash-Tabelle sein
|
|
|
|
|
_ASSERTE(HashIdx < ALLOC_HASH_ENTRIES);
|
|
|
|
|
|
|
|
|
|
pHashEntryLast = NULL;
|
|
|
|
|
pHashEntry = &IMallocHashTable[HashIdx];
|
|
|
|
|
while(pHashEntry != NULL) {
|
|
|
|
|
if (pHashEntry->pData == pData) {
|
|
|
|
|
#ifdef HASH_ENTRY_REMOVE_AT_FREE
|
|
|
|
|
IMallocHashFreed++;
|
|
|
|
|
IMallocHashCurrentCount--;
|
|
|
|
|
// gebe den Speicher frei
|
|
|
|
|
if (pHashEntryLast == NULL) {
|
|
|
|
|
// Es ist ein Eintrag direkt in der Tabelle
|
|
|
|
|
if (pHashEntry->Next == NULL) {
|
|
|
|
|
// Es ist der letze Eintrag l<>sche also die Tabelle
|
|
|
|
|
memset(&IMallocHashTable[HashIdx], 0, sizeof(IMallocHashTable[HashIdx]));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Es sind noch Eintr<74>ge verkettet, <20>berschreibe einfach den nicht mehr gebrauchten...
|
|
|
|
|
IMallocHashEntryType *pTmp = pHashEntry->Next;
|
|
|
|
|
*pHashEntry = *(pHashEntry->Next);
|
|
|
|
|
_free_dbg(pTmp, _CRT_BLOCK);
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// ich bin in einem dynamischen Bereich
|
|
|
|
|
// dies war eine kollisions, z<>hle also wieder zur<75>ck:
|
|
|
|
|
IMallocHashCurrentCollisions--;
|
|
|
|
|
pHashEntryLast->Next = pHashEntry->Next;
|
|
|
|
|
_free_dbg(pHashEntry, _CRT_BLOCK);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
// erh<72>he nur den Removed counter und behalte das Object im Speicher
|
|
|
|
|
pHashEntry->cRemovedFlag++;
|
|
|
|
|
return TRUE; // erfolgreich
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
pHashEntryLast = pHashEntry;
|
|
|
|
|
pHashEntry = pHashEntry->Next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wenn hier angelangt, dann wurde der Eintrag nicht gefunden!
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Callback-Funtion for StackWalk f<>r meine CallStack-Ausgabe aus der Hash-Tabelle
|
|
|
|
|
static BOOL __stdcall ReadProcMemoryFromIMallocHash(HANDLE pData, DWORD64 lpBaseAddress, PVOID lpBuffer, DWORD nSize, PDWORD lpNumberOfBytesRead) {
|
|
|
|
|
// Versuche die hRequestID zu finden
|
|
|
|
|
IMallocHashEntryType *pHashEntry;
|
|
|
|
|
*lpNumberOfBytesRead = 0;
|
|
|
|
|
|
|
|
|
|
pHashEntry = IMallocHashFind((PVOID) pData);
|
|
|
|
|
if (pHashEntry == (IMallocHashEntryType*) ALLOC_ENTRY_NOT_FOUND) {
|
|
|
|
|
// nicht gefunden, somit kann ich den Speicher nicht lesen
|
|
|
|
|
*lpNumberOfBytesRead = 0;
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
if ( ((DWORD) lpBaseAddress >= pHashEntry->dwESPOffset) && ((DWORD) lpBaseAddress <= (pHashEntry->dwESPOffset+pHashEntry->dwESPLen)) ) {
|
|
|
|
|
// Speicher liegt im ESP:
|
|
|
|
|
// Errechne den 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)) ) {
|
|
|
|
|
// Speicher liegt im EIP:
|
|
|
|
|
// Errechne den 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) // Der Speicher konnte nicht gefunden werden
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
// AllocHashOutLeaks
|
|
|
|
|
// Gibt allen Speicher aus, der noch nicht wieder freigegeben wurde
|
|
|
|
|
// Returns the number of bytes, that are not freed (leaks)
|
|
|
|
|
ULONG IMallocHashOutLeaks(FILE *fFile) {
|
|
|
|
|
ULONG ulTemp;
|
|
|
|
|
IMallocHashEntryType *pHashEntry;
|
|
|
|
|
ULONG ulCount = 0;
|
|
|
|
|
ULONG ulLeaksByte = 0;
|
|
|
|
|
|
|
|
|
|
// Gehe jeden Eintrag durch und gebe ihn aus
|
|
|
|
|
for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
|
|
|
|
|
pHashEntry = &IMallocHashTable[ulTemp];
|
|
|
|
|
if (pHashEntry->pData != 0) {
|
|
|
|
|
while(pHashEntry != NULL) {
|
|
|
|
|
// gebe die Zeile aus
|
|
|
|
|
if ( (pHashEntry->cRemovedFlag <= 0) || (pHashEntry->cRemovedFlag > 1) ) {
|
|
|
|
|
ulCount++;
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("<LEAK requestID=\"%u\" size=\"%u\">\n"), pHashEntry->pData, pHashEntry->nDataSize);
|
|
|
|
|
else
|
|
|
|
|
_ftprintf(fFile, _T("Pointer (RequestID): %12i, Removed: %i, Size: %12i\n"), pHashEntry->pData, pHashEntry->cRemovedFlag, pHashEntry->nDataSize);
|
|
|
|
|
CONTEXT c;
|
|
|
|
|
memset( &c, '\0', sizeof c );
|
|
|
|
|
c.Eip = pHashEntry->dwEIPOffset;
|
|
|
|
|
c.Ebp = pHashEntry->dwESPOffset;
|
|
|
|
|
ShowStackRM( NULL, c, fFile, &ReadProcMemoryFromIMallocHash, (HANDLE) pHashEntry->pData);
|
|
|
|
|
// Z<>hle zusammen wieviel Byte noch nicht freigegeben wurden
|
|
|
|
|
if (pHashEntry->nDataSize > 0)
|
|
|
|
|
ulLeaksByte += pHashEntry->nDataSize;
|
|
|
|
|
else
|
|
|
|
|
ulLeaksByte++; // Wenn zwar Speicher allokiert wurde, dieser aber 0 Bytes lang war, so reserviere f<>r diesen zumindest 1 Byte
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("</LEAK>\n")); // terminate the xml-node
|
|
|
|
|
}
|
|
|
|
|
pHashEntry = pHashEntry->Next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("\n**** Number of leaks: %i\n"), ulCount);
|
|
|
|
|
return ulLeaksByte;
|
|
|
|
|
} // AllocHashOutLeaks
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void AllocHashInit(void) {
|
|
|
|
|
|
|
|
|
|
memset(AllocHashTable, 0, sizeof(AllocHashTable));
|
|
|
|
|
AllocHashEntries = 0;
|
|
|
|
|
AllocHashCollisions = 0;
|
|
|
|
|
AllocHashFreed = 0;
|
|
|
|
|
AllocHashCurrentCount = 0;
|
|
|
|
|
AllocHashMaxUsed = 0;
|
|
|
|
|
|
|
|
|
|
AllocHashMaxCollisions = 0;
|
|
|
|
|
AllocHashCurrentCollisions = 0;
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_IMALLOC_SPY
|
|
|
|
|
memset(IMallocHashTable, 0, sizeof(IMallocHashTable));
|
|
|
|
|
IMallocHashEntries = 0;
|
|
|
|
|
IMallocHashCollisions = 0;
|
|
|
|
|
IMallocHashFreed = 0;
|
|
|
|
|
IMallocHashCurrentCount = 0;
|
|
|
|
|
IMallocHashMaxUsed = 0;
|
|
|
|
|
|
|
|
|
|
IMallocHashMaxCollisions = 0;
|
|
|
|
|
IMallocHashCurrentCollisions = 0;
|
|
|
|
|
#endif
|
|
|
|
|
return;
|
|
|
|
|
} // AllocHashInit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// AllocHashDeinit
|
|
|
|
|
// Returns the number of bytes, that are not freed (leaks)
|
|
|
|
|
static ULONG AllocHashDeinit(void) {
|
|
|
|
|
ULONG ulRet = 0;
|
|
|
|
|
bool bAppend = g_CallstackOutputType != ACOutput_XML;
|
|
|
|
|
AllocCheckFileOpen(bAppend); // open global log-file
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
{
|
|
|
|
|
_ftprintf(g_fFile, _T("<MEMREPORT "));
|
|
|
|
|
WriteDateTime(g_fFile, TRUE);
|
|
|
|
|
_ftprintf(g_fFile, _T(">\n"));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_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
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
_ftprintf(g_fFile, _T("##### Memory used: #########################################\n"));
|
|
|
|
|
AllocHashOut(g_fFile);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// output the Memoty leaks
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
_ftprintf(g_fFile, _T("\n##### Leaks: ###############################################\n"));
|
|
|
|
|
ulRet = AllocHashOutLeaks(g_fFile);
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
{
|
|
|
|
|
// 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 = 0, *pHashEntryOld = 0;
|
|
|
|
|
|
|
|
|
|
// 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));
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_IMALLOC_SPY
|
|
|
|
|
// output the Memoty leaks
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
_ftprintf(g_fFile, _T("\n##### COM-Leaks: ###############################################\n"));
|
|
|
|
|
ulRet = IMallocHashOutLeaks(g_fFile);
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
{
|
|
|
|
|
// 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"), IMallocHashEntries);
|
|
|
|
|
_ftprintf(g_fFile, _T(" Freed: %i\n"), IMallocHashFreed);
|
|
|
|
|
_ftprintf(g_fFile, _T(" Sum Collisions: %i\n"), IMallocHashCollisions);
|
|
|
|
|
_ftprintf(g_fFile, _T("\n"));
|
|
|
|
|
_ftprintf(g_fFile, _T(" Max used: %i\n"), IMallocHashMaxUsed);
|
|
|
|
|
_ftprintf(g_fFile, _T(" Max Collisions: %i\n"), IMallocHashMaxCollisions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Free Hash-Table
|
|
|
|
|
//ULONG ulTemp;
|
|
|
|
|
IMallocHashEntryType *pIMHashEntry, *pIMHashEntryOld;
|
|
|
|
|
|
|
|
|
|
// Gehe jeden Eintrag durch und gebe ihn frei
|
|
|
|
|
for(ulTemp = 0; ulTemp < ALLOC_HASH_ENTRIES; ulTemp++) {
|
|
|
|
|
pIMHashEntry = &IMallocHashTable[ulTemp];
|
|
|
|
|
while(pHashEntry != NULL) {
|
|
|
|
|
pIMHashEntryOld = pIMHashEntry;
|
|
|
|
|
pIMHashEntry = pIMHashEntry->Next;
|
|
|
|
|
if (pIMHashEntryOld != &IMallocHashTable[ulTemp]) {
|
|
|
|
|
// es ist dynamischer Speicher, gebe ihn also frei:
|
|
|
|
|
_free_dbg(pIMHashEntryOld, _CRT_BLOCK);
|
|
|
|
|
}
|
|
|
|
|
} // while
|
|
|
|
|
} // for
|
|
|
|
|
// L<>sche die gesamte Hash-Tabelle
|
|
|
|
|
memset(IMallocHashTable, 0, sizeof(IMallocHashTable));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(g_fFile, _T("</MEMREPORT>\n"));
|
|
|
|
|
|
|
|
|
|
return ulRet;
|
|
|
|
|
} // AllocHashDeinit
|
|
|
|
|
|
|
|
|
|
// AllocHashFunction
|
|
|
|
|
// The has-function (very simple)
|
|
|
|
|
static inline ULONG AllocHashFunction(long lRequestID) {
|
|
|
|
|
// I couldn<64>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
|
|
|
|
|
DWORD 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
|
|
|
|
|
AllocHashEntryType *pTmp = pHashEntry->Next;
|
|
|
|
|
*pHashEntry = *(pHashEntry->Next);
|
|
|
|
|
free(pTmp);
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
static BOOL __stdcall ReadProcMemoryFromHash(HANDLE hRequestID, DWORD64 lpBaseAddress, PVOID lpBuffer, DWORD nSize, PDWORD lpNumberOfBytesRead) {
|
|
|
|
|
// 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++;
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("<LEAK requestID=\"%u\" size=\"%u\">\n"), pHashEntry->lRequestID, pHashEntry->nDataSize);
|
|
|
|
|
else
|
|
|
|
|
_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
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("</LEAK>\n")); // terminate the xml-node
|
|
|
|
|
}
|
|
|
|
|
pHashEntry = pHashEntry->Next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
_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) {
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("<MEMUSED requestID=\"%u\" size=\"%u\"\n"), pHashEntry->lRequestID, pHashEntry->nDataSize);
|
|
|
|
|
else
|
|
|
|
|
_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;
|
|
|
|
|
|
|
|
|
|
static _CRT_ALLOC_HOOK pfnOldCrtAllocHook = NULL;
|
|
|
|
|
|
|
|
|
|
// 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!
|
|
|
|
|
// Special case for VC 5
|
|
|
|
|
#if _MSC_VER <= 1100
|
|
|
|
|
static int MyAllocHook(int nAllocType, void *pvData,
|
|
|
|
|
size_t nSize, int nBlockUse, long lRequest,
|
|
|
|
|
const char * szFileName, int nLine ) {
|
|
|
|
|
#else
|
|
|
|
|
static int MyAllocHook(int nAllocType, void *pvData,
|
|
|
|
|
size_t nSize, int nBlockUse, long lRequest,
|
|
|
|
|
const unsigned char * szFileName, int nLine ) {
|
|
|
|
|
#endif
|
|
|
|
|
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 (_BLOCK_TYPE(nBlockUse) == _CRT_BLOCK) // Ignore internal C runtime library allocations
|
|
|
|
|
return TRUE;
|
|
|
|
|
#endif
|
|
|
|
|
extern int _crtDbgFlag;
|
|
|
|
|
if ( ((_CRTDBG_ALLOC_MEM_DF & _crtDbgFlag) == 0) && ( (nAllocType == _HOOK_ALLOC) || (nAllocType == _HOOK_REALLOC) ) )
|
|
|
|
|
{
|
|
|
|
|
// Someone has disabled that the runtime should log this allocation
|
|
|
|
|
// so we do not log this allocation
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prevent from reentrat calls
|
|
|
|
|
if (InterlockedIncrement(&g_lMallocCalled) > 1) { // I was already called
|
|
|
|
|
InterlockedDecrement(&g_lMallocCalled);
|
|
|
|
|
// call the previous alloc hook
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_ulShowStackAtAlloc > 0) {
|
|
|
|
|
AllocCheckFileOpen(); // Open logfile
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ASSERT( (nAllocType == _HOOK_ALLOC) || (nAllocType == _HOOK_REALLOC) || (nAllocType == _HOOK_FREE) );
|
|
|
|
|
_ASSERT( ( _BLOCK_TYPE(nBlockUse) >= 0 ) && ( _BLOCK_TYPE(nBlockUse) < 5 ) );
|
|
|
|
|
|
|
|
|
|
if (nAllocType == _HOOK_FREE) { // 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 (pHead->nBlockUse == _IGNORE_BLOCK)
|
|
|
|
|
{
|
|
|
|
|
InterlockedDecrement(&g_lMallocCalled);
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_ulShowStackAtAlloc > 0) {
|
|
|
|
|
_ftprintf( g_fFile, _T("##### Memory operation: %s a %d-byte '%s' block (# %ld)"),
|
|
|
|
|
operation[nAllocType], nSize, blockType[_BLOCK_TYPE(nBlockUse)], lRequest );
|
|
|
|
|
if ( pvData != NULL )
|
|
|
|
|
_ftprintf( g_fFile, _T(" at 0x%X"), pvData );
|
|
|
|
|
_ftprintf(g_fFile, _T("\n"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nAllocType == _HOOK_FREE) { // 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 == _HOOK_REALLOC) { // 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 == _HOOK_FREE) ) {
|
|
|
|
|
InterlockedDecrement(&g_lMallocCalled);
|
|
|
|
|
// call the previous alloc hook
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
// call the previous alloc hook
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CONTEXT c;
|
|
|
|
|
memset( &c, '\0', sizeof c );
|
|
|
|
|
c.ContextFlags = CONTEXT_FULL;
|
|
|
|
|
|
|
|
|
|
// Get the context of this thread
|
|
|
|
|
#if 0
|
|
|
|
|
// init CONTEXT record so we know where to start the stackwalk
|
|
|
|
|
if ( MyGetCurrentThreadContext( hThread, &c ) == 0) {
|
|
|
|
|
if(g_ulShowStackAtAlloc > 1) {
|
|
|
|
|
_ftprintf(g_fFile, _T("###### Could not call 'GetThreadContext' successfully\n"));
|
|
|
|
|
}
|
|
|
|
|
InterlockedDecrement(&g_lMallocCalled);
|
|
|
|
|
// call the previous alloc hook
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
CloseHandle(hThread);
|
|
|
|
|
return TRUE; // could not get context
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
__asm
|
|
|
|
|
{
|
|
|
|
|
call x
|
|
|
|
|
x: pop eax
|
|
|
|
|
mov c.Eip, eax
|
|
|
|
|
mov c.Ebp, ebp
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if(g_ulShowStackAtAlloc > 1) {
|
|
|
|
|
if(g_ulShowStackAtAlloc > 2) {
|
|
|
|
|
// output the callstack
|
|
|
|
|
ShowStack( hThread, c, g_fFile);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Output only (re)allocs
|
|
|
|
|
if (nAllocType != _HOOK_FREE) {
|
|
|
|
|
ShowStack( hThread, c, g_fFile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // g_ulShowStackAtAlloc > 1
|
|
|
|
|
CloseHandle( hThread );
|
|
|
|
|
|
|
|
|
|
// Only isert in the Hash-Table if it is not a "freeing"
|
|
|
|
|
if (nAllocType != _HOOK_FREE) {
|
|
|
|
|
if(lRequest != 0) // Always a valid RequestID should be provided (see comments in the header)
|
|
|
|
|
AllocHashInsert(lRequest, c, nSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InterlockedDecrement(&g_lMallocCalled);
|
|
|
|
|
// call the previous alloc hook
|
|
|
|
|
if (pfnOldCrtAllocHook != NULL)
|
|
|
|
|
pfnOldCrtAllocHook(nAllocType, pvData, nSize, nBlockUse, lRequest, szFileName, nLine);
|
|
|
|
|
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_SYMBOL64 )
|
|
|
|
|
#define TTBUFLEN 8096 // for a temp buffer (2^13)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SymCleanup()
|
|
|
|
|
typedef BOOL (__stdcall *tSC)( IN HANDLE hProcess );
|
|
|
|
|
tSC pSC = NULL;
|
|
|
|
|
|
|
|
|
|
// SymFunctionTableAccess64()
|
|
|
|
|
typedef PVOID (__stdcall *tSFTA)( HANDLE hProcess, DWORD64 AddrBase );
|
|
|
|
|
tSFTA pSFTA = NULL;
|
|
|
|
|
|
|
|
|
|
// SymGetLineFromAddr64()
|
|
|
|
|
typedef BOOL (__stdcall *tSGLFA)( IN HANDLE hProcess, IN DWORD64 dwAddr,
|
|
|
|
|
OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line );
|
|
|
|
|
tSGLFA pSGLFA = NULL;
|
|
|
|
|
|
|
|
|
|
// SymGetModuleBase64()
|
|
|
|
|
typedef DWORD64 (__stdcall *tSGMB)( IN HANDLE hProcess, IN DWORD64 dwAddr );
|
|
|
|
|
tSGMB pSGMB = NULL;
|
|
|
|
|
|
|
|
|
|
// SymGetModuleInfo64()
|
|
|
|
|
typedef BOOL (__stdcall *tSGMI)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo );
|
|
|
|
|
tSGMI pSGMI = NULL;
|
|
|
|
|
|
|
|
|
|
// SymGetOptions()
|
|
|
|
|
typedef DWORD (__stdcall *tSGO)( VOID );
|
|
|
|
|
tSGO pSGO = NULL;
|
|
|
|
|
|
|
|
|
|
// SymGetSymFromAddr64()
|
|
|
|
|
typedef BOOL (__stdcall *tSGSFA)( IN HANDLE hProcess, IN DWORD64 dwAddr,
|
|
|
|
|
OUT PDWORD64 pdwDisplacement, OUT PIMAGEHLP_SYMBOL64 Symbol );
|
|
|
|
|
tSGSFA pSGSFA = NULL;
|
|
|
|
|
|
|
|
|
|
// SymInitialize()
|
|
|
|
|
typedef BOOL (__stdcall *tSI)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess );
|
|
|
|
|
tSI pSI = NULL;
|
|
|
|
|
|
|
|
|
|
// SymLoadModule64()
|
|
|
|
|
typedef DWORD (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile,
|
|
|
|
|
IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll );
|
|
|
|
|
tSLM pSLM = NULL;
|
|
|
|
|
|
|
|
|
|
// SymSetOptions()
|
|
|
|
|
typedef DWORD (__stdcall *tSSO)( IN DWORD SymOptions );
|
|
|
|
|
tSSO pSSO = NULL;
|
|
|
|
|
|
|
|
|
|
// StackWalk64()
|
|
|
|
|
typedef BOOL (__stdcall *tSW)(
|
|
|
|
|
DWORD MachineType,
|
|
|
|
|
HANDLE hProcess,
|
|
|
|
|
HANDLE hThread,
|
|
|
|
|
LPSTACKFRAME64 StackFrame,
|
|
|
|
|
PVOID ContextRecord,
|
|
|
|
|
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
|
|
|
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
|
|
|
|
|
PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
|
|
|
|
|
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress );
|
|
|
|
|
tSW pSW = NULL;
|
|
|
|
|
|
|
|
|
|
// UnDecorateSymbolName()
|
|
|
|
|
typedef DWORD (__stdcall WINAPI *tUDSN)( PCSTR DecoratedName, PSTR UnDecoratedName,
|
|
|
|
|
DWORD UndecoratedLength, DWORD Flags );
|
|
|
|
|
tUDSN pUDSN = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct ModuleEntry
|
|
|
|
|
{
|
|
|
|
|
std::string imageName;
|
|
|
|
|
std::string moduleName;
|
|
|
|
|
DWORD baseAddress;
|
|
|
|
|
DWORD size;
|
|
|
|
|
};
|
|
|
|
|
typedef std::vector< ModuleEntry > ModuleList;
|
|
|
|
|
typedef ModuleList::iterator ModuleListIter;
|
|
|
|
|
|
|
|
|
|
// **************************************** ToolHelp32 ************************
|
|
|
|
|
#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 GetModuleListTH32(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);
|
|
|
|
|
|
|
|
|
|
// try both dlls...
|
|
|
|
|
const TCHAR *dllname[] = { _T("kernel32.dll"), _T("tlhelp32.dll") };
|
|
|
|
|
HINSTANCE hToolhelp;
|
|
|
|
|
tCT32S pCT32S;
|
|
|
|
|
tM32F pM32F;
|
|
|
|
|
tM32N pM32N;
|
|
|
|
|
|
|
|
|
|
HANDLE hSnap;
|
|
|
|
|
MODULEENTRY32 me;
|
|
|
|
|
me.dwSize = sizeof(me);
|
|
|
|
|
bool keepGoing;
|
|
|
|
|
ModuleEntry e;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i<lenof(dllname); i++ )
|
|
|
|
|
{
|
|
|
|
|
hToolhelp = LoadLibrary( dllname[i] );
|
|
|
|
|
if (hToolhelp == NULL)
|
|
|
|
|
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 = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hToolhelp == NULL)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
hSnap = pCT32S( TH32CS_SNAPMODULE, pid );
|
|
|
|
|
if (hSnap == (HANDLE) -1)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
keepGoing = !!pM32F( hSnap, &me );
|
|
|
|
|
while (keepGoing)
|
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
} // GetModuleListTH32
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// **************************************** PSAPI ************************
|
|
|
|
|
typedef struct _MODULEINFO {
|
|
|
|
|
LPVOID lpBaseOfDll;
|
|
|
|
|
DWORD SizeOfImage;
|
|
|
|
|
LPVOID EntryPoint;
|
|
|
|
|
} MODULEINFO, *LPMODULEINFO;
|
|
|
|
|
|
|
|
|
|
static bool GetModuleListPSAPI(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()
|
|
|
|
|
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 )
|
|
|
|
|
{
|
|
|
|
|
// we couldn<64>t find all functions
|
|
|
|
|
FreeLibrary( hPsapi );
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hMods = (HMODULE*) malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof HMODULE));
|
|
|
|
|
tt = (char*) malloc(sizeof(char) * TTBUFLEN);
|
|
|
|
|
|
|
|
|
|
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++ )
|
|
|
|
|
{
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
modules.push_back(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
if (hPsapi)
|
|
|
|
|
FreeLibrary(hPsapi);
|
|
|
|
|
free(tt);
|
|
|
|
|
free(hMods);
|
|
|
|
|
|
|
|
|
|
return modules.size() != 0;
|
|
|
|
|
} // GetModuleListPSAPI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool GetModuleList(ModuleList& modules, DWORD pid, HANDLE hProcess, FILE *fLogFile)
|
|
|
|
|
{
|
|
|
|
|
// first try toolhelp32
|
|
|
|
|
if (GetModuleListTH32(modules, pid, fLogFile) )
|
|
|
|
|
return true;
|
|
|
|
|
// then try psapi
|
|
|
|
|
return GetModuleListPSAPI(modules, pid, hProcess, fLogFile);
|
|
|
|
|
} // GetModuleList
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void EnumAndLoadModuleSymbols( HANDLE hProcess, DWORD pid, FILE *fLogFile )
|
|
|
|
|
{
|
|
|
|
|
static ModuleList modules;
|
|
|
|
|
static ModuleListIter it;
|
|
|
|
|
char *img, *mod;
|
|
|
|
|
|
|
|
|
|
// fill in module list
|
|
|
|
|
GetModuleList(modules, pid, hProcess, fLogFile);
|
|
|
|
|
|
|
|
|
|
for ( it = modules.begin(); it != modules.end(); ++ it )
|
|
|
|
|
{
|
|
|
|
|
// SymLoadModule() wants writeable strings
|
|
|
|
|
img = strdup(it->imageName.c_str());
|
|
|
|
|
mod = strdup(it->moduleName.c_str());
|
|
|
|
|
|
|
|
|
|
pSLM( hProcess, 0, img, mod, it->baseAddress, it->size );
|
|
|
|
|
|
|
|
|
|
free(img);
|
|
|
|
|
free(mod);
|
|
|
|
|
std::string s;
|
|
|
|
|
}
|
|
|
|
|
} // EnumAndLoadModuleSymbols
|
|
|
|
|
|
|
|
|
|
static int InitStackWalk(void)
|
|
|
|
|
{
|
|
|
|
|
if (g_bInitialized != FALSE)
|
|
|
|
|
return 0; // already initialized
|
|
|
|
|
|
|
|
|
|
// 02-12-19: Now we only support dbghelp.dll!
|
|
|
|
|
// To use it on NT you have to install the redistrubutable for DBGHELP.DLL
|
|
|
|
|
g_hImagehlpDll = LoadLibrary( _T("dbghelp.dll") );
|
|
|
|
|
if ( g_hImagehlpDll == NULL )
|
|
|
|
|
{
|
|
|
|
|
printf( "LoadLibrary( \"dbghelp.dll\" ): GetLastError = %lu\n", gle );
|
|
|
|
|
g_bInitialized = FALSE;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now we only support the newer dbghlp.dll with the "64"-functions (StackWalk64, a.s.o.)
|
|
|
|
|
// If your dbghlp.dll does not support this, please download the redistributable from MS
|
|
|
|
|
// Normally from: http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=CD1FC4B2-0885-47F4-AF45-7FD5E14DB6C0
|
|
|
|
|
|
|
|
|
|
pSC = (tSC) GetProcAddress( g_hImagehlpDll, "SymCleanup" );
|
|
|
|
|
pSFTA = (tSFTA) GetProcAddress( g_hImagehlpDll, "SymFunctionTableAccess64" );
|
|
|
|
|
pSGLFA = (tSGLFA) GetProcAddress( g_hImagehlpDll, "SymGetLineFromAddr64" );
|
|
|
|
|
pSGMB = (tSGMB) GetProcAddress( g_hImagehlpDll, "SymGetModuleBase64" );
|
|
|
|
|
pSGMI = (tSGMI) GetProcAddress( g_hImagehlpDll, "SymGetModuleInfo64" );
|
|
|
|
|
pSGO = (tSGO) GetProcAddress( g_hImagehlpDll, "SymGetOptions" );
|
|
|
|
|
pSGSFA = (tSGSFA) GetProcAddress( g_hImagehlpDll, "SymGetSymFromAddr64" );
|
|
|
|
|
pSI = (tSI) GetProcAddress( g_hImagehlpDll, "SymInitialize" );
|
|
|
|
|
pSSO = (tSSO) GetProcAddress( g_hImagehlpDll, "SymSetOptions" );
|
|
|
|
|
pSW = (tSW) GetProcAddress( g_hImagehlpDll, "StackWalk64" );
|
|
|
|
|
pUDSN = (tUDSN) GetProcAddress( g_hImagehlpDll, "UnDecorateSymbolName" );
|
|
|
|
|
pSLM = (tSLM) GetProcAddress( g_hImagehlpDll, "SymLoadModule64" );
|
|
|
|
|
|
|
|
|
|
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(eAllocCheckOutput eOutput, LPCTSTR pszFileName, ULONG ulShowStackAtAlloc) {
|
|
|
|
|
if (g_bInitialized) {
|
|
|
|
|
return 2; // already initialized!
|
|
|
|
|
}
|
|
|
|
|
if (ulShowStackAtAlloc <= 3)
|
|
|
|
|
g_ulShowStackAtAlloc = ulShowStackAtAlloc;
|
|
|
|
|
else
|
|
|
|
|
g_ulShowStackAtAlloc = 0;
|
|
|
|
|
|
|
|
|
|
if (pszFileName != NULL)
|
|
|
|
|
g_pszAllocLogName = _tcsdup(pszFileName);
|
|
|
|
|
else
|
|
|
|
|
g_pszAllocLogName = NULL;
|
|
|
|
|
|
|
|
|
|
g_CallstackOutputType = eOutput;
|
|
|
|
|
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
AllocHashInit();
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_IMALLOC_SPY
|
|
|
|
|
HRESULT hr;
|
|
|
|
|
// erzeuge mein malloc-Spy object
|
|
|
|
|
LPMALLOCSPY pMallocSpy = new CMallocSpy(); // wird sp<73>ter durch Release freigegeben
|
|
|
|
|
if (pMallocSpy != NULL)
|
|
|
|
|
{
|
|
|
|
|
// CoInitilize(); // ??? Ist dies notwendig ?
|
|
|
|
|
hr = CoRegisterMallocSpy(pMallocSpy);
|
|
|
|
|
if FAILED(hr)
|
|
|
|
|
{
|
|
|
|
|
_tprintf(_T("\nCoRegisterMallocSpay failed with %.8x"), hr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// save the previous alloc hook
|
|
|
|
|
pfnOldCrtAllocHook = _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)
|
|
|
|
|
{
|
|
|
|
|
if (pExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
|
|
|
|
|
{
|
|
|
|
|
static char MyStack[1024*128]; // be sure that we have enought space...
|
|
|
|
|
// it assumes that DS and SS are the same!!! (this is the case for Win32)
|
|
|
|
|
// change the stack only if the selectors are the same (this is the case for Win32)
|
|
|
|
|
//__asm push offset MyStack[1024*128];
|
|
|
|
|
//__asm pop esp;
|
|
|
|
|
__asm mov eax,offset MyStack[1024*128];
|
|
|
|
|
__asm mov esp,eax;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(eAllocCheckOutput eOutput, BOOL bSetUnhandledExeptionFilter, ULONG ulShowStackAtAlloc) // will create the filename by itself
|
|
|
|
|
{
|
|
|
|
|
TCHAR szModName[_MAX_PATH];
|
|
|
|
|
if (GetModuleFileName(NULL, szModName, sizeof(szModName)/sizeof(TCHAR)) != 0)
|
|
|
|
|
{
|
|
|
|
|
_tcscpy(s_szExceptionLogFileName, szModName);
|
|
|
|
|
if (eOutput == ACOutput_XML)
|
|
|
|
|
_tcscat(s_szExceptionLogFileName, _T(".exp.xml"));
|
|
|
|
|
else
|
|
|
|
|
_tcscat(s_szExceptionLogFileName, _T(".exp.log"));
|
|
|
|
|
|
|
|
|
|
if (eOutput == ACOutput_XML)
|
|
|
|
|
_tcscat(szModName, _T(".mem.xml-leaks"));
|
|
|
|
|
else
|
|
|
|
|
_tcscat(szModName, _T(".mem.log"));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (eOutput == ACOutput_XML)
|
|
|
|
|
_tcscpy(szModName, _T("\\mem-leaks.xml-leaks")); // default
|
|
|
|
|
else
|
|
|
|
|
_tcscpy(szModName, _T("\\mem-leaks.log")); // default
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((bSetUnhandledExeptionFilter != FALSE) && (s_bUnhandledExeptionFilterSet == FALSE) )
|
|
|
|
|
{
|
|
|
|
|
// set global exception handler (for handling all unhandled exceptions)
|
|
|
|
|
SetUnhandledExceptionFilter(CrashHandlerExceptionFilter);
|
|
|
|
|
s_bUnhandledExeptionFilterSet = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return InitAllocCheckWN(eOutput, szModName, ulShowStackAtAlloc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
InterlockedIncrement(&g_lMallocCalled); // No deactivate MyAllocHook, because StackWalker will allocate some memory)
|
|
|
|
|
ulRet = AllocHashDeinit(); // output the not freed memory
|
|
|
|
|
// remove the hook and set the old one
|
|
|
|
|
_CrtSetAllocHook(pfnOldCrtAllocHook);
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_IMALLOC_SPY
|
|
|
|
|
CoRevokeMallocSpy();
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DeleteCriticalSection(&g_csFileOpenClose);
|
|
|
|
|
InterlockedDecrement(&g_lMallocCalled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s_bUnhandledExeptionFilterSet != TRUE)
|
|
|
|
|
{
|
|
|
|
|
SetUnhandledExceptionFilter(NULL);
|
|
|
|
|
s_bUnhandledExeptionFilterSet = FALSE;
|
|
|
|
|
}
|
|
|
|
|
return ulRet;
|
|
|
|
|
} // DeInitAllocCheck
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void OnlyInstallUnhandeldExceptionFilter(eAllocCheckOutput eOutput)
|
|
|
|
|
{
|
|
|
|
|
if (s_bUnhandledExeptionFilterSet == FALSE)
|
|
|
|
|
{
|
|
|
|
|
TCHAR szModName[_MAX_PATH];
|
|
|
|
|
if (GetModuleFileName(NULL, szModName, sizeof(szModName)/sizeof(TCHAR)) != 0)
|
|
|
|
|
{
|
|
|
|
|
_tcscpy(s_szExceptionLogFileName, szModName);
|
|
|
|
|
if (eOutput == ACOutput_XML)
|
|
|
|
|
_tcscat(s_szExceptionLogFileName, _T(".exp.xml"));
|
|
|
|
|
else
|
|
|
|
|
_tcscat(s_szExceptionLogFileName, _T(".exp.log"));
|
|
|
|
|
|
|
|
|
|
if (eOutput == ACOutput_XML)
|
|
|
|
|
_tcscat(szModName, _T(".mem.xml-leaks"));
|
|
|
|
|
else
|
|
|
|
|
_tcscat(szModName, _T(".mem.log"));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (eOutput == ACOutput_XML)
|
|
|
|
|
_tcscpy(szModName, _T("\\mem-leaks.xml-leaks")); // default
|
|
|
|
|
else
|
|
|
|
|
_tcscpy(szModName, _T("\\mem-leaks.log")); // default
|
|
|
|
|
}
|
|
|
|
|
// set it again; WARNING: this will override the setting for a possible AllocCheck-Setting
|
|
|
|
|
g_CallstackOutputType = eOutput;
|
|
|
|
|
|
|
|
|
|
// set global exception handler (for handling all unhandled exceptions)
|
|
|
|
|
SetUnhandledExceptionFilter(CrashHandlerExceptionFilter);
|
|
|
|
|
s_bUnhandledExeptionFilterSet = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
std::string SimpleXMLEncode(PCSTR szText)
|
|
|
|
|
{
|
|
|
|
|
std::string szRet;
|
|
|
|
|
|
|
|
|
|
for (size_t i=0; i<strlen(szText); i++)
|
|
|
|
|
{
|
|
|
|
|
switch(szText[i])
|
|
|
|
|
{
|
|
|
|
|
case '&':
|
|
|
|
|
szRet.append("&");
|
|
|
|
|
break;
|
|
|
|
|
case '<':
|
|
|
|
|
szRet.append("<");
|
|
|
|
|
break;
|
|
|
|
|
case '>':
|
|
|
|
|
szRet.append(">");
|
|
|
|
|
break;
|
|
|
|
|
case '"':
|
|
|
|
|
szRet.append(""");
|
|
|
|
|
break;
|
|
|
|
|
case '\'':
|
|
|
|
|
szRet.append("'");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
szRet += szText[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return szRet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// #################################################################################
|
|
|
|
|
// #################################################################################
|
|
|
|
|
// Here the Stackwalk-Part begins.
|
|
|
|
|
// Some of the code is from an example from a book
|
|
|
|
|
// But I couldn<64>t find the reference anymore... sorry...
|
|
|
|
|
// If someone knowns, please let me know...
|
|
|
|
|
// #################################################################################
|
|
|
|
|
// #################################################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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, LPCTSTR 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
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
{
|
|
|
|
|
_ftprintf(fFile, _T("<EXCEPTION code=\"%8.8X\" addr=\"%8.8X\" "),
|
|
|
|
|
ep->ExceptionRecord->ExceptionCode,
|
|
|
|
|
ep->ExceptionRecord->ExceptionAddress);
|
|
|
|
|
WriteDateTime(fFile, TRUE);
|
|
|
|
|
_ftprintf(fFile, _T("code_desc=\"%s\" more_desc=\"%s\">\n"), GetExpectionCodeText(ep->ExceptionRecord->ExceptionCode),
|
|
|
|
|
GetAdditionalExpectionCodeText(ep->ExceptionRecord));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_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 );
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
_ftprintf(fFile, _T("</EXCEPTION>\n"));
|
|
|
|
|
|
|
|
|
|
fclose(fFile);
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
} // StackwalkFilter
|
|
|
|
|
|
|
|
|
|
void ShowStack( HANDLE hThread, CONTEXT& c, LPCTSTR 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_ROUTINE64 ReadMemoryFunction, HANDLE hSWProcess) {
|
|
|
|
|
// normally, call ImageNtHeader() and use machine info from PE header
|
|
|
|
|
// but we assume that it is an I386 image...
|
|
|
|
|
DWORD imageType = IMAGE_FILE_MACHINE_I386;
|
|
|
|
|
HANDLE hProcess = GetCurrentProcess(); // hProcess normally comes from outside but we only do the stackdump in our own process
|
|
|
|
|
int frameNum; // counts walked frames
|
|
|
|
|
DWORD64 offsetFromSymbol; // tells us how far from the symbol we were
|
|
|
|
|
DWORD offsetFromLine; // tells us how far from the line we were
|
|
|
|
|
DWORD symOptions; // symbol handler settings
|
|
|
|
|
|
|
|
|
|
static IMAGEHLP_SYMBOL64 *pSym = NULL;
|
|
|
|
|
char undName[MAXNAMELEN]; // undecorated name
|
|
|
|
|
char undFullName[MAXNAMELEN]; // undecorated name with all shenanigans
|
|
|
|
|
IMAGEHLP_MODULE64 Module;
|
|
|
|
|
IMAGEHLP_LINE64 Line;
|
|
|
|
|
BOOL bXMLTagWrote;
|
|
|
|
|
|
|
|
|
|
std::string symSearchPath;
|
|
|
|
|
|
|
|
|
|
static int bFirstTime = TRUE;
|
|
|
|
|
|
|
|
|
|
// If no logfile is present, outpur to "stdout"
|
|
|
|
|
if (fLogFile == NULL) {
|
|
|
|
|
fLogFile = stdout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
STACKFRAME64 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<72>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_SYMBOL64 *) malloc( IMGSYMLEN + MAXNAMELEN );
|
|
|
|
|
if (!pSym) goto cleanup; // not enough memory...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
{
|
|
|
|
|
_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
|
2011-02-14 00:27:05 +00:00
|
|
|
|
if ( *p == GOLD_SLASH_CHR || *p == GOLD_WRONG_SLASH_CHR || *p == ':' )
|
2007-01-08 09:41:15 +00:00
|
|
|
|
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 ) )
|
|
|
|
|
{
|
|
|
|
|
if (g_CallstackOutputType != ACOutput_XML)
|
|
|
|
|
_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 dbghlp.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;
|
|
|
|
|
s.AddrStack.Offset = c.Ebp;
|
|
|
|
|
s.AddrStack.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;
|
|
|
|
|
|
|
|
|
|
for ( frameNum = 0; ; ++ frameNum )
|
|
|
|
|
{
|
|
|
|
|
// get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64())
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
bXMLTagWrote = FALSE;
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
_ftprintf(fLogFile, _T("\n%lu: %3d"), g_dwShowCount, frameNum);
|
|
|
|
|
if ( s.AddrPC.Offset == 0 )
|
|
|
|
|
{
|
|
|
|
|
// Special case: If we are here, we have no valid callstack entry!
|
|
|
|
|
switch(g_CallstackOutputType)
|
|
|
|
|
{
|
|
|
|
|
case ACOutput_Simple:
|
|
|
|
|
_ftprintf(fLogFile, _T("%lu: (-nosymbols- PC == 0)\n"), g_dwShowCount);
|
|
|
|
|
break;
|
|
|
|
|
case ACOutput_Advanced:
|
|
|
|
|
_ftprintf(fLogFile, _T(" (-nosymbols- PC == 0)\n"));
|
|
|
|
|
break;
|
|
|
|
|
case ACOutput_XML:
|
|
|
|
|
// TODO: ....
|
|
|
|
|
_ftprintf(fLogFile, _T("<STACKENTRY decl=\"(-nosymbols- PC == 0)\"/>\n"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// we seem to have a valid PC
|
|
|
|
|
undName[0] = 0;
|
|
|
|
|
undFullName[0] = 0;
|
|
|
|
|
offsetFromSymbol = 0;
|
|
|
|
|
// show procedure info (SymGetSymFromAddr())
|
|
|
|
|
if ( ! pSGSFA( hProcess, s.AddrPC.Offset, &offsetFromSymbol, pSym ) )
|
|
|
|
|
{
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
{
|
|
|
|
|
if ( gle != 487 )
|
|
|
|
|
_ftprintf(fLogFile, _T(" SymGetSymFromAddr(): GetLastError = %lu\n"), gle );
|
|
|
|
|
else
|
|
|
|
|
_ftprintf(fLogFile, _T("\n"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// UnDecorateSymbolName()
|
|
|
|
|
pUDSN( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY );
|
|
|
|
|
pUDSN( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE );
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
{
|
|
|
|
|
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 );
|
|
|
|
|
strcpy(undName, pSym->Name);
|
|
|
|
|
}
|
|
|
|
|
fprintf(fLogFile, "%lu: Decl: %s\n", g_dwShowCount, undFullName );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
// fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
|
|
|
|
|
|
|
|
|
|
// show line number info, NT5.0-method (SymGetLineFromAddr())
|
|
|
|
|
offsetFromLine = 0;
|
|
|
|
|
if ( pSGLFA != NULL )
|
|
|
|
|
{ // yes, we have SymGetLineFromAddr()
|
|
|
|
|
if ( ! pSGLFA( hProcess, s.AddrPC.Offset, &offsetFromLine, &Line ) )
|
|
|
|
|
{
|
|
|
|
|
if ( (gle != 487) && (frameNum > 0) ) // ignore error for first frame
|
|
|
|
|
{
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
{
|
|
|
|
|
_ftprintf(fLogFile, _T("<STACKENTRY "));
|
|
|
|
|
bXMLTagWrote = TRUE;
|
|
|
|
|
fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
|
|
|
|
|
_ftprintf(fLogFile, _T("srcfile=\"SymGetLineFromAddr(): GetLastError = %lu\" "), gle);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
_ftprintf(fLogFile, _T("%lu: SymGetLineFromAddr(): GetLastError = %lu\n"), g_dwShowCount, gle );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
switch(g_CallstackOutputType)
|
|
|
|
|
{
|
|
|
|
|
case ACOutput_Advanced:
|
|
|
|
|
fprintf(fLogFile, "%lu: Line: %s(%lu) %+ld bytes\n", g_dwShowCount,
|
|
|
|
|
Line.FileName, Line.LineNumber, offsetFromLine );
|
|
|
|
|
break;
|
|
|
|
|
case ACOutput_Simple:
|
|
|
|
|
fprintf(fLogFile, "%lu: %s(%lu) %+ld bytes (%s)\n", g_dwShowCount,
|
|
|
|
|
Line.FileName, Line.LineNumber, offsetFromLine, undName);
|
|
|
|
|
break;
|
|
|
|
|
case ACOutput_XML:
|
|
|
|
|
_ftprintf(fLogFile, _T("<STACKENTRY "));
|
|
|
|
|
bXMLTagWrote = TRUE;
|
|
|
|
|
fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
|
|
|
|
|
fprintf(fLogFile, "srcfile=\"%s\" line=\"%lu\" line_offset=\"%+ld\" ",
|
|
|
|
|
SimpleXMLEncode(Line.FileName).c_str(), Line.LineNumber, offsetFromLine, undName);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // yes, we have SymGetLineFromAddr()
|
|
|
|
|
|
|
|
|
|
// show module info (SymGetModuleInfo())
|
|
|
|
|
if ( (g_CallstackOutputType == ACOutput_Advanced) || (g_CallstackOutputType == ACOutput_XML) )
|
|
|
|
|
{
|
|
|
|
|
if ( ! pSGMI( hProcess, s.AddrPC.Offset, &Module ) )
|
|
|
|
|
{
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
_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;
|
|
|
|
|
#if API_VERSION_NUMBER >= 9
|
|
|
|
|
case SymDia:
|
|
|
|
|
strcpy( ty, "DIA" );
|
|
|
|
|
break;
|
|
|
|
|
#endif
|
|
|
|
|
default:
|
|
|
|
|
_snprintf( ty, sizeof ty, "symtype=%ld", (long) Module.SymType );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_XML)
|
|
|
|
|
{
|
|
|
|
|
// now, check if the XML-Entry is written...
|
|
|
|
|
if (bXMLTagWrote == FALSE)
|
|
|
|
|
{
|
|
|
|
|
_ftprintf(fLogFile, _T("<STACKENTRY "));
|
|
|
|
|
bXMLTagWrote = TRUE;
|
|
|
|
|
fprintf(fLogFile, "decl=\"%s\" decl_offset=\"%+ld\" ", SimpleXMLEncode(undName).c_str(), (long) offsetFromSymbol);
|
|
|
|
|
_ftprintf(fLogFile, _T("srcfile=\"\" "));
|
|
|
|
|
bXMLTagWrote = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (g_CallstackOutputType == ACOutput_Advanced)
|
|
|
|
|
{
|
|
|
|
|
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 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// XML:
|
|
|
|
|
if (bXMLTagWrote == TRUE)
|
|
|
|
|
fprintf(fLogFile, "module=\"%s\" base=\"%08lx\" ", Module.ModuleName, Module.BaseOfImage);
|
|
|
|
|
}
|
|
|
|
|
} // got module info OK
|
|
|
|
|
}
|
|
|
|
|
if ( (g_CallstackOutputType == ACOutput_XML) && (bXMLTagWrote == TRUE) )
|
|
|
|
|
_ftprintf(fLogFile, _T("/>\n")); // terminate the XML node
|
|
|
|
|
|
|
|
|
|
} // 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 ( (g_CallstackOutputType != ACOutput_XML) && (gle != 0) )
|
|
|
|
|
_ftprintf(fLogFile, _T("\n%lu: StackWalk(): GetLastError = %lu\n"), g_dwShowCount, gle );
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
//if (pSym) free( pSym );
|
|
|
|
|
if (fLogFile) {
|
|
|
|
|
_ftprintf(fLogFile, _T("\n\n"));
|
|
|
|
|
if (g_dwShowCount % 1000)
|
|
|
|
|
fflush(fLogFile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LeaveCriticalSection(&g_csFileOpenClose);
|
|
|
|
|
// Critical section end...
|
|
|
|
|
} // ShowStackRM
|