diff options
Diffstat (limited to 'src/plugins/dbghelpplug.c')
-rw-r--r-- | src/plugins/dbghelpplug.c | 1895 |
1 files changed, 1895 insertions, 0 deletions
diff --git a/src/plugins/dbghelpplug.c b/src/plugins/dbghelpplug.c new file mode 100644 index 000000000..567291bc5 --- /dev/null +++ b/src/plugins/dbghelpplug.c @@ -0,0 +1,1895 @@ +/* + * 2008 January 19 + * + * The author disclaims copyright to this source code. In place of + * a legal notice, here is a blessing: + * + * May you do good and not evil. + * May you find forgiveness for yourself and forgive others. + * May you share freely, never taking more than you give. + * + ******************************************************************** + * + * DONE: + * - Command line + * - Windows version + * - Exception + * - Registers + * - Stacktrace (frame number starting at 0) + * + Functions: + * - address + * - name + * - offset in the function + * - line number and source file + * - source code of the line (external source) + * - function parameters + * - local function variables + * + Variables/parameters: + * - variable name + * - variable type (char/wchar, integers, floats, enums, arrays, + * pointers, structs, unions, ...) + * - readability of memory + * - value of char/wchar + * - value of integers + * - value of floats + * - value of enums (hex number) + * - values of arrays + * - address of pointers + * - value of simple pointers (not pointing to another pointer) + * - show (char*) and (wchar*) as nul-terminated strings + * - show chars/wchar escaped + * + Modules: + * - base address + * - file + * - version + * - size + * + * TODO: + * + Variables/parameters: + * - structure members + * - union members + * - Portability to MinGW + * + * $Id$ + */ + +#ifdef _WIN32 + + +///////////////////////////////////////////////////////////////////// +// Include files +// + +#include <assert.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#define _NO_CVCONST_H +#include <dbghelp.h> + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + + +///////////////////////////////////////////////////////////////////// +// Types from Cvconst.h (DIA SDK) +// + +#ifdef _NO_CVCONST_H + +typedef enum _BasicType +{ + btNoType = 0, + btVoid = 1, + btChar = 2, + btWChar = 3, + btInt = 6, + btUInt = 7, + btFloat = 8, + btBCD = 9, + btBool = 10, + btLong = 13, + btULong = 14, + btCurrency = 25, + btDate = 26, + btVariant = 27, + btComplex = 28, + btBit = 29, + btBSTR = 30, + btHresult = 31 +} BasicType; + +typedef enum _UdtKind +{ + UdtStruct, + UdtClass, + UdtUnion +} UdtKind; + +typedef enum _SymTag { + SymTagNull = 0, + SymTagExe = 1, + SymTagCompiland = 2, + SymTagCompilandDetails = 3, + SymTagCompilandEnv = 4, + SymTagFunction = 5, + SymTagBlock = 6, + SymTagData = 7, + SymTagAnnotation = 8, + SymTagLabel = 9, + SymTagPublicSymbol = 10, + SymTagUDT = 11, + SymTagEnum = 12, + SymTagFunctionType = 13, + SymTagPointerType = 14, + SymTagArrayType = 15, + SymTagBaseType = 16, + SymTagTypedef = 17, + SymTagBaseClass = 18, + SymTagFriend = 19, + SymTagFunctionArgType = 20, + SymTagFuncDebugStart = 21, + SymTagFuncDebugEnd = 22, + SymTagUsingNamespace = 23, + SymTagVTableShape = 24, + SymTagVTable = 25, + SymTagCustom = 26, + SymTagThunk = 27, + SymTagCustomType = 28, + SymTagManagedType = 29, + SymTagDimension = 30 +} SymTag; + +#endif /* _NO_CVCONST_H */ + + +///////////////////////////////////////////////////////////////////// +// dbghelp function prototypes +// + +typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam +); +typedef BOOL (WINAPI *SYMINITIALIZE)( + HANDLE hProcess, + PSTR UserSearchPath, + BOOL fInvadeProcess +); +typedef DWORD (WINAPI *SYMSETOPTIONS)( + DWORD SymOptions +); +typedef DWORD (WINAPI *SYMGETOPTIONS)( + VOID +); +typedef BOOL (WINAPI *SYMCLEANUP)( + HANDLE hProcess +); +typedef BOOL (WINAPI *SYMGETTYPEINFO)( + HANDLE hProcess, + DWORD64 ModBase, + ULONG TypeId, + IMAGEHLP_SYMBOL_TYPE_INFO GetType, + PVOID pInfo +); +typedef BOOL (WINAPI *SYMGETLINEFROMADDR)( + HANDLE hProcess, + DWORD dwAddr, + PDWORD pdwDisplacement, + PIMAGEHLP_LINE Line +); +typedef BOOL (WINAPI *SYMENUMSYMBOLS)( + HANDLE hProcess, + ULONG64 BaseOfDll, + PCSTR Mask, + PSYM_ENUMERATESYMBOLS_CALLBACK EnumSymbolsCallback, + PVOID UserContext +); +typedef BOOL (WINAPI *SYMSETCONTEXT)( + HANDLE hProcess, + PIMAGEHLP_STACK_FRAME StackFrame, + PIMAGEHLP_CONTEXT Context +); +typedef BOOL (WINAPI *SYMFROMADDR)( + HANDLE hProcess, + DWORD64 Address, + PDWORD64 Displacement, + PSYMBOL_INFO Symbol +); +typedef BOOL (WINAPI *STACKWALK)( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE TranslateAddress +); +typedef PVOID (WINAPI *SYMFUNCTIONTABLEACCESS)( + HANDLE hProcess, + DWORD AddrBase +); +typedef DWORD (WINAPI *SYMGETMODULEBASE)( + HANDLE hProcess, + DWORD dwAddr +); + + +///////////////////////////////////////////////////////////////////// +// Custom info + +/// Internal structure used to pass some data around +typedef struct _InternalData { + // PrintStacktrace + FILE* log_file; + STACKFRAME* pStackframe; + HANDLE hProcess; + DWORD nr_of_frame; + + // PrintFunctionDetail + BOOL as_arg_list; + BOOL log_params; + BOOL log_locals; + BOOL log_globals; + DWORD nr_of_var; + + // PrintDataInfo + ULONG64 modBase; +} InterData; + +/// dbghelp dll filename +#define DBGHELP_DLL "dbghelp.dll" + +// Default report filename, used when the module path is unavailable +#define DBG_DEFAULT_FILENAME "athena" + +// Extended information printed in the console +#define DBG_EXTENDED_INFORMATION \ + "Please report the crash in the bug tracker:\n" \ + "http://www.eathena.ws/board/index.php?autocom=bugtracker\n" + + +///////////////////////////////////////////////////////////////////// +// Global variables + +HANDLE dbghelp_dll = INVALID_HANDLE_VALUE; +MINIDUMPWRITEDUMP MiniDumpWriteDump_ = NULL; +SYMINITIALIZE SymInitialize_ = NULL; +SYMSETOPTIONS SymSetOptions_ = NULL; +SYMGETOPTIONS SymGetOptions_ = NULL; +SYMCLEANUP SymCleanup_ = NULL; +SYMGETTYPEINFO SymGetTypeInfo_ = NULL; +SYMGETLINEFROMADDR SymGetLineFromAddr_ = NULL; +SYMENUMSYMBOLS SymEnumSymbols_ = NULL; +SYMSETCONTEXT SymSetContext_ = NULL; +SYMFROMADDR SymFromAddr_ = NULL; +STACKWALK StackWalk_ = NULL; +SYMFUNCTIONTABLEACCESS SymFunctionTableAccess_ = NULL; +SYMGETMODULEBASE SymGetModuleBase_ = NULL; + + + +///////////////////////////////////////////////////////////////////// +// Code + + +/// Writes the minidump to file. The callback function will at the +/// same time write the list of modules to the log file. +/// +/// @param file Filename of the minidump +/// @param ptrs Exception info +/// @param module_callback Callback for MiniDumpWriteDump +/// @param log_file Log file +static VOID +Dhp__WriteMinidumpFile( + const char* file, + PEXCEPTION_POINTERS ptrs, + MINIDUMP_CALLBACK_ROUTINE module_callback, + FILE* log_file) +{ + // open minidump file + HANDLE minidump_file = CreateFileA( + file, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL + ); + + if( minidump_file != INVALID_HANDLE_VALUE ) + { + MINIDUMP_EXCEPTION_INFORMATION expt_info; + MINIDUMP_CALLBACK_INFORMATION dump_cb_info; + + expt_info.ThreadId = GetCurrentThreadId(); + expt_info.ExceptionPointers = ptrs; + expt_info.ClientPointers = FALSE; + + dump_cb_info.CallbackRoutine = module_callback; + dump_cb_info.CallbackParam = (void*)log_file; + + if( module_callback != NULL && log_file != NULL ) + fprintf(log_file, "\n\nLoaded modules:\n"); + + MiniDumpWriteDump_( + GetCurrentProcess(), + GetCurrentProcessId(), + minidump_file, + MiniDumpNormal, + ptrs ? &expt_info : NULL, + NULL, + &dump_cb_info + ); + + CloseHandle(minidump_file); + } +} + + +/// Prints module information to the log file. +/// Used as a callback to MiniDumpWriteDump. +/// +/// @param data Log file +/// @param callback_input +/// @param callback_output +/// @return +static BOOL CALLBACK +Dhp__PrintModuleInfoCallback( + void* data, + CONST PMINIDUMP_CALLBACK_INPUT callback_input, + PMINIDUMP_CALLBACK_OUTPUT callback_output) +{ + if( data != NULL && + callback_input != NULL && + callback_input->CallbackType == ModuleCallback) + { + FILE* log_file = (FILE*)data; + MINIDUMP_MODULE_CALLBACK module = callback_input->Module; + + fprintf(log_file, "0x%p", module.BaseOfImage); + + fprintf(log_file, " %ws", module.FullPath, log_file); + + fprintf(log_file, " (%d.%d.%d.%d, %d bytes)\n", + HIWORD(module.VersionInfo.dwFileVersionMS), + LOWORD(module.VersionInfo.dwFileVersionMS), + HIWORD(module.VersionInfo.dwFileVersionLS), + LOWORD(module.VersionInfo.dwFileVersionLS), + module.SizeOfImage); + } + + return TRUE; +} + + +/// Prints details about the current process, platform and exception +/// information to the log file. +/// +/// @param exception Exception info +/// @param context Exception context +/// @param log_file Log file +static VOID +Dhp__PrintProcessInfo( + EXCEPTION_RECORD* exception, + CONTEXT* context, + FILE* log_file) +{ + OSVERSIONINFOA oi; + LPSTR cmd_line; + + fprintf(log_file, + "\nProcess info:\n"); + + // print the command line + cmd_line = GetCommandLineA(); + if( cmd_line ) + fprintf(log_file, + "Cmd line: %s\n", + cmd_line); + + // print information about the OS + oi.dwOSVersionInfoSize = sizeof(oi); + GetVersionExA(&oi); + fprintf(log_file, + "Platform: Windows OS version %d.%d build %d %s\n", + oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber, oi.szCSDVersion); + + // print the exception code + if( exception ) + { + fprintf(log_file, + "\nException:\n" + "0x%x", + exception->ExceptionCode); + switch( exception->ExceptionCode ) + { +#define PRINT(x) case x: fprintf(log_file, " "#x); break + PRINT(EXCEPTION_ACCESS_VIOLATION); + PRINT(EXCEPTION_DATATYPE_MISALIGNMENT); + PRINT(EXCEPTION_BREAKPOINT); + PRINT(EXCEPTION_SINGLE_STEP); + PRINT(EXCEPTION_ARRAY_BOUNDS_EXCEEDED); + PRINT(EXCEPTION_FLT_DENORMAL_OPERAND); + PRINT(EXCEPTION_FLT_DIVIDE_BY_ZERO); + PRINT(EXCEPTION_FLT_INEXACT_RESULT); + PRINT(EXCEPTION_FLT_INVALID_OPERATION); + PRINT(EXCEPTION_FLT_OVERFLOW); + PRINT(EXCEPTION_FLT_STACK_CHECK); + PRINT(EXCEPTION_FLT_UNDERFLOW); + PRINT(EXCEPTION_INT_DIVIDE_BY_ZERO); + PRINT(EXCEPTION_INT_OVERFLOW); + PRINT(EXCEPTION_PRIV_INSTRUCTION); + PRINT(EXCEPTION_IN_PAGE_ERROR); + PRINT(EXCEPTION_ILLEGAL_INSTRUCTION); + PRINT(EXCEPTION_NONCONTINUABLE_EXCEPTION); + PRINT(EXCEPTION_STACK_OVERFLOW); + PRINT(EXCEPTION_INVALID_DISPOSITION); + PRINT(EXCEPTION_GUARD_PAGE); + PRINT(EXCEPTION_INVALID_HANDLE); +#undef PRINT + } + + // print where the fault occured + fprintf(log_file, " at location 0x%p", exception->ExceptionAddress); + + // if the exception was an access violation, print additional information + if( exception->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && exception->NumberParameters >= 2 ) + fprintf(log_file, " %s location 0x%p", exception->ExceptionInformation[0] ? "writing to" : "reading from", exception->ExceptionInformation[1]); + + fprintf(log_file, "\n"); + } + + // print the register info + if( context ) + { +#if defined(_M_IX86) + fprintf(log_file, + "\nRegisters:\n"); + if( context->ContextFlags & CONTEXT_INTEGER ) + { + fprintf(log_file, + "eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n", + context->Eax, context->Ebx, context->Ecx, + context->Edx, context->Esi, context->Edi); + } + if( context->ContextFlags & CONTEXT_CONTROL ) + { + fprintf(log_file, + "eip=%08x esp=%08x ebp=%08x iopl=%1x %s %s %s %s %s %s %s %s %s %s\n", + context->Eip, context->Esp, context->Ebp, + (context->EFlags >> 12) & 3, // IOPL level value + context->EFlags & 0x00100000 ? "vip" : " ", // VIP (virtual interrupt pending) + context->EFlags & 0x00080000 ? "vif" : " ", // VIF (virtual interrupt flag) + context->EFlags & 0x00000800 ? "ov" : "nv", // VIF (virtual interrupt flag) + context->EFlags & 0x00000400 ? "dn" : "up", // OF (overflow flag) + context->EFlags & 0x00000200 ? "ei" : "di", // IF (interrupt enable flag) + context->EFlags & 0x00000080 ? "ng" : "pl", // SF (sign flag) + context->EFlags & 0x00000040 ? "zr" : "nz", // ZF (zero flag) + context->EFlags & 0x00000010 ? "ac" : "na", // AF (aux carry flag) + context->EFlags & 0x00000004 ? "po" : "pe", // PF (parity flag) + context->EFlags & 0x00000001 ? "cy" : "nc"); // CF (carry flag) + } + if( context->ContextFlags & CONTEXT_SEGMENTS ) + { + fprintf(log_file, + "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x", + context->SegCs, + context->SegSs, + context->SegDs, + context->SegEs, + context->SegFs, + context->SegGs, + context->EFlags); + if( context->ContextFlags & CONTEXT_CONTROL ) + fprintf(log_file, + " efl=%08x", + context->EFlags); + fprintf(log_file, "\n"); + } + else if( context->ContextFlags & CONTEXT_CONTROL ) + fprintf(log_file, + " efl=%08x\n", + context->EFlags); +#else /* defined(_M_IX86) */ + //TODO add more processors +#endif + } +} + + +/// Prints the typename of the symbol. +/// +/// @param typeIndex Type index of the symbol +/// @param symtag Symbol tag +/// @param withParens If brackets are printed around the typename +/// @param interData Inter data +static VOID +Dhp__PrintTypeName( + DWORD typeIndex, + DWORD symtag, + BOOL withParens, + InterData* interData) +{ + // inter data + FILE* log_file; + HANDLE hProcess; + ULONG64 modBase; + // + assert( interData != NULL ); + log_file = interData->log_file; + hProcess = interData->hProcess; + modBase = interData->modBase; + + if( withParens ) + fprintf(log_file, "("); + + switch( symtag ) + { + case SymTagEnum: + { + WCHAR* pwszTypeName; + + if( SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_SYMNAME, &pwszTypeName) ) + { + fprintf(log_file, "enum %ls", pwszTypeName); + LocalFree(pwszTypeName); + } + else + fprintf(log_file, "enum <symname not found>"); + } + break; + case SymTagBaseType: + { + DWORD basetype; + ULONG64 length = 0; + + SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_LENGTH, &length); + if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &basetype) ) + { + fprintf(log_file, "<basetype not found>"); + break; + } + switch( basetype ) + { + case btVoid: fprintf(log_file, "void"); break; + case btChar: fprintf(log_file, "char"); break; + case btWChar: fprintf(log_file, "wchar"); break; + case btULong: fprintf(log_file, "unsigned "); // next + case btLong: fprintf(log_file, "long"); break; + case btUInt: fprintf(log_file, "unsigned "); // next + case btInt: + if( length == sizeof(char) ) fprintf(log_file, "char"); + else if( length == sizeof(short) ) fprintf(log_file, "short"); + else if( length == sizeof(int) ) fprintf(log_file, "int"); + else if( length == sizeof(long long) ) fprintf(log_file, "long long"); + else fprintf(log_file, "<int%d>", length*8); + break; + case btFloat: + if( length == sizeof(float) ) fprintf(log_file, "float"); + else if( length == sizeof(double) ) fprintf(log_file, "double"); + else if( length == sizeof(long double) ) fprintf(log_file, "long double"); + else fprintf(log_file, "<float%d>", length*8); + break; + default: fprintf(log_file, "<TODO name of basetype %d %d>", basetype, length); break; + } + } + break; + case SymTagPointerType: + { + DWORD subtype; + DWORD subtag; + + if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_TYPE, &subtype) ) + fprintf(log_file, "<type not found>*"); + else if( !SymGetTypeInfo_(hProcess, modBase, subtype, TI_GET_SYMTAG, &subtag) ) + fprintf(log_file, "<symtag not found>*"); + else + { + Dhp__PrintTypeName(subtype, subtag, FALSE, interData); + fprintf(log_file, "*"); + } + } + break; + case SymTagArrayType: + { + DWORD childTypeIndex; + DWORD childSymtag; + + // print root type + childTypeIndex = typeIndex; + childSymtag = symtag; + for( ; ; ) + { + if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_TYPE, &childTypeIndex) ) + { + fprintf(log_file, "<child type not found>"); + break; + } + if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) ) + { + fprintf(log_file, "<child symtag not found>"); + break; + } + if( childSymtag != SymTagArrayType ) + { + Dhp__PrintTypeName(childTypeIndex, childSymtag, FALSE, interData); + break; + } + // next dimension + } + // print dimensions + childTypeIndex = typeIndex; + childSymtag = symtag; + for( ; childSymtag == SymTagArrayType ; ) + { + DWORD childCount; + if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_COUNT, &childCount) ) + fprintf(log_file, "[<count not found>]"); + else + fprintf(log_file, "[%u]", childCount); + if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_TYPE, &childTypeIndex) ) + { + fprintf(log_file, "<child type not found>"); + break; + } + if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) ) + { + fprintf(log_file, "<child symtag not found>"); + break; + } + // next dimension + } + } + break; + default: + { + WCHAR* pSymname; + DWORD udtkind; + + if( SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_UDTKIND, &udtkind) ) + { + switch( (UdtKind)udtkind ) + { + case UdtStruct: fprintf(log_file, "struct "); break; + case UdtClass: fprintf(log_file, "class "); break; + case UdtUnion: fprintf(log_file, "union "); break; + default: fprintf(log_file, "<unknown udtkind %d> ", udtkind); break; + } + } + if( SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_SYMNAME, &pSymname ) ) + { + fprintf(log_file, "%ls", pSymname); + LocalFree( pSymname ); + } + else + fprintf(log_file, "<TODO typename of tag %d>", symtag); break; + } + break; + } + + if( withParens ) + fprintf(log_file, ")"); +} + + +/// Prints the bytes in the target location. +/// +/// @param log_file Log file +/// @param p Pointer to the data +/// @param length Length of the data +static VOID +Dhp__PrintValueBytes( + FILE* log_file, + BYTE* p, + ULONG64 length) +{ + ULONG64 i; + + fprintf(log_file, "<bytes:"); + for( i = 0; i < length; ++i ) + { + fprintf(log_file, "%02X", p[i]); + } + fprintf(log_file, ">"); +} + + +/// Prints a wide string/char value. +/// +/// @param log_file Log file +/// @param p Pointer to the value +/// @param length Length of the value +static VOID +Dhp__PrintValueWideChars( + FILE* log_file, + WCHAR* wstr, + ULONG64 length, + BOOL isString) +{ + ULONG64 i; + char* buf; + char delim; + + length /= sizeof(WCHAR); + delim = ( isString || length > 1 ? '\"' : '\'' ); + fprintf(log_file, "%c", delim); + buf = (char *)LocalAlloc(LMEM_FIXED, MB_CUR_MAX+1); + buf[MB_CUR_MAX] = '\0'; + for( i = 0; i < length; ++i ) + { + int n; + switch( wstr[i] ) + { + case L'\"': fprintf(log_file, "\\\""); break; + case L'\'': fprintf(log_file, "\\\'"); break; + case L'\\': fprintf(log_file, "\\\\"); break; + case L'\a': fprintf(log_file, "\\a"); break; + case L'\b': fprintf(log_file, "\\b"); break; + case L'\f': fprintf(log_file, "\\f"); break; + case L'\n': fprintf(log_file, "\\n"); break; + case L'\r': fprintf(log_file, "\\r"); break; + case L'\t': fprintf(log_file, "\\t"); break; + case L'\v': fprintf(log_file, "\\v"); break; + default: + if( iswprint(wstr[i]) && (n=wctomb(buf, wstr[i])) > 0 ) + { + buf[n] = '\0'; + fprintf(log_file, "%s", buf); + } + else fprintf(log_file, "\\u%04X", wstr[i]); + break; + } + } + LocalFree(buf); + fprintf(log_file, "%c", delim); +} + + +/// Prints a string/char value. +/// +/// @param log_file Log file +/// @param p Pointer to the value +/// @param length Length of the value +static VOID +Dhp__PrintValueChars( + FILE* log_file, + char* str, + ULONG64 length, + BOOL isString) +{ + ULONG64 i; + char delim; + + length /= sizeof(char); + delim = ( isString || length > 1 ? '\"' : '\'' ); + fprintf(log_file, "%c", delim); + for( i = 0; i < length; ++i ) + { + switch( str[i] ) + { + case '\"': fprintf(log_file, "\\\""); break; + case '\'': fprintf(log_file, "\\\'"); break; + case '\\': fprintf(log_file, "\\\\"); break; + case '\a': fprintf(log_file, "\\a"); break; + case '\b': fprintf(log_file, "\\b"); break; + case '\f': fprintf(log_file, "\\f"); break; + case '\n': fprintf(log_file, "\\n"); break; + case '\r': fprintf(log_file, "\\r"); break; + case '\t': fprintf(log_file, "\\t"); break; + case '\v': fprintf(log_file, "\\v"); break; + default: + if( isprint((unsigned char)str[i]) ) fprintf(log_file, "%c", str[i]); + else fprintf(log_file, "\\x%02X", (unsigned char)str[i]); + break; + } + } + fprintf(log_file, "%c", delim); +} + + +/// Prints a float value. +/// +/// @param log_file Log file +/// @param p Pointer to the value +/// @param length Length of the value +static VOID +Dhp__PrintValueFloat( + FILE* log_file, + VOID* p, + ULONG64 length) +{ + if( length == sizeof(float) ) fprintf(log_file, "%f", *(float*)p); + else if( length == sizeof(double) ) fprintf(log_file, "%lf", *(double*)p); + else if( length == sizeof(long double) ) fprintf(log_file, "%Lf", *(long double*)p); + else + { + fprintf(log_file, "<unexpected length %I64u>", length); + Dhp__PrintValueBytes(log_file, (BYTE*)p, length); + } +} + + +/// Prints a hex value. +/// +/// @param log_file Log file +/// @param p Pointer to the value +/// @param length Length of the value +static VOID +Dhp__PrintValueHex( + FILE* log_file, + VOID* p, + ULONG64 length) +{ + if( length == sizeof(UINT32) ) fprintf(log_file, "0x%I32X", *(UINT32*)p); + else if( length == sizeof(UINT64) ) fprintf(log_file, "0x%I64X", *(UINT64*)p); + else if( length == sizeof(char) ) fprintf(log_file, "0x%X", *(unsigned char*)p); + else if( length == sizeof(short) ) fprintf(log_file, "0x%X", *(unsigned short*)p); + else if( length == sizeof(int) ) fprintf(log_file, "0x%x", *(unsigned int*)p); + else if( length == sizeof(long) ) fprintf(log_file, "0x%lX", *(unsigned long*)p); + else if( length == sizeof(long long) ) fprintf(log_file, "0x%llX", *(unsigned long long*)p); + else + { + fprintf(log_file, "<unexpected length %I64u>", length); + Dhp__PrintValueBytes(log_file, (BYTE*)p, length); + } +} + + +/// Prints an unsigned integer value. +/// +/// @param log_file Log file +/// @param p Pointer to the value +/// @param length Length of the value +static VOID +Dhp__PrintValueUnsigned( + FILE* log_file, + VOID* p, + ULONG64 length) +{ + if( length == sizeof(INT32) ) fprintf(log_file, "%I32u", *(INT32*)p); + else if( length == sizeof(INT64) ) fprintf(log_file, "%I64u", *(INT64*)p); + else if( length == sizeof(char) ) fprintf(log_file, "%u", *(unsigned char*)p); + else if( length == sizeof(short) ) fprintf(log_file, "%u", *(unsigned short*)p); + else if( length == sizeof(int) ) fprintf(log_file, "%u", *(unsigned int*)p); + else if( length == sizeof(long) ) fprintf(log_file, "%lu", *(unsigned long*)p); + else if( length == sizeof(long long) ) fprintf(log_file, "%llu", *(unsigned long long*)p); + else + { + fprintf(log_file, "<unexpected length %I64u>", length); + Dhp__PrintValueBytes(log_file, (BYTE*)p, length); + } +} + + +/// Prints a signed integer value. +/// +/// @param log_file Log file +/// @param p Pointer to the value +/// @param length Length of the value +static VOID +Dhp__PrintValueSigned( + FILE* log_file, + VOID* p, + ULONG64 length) +{ + if( length == sizeof(INT32) ) fprintf(log_file, "%I32d", *(INT32*)p); + else if( length == sizeof(INT64) ) fprintf(log_file, "%I64d", *(INT64*)p); + else if( length == sizeof(char) ) fprintf(log_file, "%d", *(signed char*)p); + else if( length == sizeof(short) ) fprintf(log_file, "%d", *(signed short*)p); + else if( length == sizeof(int) ) fprintf(log_file, "%d", *(signed int*)p); + else if( length == sizeof(long) ) fprintf(log_file, "%ld", *(signed long*)p); + else if( length == sizeof(long long) ) fprintf(log_file, "%lld", *(signed long long*)p); + else + { + fprintf(log_file, "<unexpected length %I64u>", length); + Dhp__PrintValueBytes(log_file, (BYTE*)p, length); + } +} + + +/// Prints a nul-terminated wide string value. +/// Checks if the memory can be read from. +/// +/// @param log_file Log file +/// @param str Target string +static VOID +Dhp__PrintValueCWideString( + FILE* log_file, + WCHAR* str) +{ + ULONG64 length = 0; + + // check if memory is readable + __try + { + while( str[length] != L'\0') + ++length; + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + if( length ) Dhp__PrintValueWideChars(log_file, str, length, TRUE); // print readable part + fprintf(log_file, "<invalid memory>"); + return; + } + + // print string + Dhp__PrintValueWideChars(log_file, str, length, TRUE); +} + + +/// Prints a nul-terminated string value. +/// Checks if the memory can be read from. +/// +/// @param log_file Log file +/// @param str Target string +static VOID +Dhp__PrintValueCString( + FILE* log_file, + char* str) +{ + ULONG64 length = 0; + + assert( log_file != NULL ); + + // check if memory is readable + __try + { + while( str[length] != '\0') + ++length; + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + if( length ) Dhp__PrintValueChars(log_file, str, length, TRUE); // print readable part + fprintf(log_file, "<invalid memory>"); + return; + } + + // print string + Dhp__PrintValueChars(log_file, str, length, TRUE); +} + + +// forward declaration of Dhp__PrintDataContents +static VOID Dhp__PrintDataContents(DWORD typeIndex, PVOID pVariable, InterData* interData); + + +/// Prints the value of the data symbol. +/// Checks if the memory can be read from. +/// +/// @param typeIndex Type index of the symbol +/// @param symtag Symbol tag +/// @param pVariable Address to the symbol contents +/// @param pInterData Inter data +static VOID +Dhp__PrintDataValue( + DWORD typeIndex, + DWORD symtag, + PVOID pVariable, + InterData* pInterData) +{ + // inter data + FILE* log_file; + DWORD64 modBase; + HANDLE hProcess; + // + ULONG64 length = 0; + DWORD basetype; + BOOL isValid = TRUE; + + assert( pInterData != NULL ); + log_file = pInterData->log_file; + modBase = pInterData->modBase; + hProcess = pInterData->hProcess; + + if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_LENGTH, &length) ) + { + fprintf(log_file, "<unknown data length>"); + return; + } + + // check if memory is readable + __try + { + BYTE* p = (BYTE*)pVariable; + ULONG i; + BYTE b; + for( i = 0; i < length; ++i ) + b = p[i]; + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + fprintf(log_file, "<invalid memory>"); + return; + } + + switch( symtag ) + { + case SymTagBaseType: + { + if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_BASETYPE, &basetype) ) + { + fprintf(log_file, "<basetype not found>"); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } + switch( basetype ) + { + case btInt: + case btLong: + Dhp__PrintValueSigned(log_file, pVariable, length); + break; + case btUInt: + case btULong: + Dhp__PrintValueUnsigned(log_file, pVariable, length); + break; + case btFloat: + Dhp__PrintValueFloat(log_file, pVariable, length); + break; + case btChar: + { + if( length == sizeof(char) ) fprintf(log_file, "%u ", *(unsigned char*)pVariable); + Dhp__PrintValueChars(log_file, (char*)pVariable, length, FALSE); + } + break; + case btWChar: + { + if( length == sizeof(WCHAR) ) fprintf(log_file, "%u ", *(WCHAR*)pVariable); + Dhp__PrintValueWideChars(log_file, (WCHAR*)pVariable, length, FALSE); + } + break; + case btVoid: + if( length > 0 ) Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + default: + fprintf(log_file, "<TODO value of basetype %d>", basetype); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } + } + break; + case SymTagEnum: + Dhp__PrintValueHex(log_file, pVariable, length); + break; + case SymTagPointerType: + { + DWORD childTypeIndex; + DWORD childSymtag; + + fprintf(log_file, "0x%p", *(void**)pVariable); + if( SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_TYPE, &childTypeIndex) && + SymGetTypeInfo_(hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) && + childSymtag != SymTagPointerType ) + { + DWORD childBasetype; + + // child isn't a pointer, print the contents + fprintf(log_file, " "); + if( childSymtag == SymTagBaseType && + SymGetTypeInfo_(hProcess, modBase, childTypeIndex, TI_GET_BASETYPE, &childBasetype) && + (childBasetype == btChar || childBasetype == btWChar) ) + { + // string or wide string + if( childBasetype == btChar ) Dhp__PrintValueCString(log_file, *(char**)pVariable); + else if( childBasetype == btWChar ) Dhp__PrintValueCWideString(log_file, *(WCHAR**)pVariable); + else fprintf(log_file, "<unexpected child basetype %d>", childBasetype); + break; + } + Dhp__PrintDataValue(childTypeIndex, childSymtag, *(PVOID*)pVariable, pInterData); + } + } + break; + case SymTagArrayType: + { + DWORD childTypeIndex; + DWORD childSymtag; + DWORD count; + DWORD i; + + if( !SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_TYPE, &childTypeIndex) ) + { + fprintf(log_file, "<child type not found>"); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } + if( !SymGetTypeInfo_( hProcess, modBase, childTypeIndex, TI_GET_SYMTAG, &childSymtag) ) + { + fprintf(log_file, "<child symtag not found>"); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } + if( !SymGetTypeInfo_( hProcess, modBase, typeIndex, TI_GET_COUNT, &count) ) + { + fprintf(log_file, "<count not found>"); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } + // print values + fprintf(log_file, "{"); + for( i = 0; i < count; ++i ) + { + BYTE* pData = pVariable; + pData += i*(length/count); + if( i > 0 ) fprintf(log_file, ","); + Dhp__PrintDataValue(childTypeIndex, childSymtag, pData, pInterData); + } + fprintf(log_file, "}"); + } + break; + default: +#if 0 + {//## TODO show children of structs/unions + TI_FINDCHILDREN_PARAMS* children; + DWORD childCount; + DWORD i; + + // count children + if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_CHILDRENCOUNT, &childCount) ) + { + fprintf(log_file, "<child count not found>"); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } + + // Prepare to get an array of "TypeIds", representing each of the children. + // SymGetTypeInfo(TI_FINDCHILDREN) expects more memory than just a + // TI_FINDCHILDREN_PARAMS struct has. Use derivation to accomplish this. + children = (TI_FINDCHILDREN_PARAMS*)LocalAlloc(LMEM_FIXED, sizeof(TI_FINDCHILDREN_PARAMS)+childCount*sizeof(ULONG)); + children->Count = childCount; + children->Start= 0; + + // Get the array of TypeIds, one for each child type + if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_FINDCHILDREN, &children) ) + { + fprintf(log_file, "<children not found>"); + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + LocalFree(children); + return; + } + + // Iterate through each of the children + fprintf(log_file, "{"); + for( i = 0; i < childCount; ++i ) + { + DWORD childOffset; + DWORD childTypeid; + WCHAR* childName; + DWORD_PTR pData; + + if( i > 0 ) fprintf(log_file, ","); + + // Get the offset of the child member, relative to its parent + if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_OFFSET, &childOffset) ) + { + fprintf(log_file, "<child offset not found>"); + continue; + } + + // Get the real "TypeId" of the child. + if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_TYPEID, &childTypeid) ) + { + fprintf(log_file, "<child typeid not found>"); + continue; + } + + // Calculate the address of the member + pData = (DWORD_PTR)pVariable; + pData += childOffset; + + // print name of the child + if( !SymGetTypeInfo_(hProcess, modBase, childTypeid, TI_GET_SYMNAME, &childName) ) + { + fprintf(log_file, "<child symname not found>"); + continue; + } + fprintf(log_file, "%ws=", childName); + LocalFree(childName); + + // print contents of the child + Dhp__PrintDataContents(childTypeid, (PVOID)pData, interData); + } + fprintf(log_file, "}"); + + LocalFree(children); + } +#endif + Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length); + break; + } +} + + +/// Prints the contents of the data symbol. (type and value) +/// +/// @param typeIndex Type index of the symbol +/// @param pVariable Address of the symbol contents +/// @param pInterData Inter data +static VOID +Dhp__PrintDataContents( + DWORD typeIndex, + PVOID pVariable, + InterData* pInterData) +{ + // inter data + FILE* log_file; + HANDLE hProcess; + DWORD64 modBase; + // + DWORD symtag; + + assert( pInterData != NULL ); + log_file = pInterData->log_file; + hProcess = pInterData->hProcess; + modBase = pInterData->modBase; + + if( SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_SYMTAG, &symtag) ) + { + // print type + Dhp__PrintTypeName(typeIndex, symtag, TRUE, pInterData); + // print value + Dhp__PrintDataValue(typeIndex, symtag, pVariable, pInterData); + } + else + fprintf(log_file, "<symtag not found>"); +} + + +/// Prints information about the data symbol. +/// +/// @param pSymInfo Symbol info +/// @param pInterData Inter data +static VOID +Dhp__PrintDataInfo( + PSYMBOL_INFO pSymInfo, + InterData* pInterData) +{ + // inter data + FILE* log_file; + STACKFRAME* pStackframe; + BOOL as_arg_list; + BOOL log_params; + BOOL log_locals; + BOOL log_globals; + int nr_of_var; + // my data + DWORD_PTR pVariable = 0; + enum{ UNKNOWN, PARAM, LOCAL, GLOBAL } scope = UNKNOWN; + + assert( pSymInfo != NULL ); + assert( pInterData != NULL ); + assert( pSymInfo->Tag == SymTagData ); + log_file = pInterData->log_file; + pStackframe = pInterData->pStackframe; + as_arg_list = pInterData->as_arg_list; + log_params = pInterData->log_params; + log_locals = pInterData->log_locals; + log_globals = pInterData->log_globals; + nr_of_var = pInterData->nr_of_var; + + // Determine the scope and address of the variable + if( pSymInfo->Flags & SYMFLAG_REGREL ) + { + pVariable = pStackframe->AddrFrame.Offset; + pVariable += (DWORD_PTR)pSymInfo->Address; + if( pSymInfo->Flags & SYMFLAG_PARAMETER ) + scope = PARAM; // parameter + else if( pSymInfo->Flags & SYMFLAG_LOCAL ) + { + scope = LOCAL; // local +#if defined(_M_IX86) + if( (LONG64)pSymInfo->Address > 0) scope = PARAM; // parameter as local (bug in DBGHELP 5.1) +#endif + } + } + else if( pSymInfo->Flags & SYMFLAG_REGISTER ) + { + scope = ( pSymInfo->Flags & SYMFLAG_PARAMETER ? PARAM : LOCAL ); // register, optimized out(?) + } + else + { + pVariable = (DWORD_PTR)pSymInfo->Address; + scope = GLOBAL; // It must be a global variable + } + + // check if we should to log the variable + if( (scope == PARAM && log_params) || + (scope == LOCAL && log_locals) || + (scope == GLOBAL && log_globals) ) + { + // print prefix and name + if( as_arg_list ) + fprintf(log_file, "%s%s=", (nr_of_var ? ", " : ""), pSymInfo->Name); + else + fprintf(log_file, "\t%s = ", pSymInfo->Name); + + // print value + if( !(pSymInfo->Flags & SYMFLAG_REGREL) && (pSymInfo->Flags & SYMF_REGISTER) ) + fprintf(log_file, "<value optimized out>"); + else + { + pInterData->modBase = pSymInfo->ModBase; + Dhp__PrintDataContents(pSymInfo->TypeIndex, (PVOID)pVariable, pInterData); + } + + // print postfix + if( !as_arg_list ) + fprintf(log_file, "\n"); + pInterData->nr_of_var = ++nr_of_var; + } +} + + +/// Prints information about the symbol. +/// +/// @param pSymInfo Symbol info +/// @param pInterData Inter data +static VOID +Dhp__PrintSymbolInfo( + PSYMBOL_INFO pSymInfo, + InterData* pInterData) +{ + assert( pSymInfo != NULL ); + assert( pInterData != NULL ); + + switch( pSymInfo->Tag ) + { + case SymTagData: Dhp__PrintDataInfo( pSymInfo, pInterData ); break; + default: /*fprintf(pInterData->log_file, "<unsupported symtag %d>", pSymInfo->Tag);*/ break; + } +} + + +/// Prints the details of one symbol to the log file. +/// Used as a callback for SymEnumSymbols. +/// +/// @param pSymInfo Symbol info +/// @param symSize Size of the symbol info structure +/// @param pData Inter data +static BOOL WINAPI +Dhp__EnumSymbolsCallback( + PSYMBOL_INFO pSymInfo, + ULONG symSize, + PVOID pData) +{ + if( pSymInfo == NULL ) + return TRUE; // try other symbols + + if( pData == NULL ) + { + printf("Dhp__EnumSymbolsCallback: pData is NULL\n"); + return FALSE; + } + + Dhp__PrintSymbolInfo(pSymInfo, (InterData*)pData); + return TRUE; +} + + +/// Prints the source code of the target line. +/// Searches for the target file in the original path, +/// in the last src folder of the original path (relative) +/// and in the current directory. +/// +/// @param filename Original source file +/// @param line Target line +/// @param log_file Log file +static VOID +Dhp__PrintSourceLine( + FILE* log_file, + char* filename, + DWORD line) +{ + char path[MAX_PATH*3]; + char pathBuffer[MAX_PATH+1]; + char* p; + + assert( filename != NULL ); + assert( log_file != NULL ); + + // generate search paths + strcpy(path, filename); // original path + p = strrchr(path, '\\'); + if( p ) + { + memcpy(p, ";\0", 2); + p = strstr(filename, "\\src\\"); + if( p ) + { + while( strstr(p+1, "\\src\\") ) + p = strstr(p+1, "\\src\\"); + strcat(path, p+1); // last src folder path + p = strrchr(path, '\\'); + memcpy(p, ";\0", 2); + } + filename = strrchr(filename, '\\')+1; + } + else + *path = '\0'; // no path + strcat(path, "."); // current directoy + + // search for file and line + if( SearchPathA(path, filename, NULL, MAX_PATH, pathBuffer, NULL) ) + { + char code[1024+1]; + DWORD i = 1; + FILE* fp; + + fp = fopen(pathBuffer, "rt"); + if( fp == NULL ) + return; + + code[1024] = '\0'; + while( fgets(code, 1024, fp) ) + { + if( i == line ) + {// found line + char* term = strchr(code, '\n'); + if( term && term != code && term[-1] == '\r' ) --term; + if( term ) *term = '\0'; + fprintf(log_file, "%d\t%s\n", line, code); + break; + } + if( strchr(code, '\n') ) + ++i; + } + fclose(fp); + } +} + + +/// Prints details of one function to the log file. +/// +/// @param interData Inter data +static VOID +Dhp__PrintFunctionDetails( + InterData* pInterData) +{ + // inter data + HANDLE hProcess; + STACKFRAME* pStackframe; + FILE* log_file; + int nr_of_frame; + // + PSYMBOL_INFO pSymbolInfo; + DWORD64 funcDisplacement=0; + IMAGEHLP_STACK_FRAME imagehlpStackFrame; + IMAGEHLP_LINE imagehlpLine; + DWORD lineDisplacement=0; + + assert( pInterData != NULL ); + hProcess = pInterData->hProcess; + pStackframe = pInterData->pStackframe; + log_file = pInterData->log_file; + nr_of_frame = pInterData->nr_of_frame; + + // frame info + fprintf(log_file, + "#%d 0x%p", + nr_of_frame, (void*)(DWORD_PTR)pStackframe->AddrPC.Offset); + + // restrict symbol enumeration to this frame only + ZeroMemory(&imagehlpStackFrame, sizeof(IMAGEHLP_STACK_FRAME)); + imagehlpStackFrame.InstructionOffset = pStackframe->AddrPC.Offset; + SymSetContext_(hProcess, &imagehlpStackFrame, 0); + + // function name and displacement + pSymbolInfo = (PSYMBOL_INFO)LocalAlloc(LMEM_FIXED, sizeof(SYMBOL_INFO)+1024); + pSymbolInfo->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbolInfo->MaxNameLen = 1024; + if( SymFromAddr_(hProcess, pStackframe->AddrPC.Offset, &funcDisplacement, pSymbolInfo) == TRUE ) + { + fprintf(log_file, + " in %.1024s+0x%I64X (", + pSymbolInfo->Name, funcDisplacement); + + // log all function parameters + pInterData->as_arg_list = TRUE; + pInterData->log_params = TRUE; + pInterData->log_locals = FALSE; + pInterData->log_globals = FALSE; + pInterData->nr_of_var = 0; + SymEnumSymbols_(hProcess, 0, 0, Dhp__EnumSymbolsCallback, pInterData); + + fprintf(log_file, + ")"); + } + else + fprintf(log_file, + "in <unknown function>"); + + // find the source line for this function. + imagehlpLine.SizeOfStruct = sizeof(IMAGEHLP_LINE); + if( SymGetLineFromAddr_(hProcess, pStackframe->AddrPC.Offset, &lineDisplacement, &imagehlpLine) != 0 ) + { + char* filename = imagehlpLine.FileName; + DWORD line = imagehlpLine.LineNumber; + + fprintf(log_file, + " at %s:%d\n", + filename, line); + + Dhp__PrintSourceLine(log_file, filename, line); + } + else + fprintf(log_file, + "\n"); + + // log all function local variables + pInterData->as_arg_list = FALSE; + pInterData->log_params = FALSE; + pInterData->log_locals = TRUE; + pInterData->log_globals = FALSE; + pInterData->nr_of_var = 0; + SymEnumSymbols_(hProcess, 0, 0, Dhp__EnumSymbolsCallback, pInterData); + + pInterData->nr_of_frame = ++nr_of_frame; + LocalFree(pSymbolInfo); +} + + +/// Walks over the stack and prints all relevant information to the log file. +/// +/// @param context Exception context +/// @param log_file Log file +static VOID +Dhp__PrintStacktrace( + CONTEXT *context, + FILE *log_file) +{ + HANDLE hProcess = GetCurrentProcess(); + STACKFRAME stackframe; + DWORD machine; + CONTEXT ctx; + InterData interData; + int skip = 0; + int i; + + assert( log_file != NULL ); + + fprintf(log_file, + "\nStacktrace:\n"); + + // Use thread information - if not supplied. + if( context == NULL ) + { + // If no context is supplied, skip 1 frame + skip = 1; + + ctx.ContextFlags = CONTEXT_FULL; + if( GetThreadContext(GetCurrentThread(), &ctx) ) + context = &ctx; + } + + if( context == NULL ) + return; + + // Write the stack trace + ZeroMemory(&stackframe, sizeof(STACKFRAME)); + stackframe.AddrPC.Mode = AddrModeFlat; + stackframe.AddrStack.Mode = AddrModeFlat; + stackframe.AddrFrame.Mode = AddrModeFlat; +#if defined(_M_IX86) + machine = IMAGE_FILE_MACHINE_I386; + stackframe.AddrPC.Offset = context->Eip; + stackframe.AddrStack.Offset = context->Esp; + stackframe.AddrFrame.Offset = context->Ebp; +#else /* defined(_M_IX86) */ +#error FIXME add more processors +some compilers don't stop on #error, this line makes sure it errors out +#endif + + interData.hProcess = hProcess; + interData.log_file = log_file; + interData.pStackframe = &stackframe; + interData.nr_of_frame = 0; + for( i = 0; ; ++i ) + { + if( !StackWalk_(machine, hProcess, GetCurrentThread(), + &stackframe, context, NULL, SymFunctionTableAccess_, + SymGetModuleBase_, NULL)) + { + break; + } + + if( i >= skip ) + { + // Check that the address is not zero. + // Sometimes StackWalk returns TRUE with a frame of zero. + if( stackframe.AddrPC.Offset != 0 ) + Dhp__PrintFunctionDetails(&interData); + } + } +} + + +typedef BOOL (WINAPI *ISDEBUGGERPRESENT)(void); +/// Checks if a debugger is attached to this process +/// +/// @return TRUE is a debugger is present +static BOOL +Dhp__IsDebuggerPresent() +{ + HANDLE kernel32_dll; + ISDEBUGGERPRESENT IsDebuggerPresent_; + BOOL result; + + kernel32_dll = LoadLibraryA("kernel32.dll"); + if( kernel32_dll == NULL ) + return FALSE; + + IsDebuggerPresent_ = (ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent"); + if( IsDebuggerPresent_ ) + result = IsDebuggerPresent_(); + else + result = FALSE; + + FreeLibrary(kernel32_dll); + + return result; +} + + +/// Loads the dbghelp.dll library. +/// +/// @return TRUE is sucessfull +static BOOL +Dhp__LoadDbghelpDll() +{ + dbghelp_dll = LoadLibraryA(DBGHELP_DLL); + if( dbghelp_dll != INVALID_HANDLE_VALUE ) + { + DWORD opts; + + // load the functions + MiniDumpWriteDump_ = (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump"); + SymInitialize_ = (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize"); + SymSetOptions_ = (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions"); + SymGetOptions_ = (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions"); + SymCleanup_ = (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup"); + SymGetTypeInfo_ = (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo"); + SymGetLineFromAddr_ = (SYMGETLINEFROMADDR)GetProcAddress(dbghelp_dll, "SymGetLineFromAddr"); + SymEnumSymbols_ = (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols"); + SymSetContext_ = (SYMSETCONTEXT)GetProcAddress(dbghelp_dll, "SymSetContext"); + SymFromAddr_ = (SYMFROMADDR)GetProcAddress(dbghelp_dll, "SymFromAddr"); + StackWalk_ = (STACKWALK)GetProcAddress(dbghelp_dll, "StackWalk"); + SymFunctionTableAccess_ = (SYMFUNCTIONTABLEACCESS)GetProcAddress(dbghelp_dll, "SymFunctionTableAccess"); + SymGetModuleBase_ = (SYMGETMODULEBASE)GetProcAddress(dbghelp_dll, "SymGetModuleBase"); + + if( MiniDumpWriteDump_ && + SymInitialize_ && SymSetOptions_ && SymGetOptions_ && + SymCleanup_ && SymGetTypeInfo_ && SymGetLineFromAddr_ && + SymEnumSymbols_ && SymSetContext_ && SymFromAddr_ && StackWalk_ && + SymFunctionTableAccess_ && SymGetModuleBase_ ) + { + // initialize the symbol loading code + opts = SymGetOptions_(); + + // Set the 'load lines' option to retrieve line number information. + // Set the 'deferred loads' option to map the debug info in memory only when needed. + SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS); + + // Initialize the dbghelp DLL with the default path and automatic + // module enumeration (and loading of symbol tables) for this process. + SymInitialize_(GetCurrentProcess(), NULL, TRUE); + + return TRUE; + } + } + + if( dbghelp_dll ) + { + FreeLibrary(dbghelp_dll); + dbghelp_dll = NULL; + } + + return FALSE; +} + + +/// Unloads the dbghelp.dll library. +static VOID +Dhp__UnloadDbghlpDll() +{ + SymCleanup_(GetCurrentProcess()); + + FreeLibrary(dbghelp_dll); + dbghelp_dll = NULL; +} + + +/// Creates the report and minidump files. +/// Puts the resulting pathnames in the arguments. +/// The buffers must be at least MAX_PATH+1 in size. +/// +/// @param out_lpszLogFileName Buffer for the report filename +/// @param out_lpszDmpFileName Buffer for the minidump filename +/// @return TRUE if the files were created +static BOOL +Dhp__CreateFiles( + char* out_logFileName, + char* out_dmpFileName) +{ +#define LEN_TIMESTAMP 14 // "YYYYMMDDhhmmss" +#define LEN_EXT 4 // ".rpt" or ".dmp" + char baseFileName[MAX_PATH+1]; + char timestamp[LEN_TIMESTAMP+1]; + FILE* fp; + time_t now; + + // Generate base filename for the report/minidump + ZeroMemory(baseFileName, sizeof(baseFileName)); + if( GetModuleFileName(NULL, baseFileName, MAX_PATH-LEN_TIMESTAMP-LEN_EXT) ) + { + char* pTerm = strrchr(baseFileName, '\\'); + if( pTerm == NULL ) pTerm = baseFileName; + pTerm = strrchr(pTerm, '.'); + if( pTerm ) *pTerm = '\0'; // remove extension + } + else if( GetTempPathA(MAX_PATH-6-LEN_TIMESTAMP-LEN_EXT, baseFileName) ) + {// in temp folder + strcat(baseFileName, DBG_DEFAULT_FILENAME); + } + else + {// in current folder + strcpy(baseFileName, DBG_DEFAULT_FILENAME); + } + + time(&now); +#if 0 + szTimestamp[0] = '\0'; +#else + strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M%S", localtime(&now)); +#endif + timestamp[LEN_TIMESTAMP] = '\0'; + + sprintf(out_logFileName, "%s%s.rpt", baseFileName, timestamp); + fp = fopen(out_logFileName, "w"); + if( fp == NULL ) + return FALSE; // failed to create log file + fclose(fp); + + sprintf(out_dmpFileName, "%s%s.dmp", baseFileName, timestamp); + fp = fopen(out_dmpFileName, "w"); + if( fp == NULL) + return FALSE; // failed to create dump file + fclose(fp); + + return TRUE; // success +#undef LEN_EXT +#undef LEN_TIMESTAMP +} + + +/// Unhandled exception handler. Where the magic starts... ;D +/// +/// @param ptrs Exception information +/// @return What to do with the exception +LONG WINAPI +Dhp__UnhandledExceptionFilter(PEXCEPTION_POINTERS ptrs) +{ + char szLogFileName[MAX_PATH+1]; + char szDmpFileName[MAX_PATH+1]; + FILE* log_file; + + // check if the crash handler was already loaded (crash while handling the crash) + if( dbghelp_dll != INVALID_HANDLE_VALUE ) + return EXCEPTION_CONTINUE_SEARCH; + + // don't log anything if we're running inside a debugger ... + if( Dhp__IsDebuggerPresent() == TRUE ) + return EXCEPTION_CONTINUE_SEARCH; + + // ... or if we can't load dbghelp.dll ... + if( Dhp__LoadDbghelpDll() == FALSE ) + return EXCEPTION_CONTINUE_SEARCH; + + // ... or if we can't create the log files + if( Dhp__CreateFiles(szLogFileName, szDmpFileName) == FALSE ) + return EXCEPTION_CONTINUE_SEARCH; + + // open log file + log_file = fopen(szLogFileName, "wt"); + + // print information about the process + Dhp__PrintProcessInfo( + ptrs ? ptrs->ExceptionRecord : NULL, + ptrs ? ptrs->ContextRecord : NULL, + log_file); + + // print the stacktrace + Dhp__PrintStacktrace( + ptrs ? ptrs->ContextRecord : NULL, + log_file); + + // write the minidump file and use the callback to print the list of modules to the log file + Dhp__WriteMinidumpFile( + szDmpFileName, + ptrs, + Dhp__PrintModuleInfoCallback, + log_file); + + fclose(log_file); + + Dhp__UnloadDbghlpDll(); + + // inform the user + fprintf(stderr, + "\n" + "This application has halted due to an unexpected error.\n" + "A crash report and minidump file were saved to disk, you can find them here:\n" + "%s\n" + "%s\n" + DBG_EXTENDED_INFORMATION + "\n" + "NOTE: The crash report and minidump files can contain sensitive information\n" + "(filenames, partial file content, usernames and passwords etc.)\n", + szLogFileName, + szDmpFileName); + + // terminate the application + return EXCEPTION_EXECUTE_HANDLER; +} + + + +///////////////////////////////////////////////////////////////////// +// DLL stuff +#if !defined(DBG_EMBEDDED) + + +/// Previous exception filter. +static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter; + + +#if defined(__GNUC__) +// GNU : define DLL load/unload functions +static void Dhp__OnStartup(void) __attribute__((constructor)); +static void Dhp__OnExit(void) __attribute__((destructor)); +#endif /* defined(__GNUC__) */ + + +/// Installs as the unhandled exception handler. +void Dhp__OnStartup(void) +{ + // Install the unhandled exception filter function + previousFilter = SetUnhandledExceptionFilter(Dhp__UnhandledExceptionFilter); +} + + +/// Uninstalls the handler. +void Dhp__OnExit(void) +{ + SetUnhandledExceptionFilter(previousFilter); +} + + +#if !defined(__GNUC__) +// Windows : invoke DLL load/unload functions +BOOL APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + switch( dwReason ) + { + case DLL_PROCESS_ATTACH: Dhp__OnStartup(); break; + case DLL_PROCESS_DETACH: Dhp__OnExit(); break; + } + return TRUE; +} +#endif /* !defined(__GNUC__) */ + + + +#endif /* !defined(DBG_EMBEDDED) */ + + + +#endif /* _WIN32 */ |