/**
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
* Copyright (C) 2012-2015 Hercules Dev Team
* Copyright (C) Athena Dev Teams
*
* Hercules is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Ported from eAthena Dev Team's version @ http://eathena-project.googlecode.com/svn/trunk/src/plugins/dbghelpplug.c
// Currently supported dbghelp 5.1
#include "common/hercules.h"
#include "common/sysinfo.h"
#include "common/HPMDataCheck.h"
#include <stdio.h>
#include <string.h>
/**
* Plugin basic information
**/
HPExport struct hplugin_info pinfo = {
"Debug Help",
SERVER_TYPE_ALL,
"0.5",
HPM_VERSION,
};
/////////////////////////////////////////////////////////////////////
// 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 "hercules"
// Extended information printed in the console
#define DBG_EXTENDED_INFORMATION \
"Please report the crash in our Issues tracker:\n" \
"https://github.com/HerculesWS/Hercules/issues\n"
// Print object children?
// WARNING: This will generate huge dump files!
//#define DBG_PRINT_CHILDREN
/////////////////////////////////////////////////////////////////////
// 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)
{
LPSTR cmd_line;
fprintf(log_file,
"\nProcess information:\n");
// print the command line
cmd_line = GetCommandLineA();
if( cmd_line )
fprintf(log_file,
"Command line: %s\n",
cmd_line);
// Print system information
if( sysinfo ) {
fprintf(log_file,
"Platform: %s\n CPU: %s\nApplication architecture: %s\nCompiler: %s\n%s: %s\n",
sysinfo->osversion(), sysinfo->cpu(), (sysinfo->is64bit())?"x64":"x86",
sysinfo->compiler(), sysinfo->vcstype(), sysinfo->vcsrevision_src());
}
// 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 symtag %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 in bytes
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 in bytes
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 in bytes
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 in bytes
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 in bytes
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 in bytes
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 in bytes
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*sizeof(WCHAR), TRUE); // print readable part
fprintf(log_file, "<invalid memory>");
return;
}
// print string
Dhp__PrintValueWideChars(log_file, str, length*sizeof(WCHAR), 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*sizeof(char), TRUE); // print readable part
fprintf(log_file, "<invalid memory>");
return;
}
// print string
Dhp__PrintValueChars(log_file, str, length*sizeof(char), 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 = 0;
for( i = 0; i < length; ++i )
b += p[i]; // add to make sure it's not optimized out in release mode
// Don't continue if there's no valid data
if( b == 0 ) {
fprintf(log_file, "<no data>");
return;
}
}
__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;
}
LocalFree(&childSymtag);
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:
#ifdef DBG_PRINT_CHILDREN
{
TI_FINDCHILDREN_PARAMS *children = NULL;
size_t childrenSize;
DWORD childCount = 0;
DWORD i;
// count children
if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_GET_CHILDRENCOUNT, &childCount)
|| !childCount ) {
fprintf(log_file, "<no children found>");
Dhp__PrintValueBytes(log_file, (BYTE*)pVariable, length);
return;
}
// 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.
childrenSize = sizeof(TI_FINDCHILDREN_PARAMS)+childCount*sizeof(ULONG);
children = (TI_FINDCHILDREN_PARAMS*)LocalAlloc(LMEM_ZEROINIT, childrenSize);
children->Count = childCount;
// Get the array of TypeIds, one for each child type
if( !SymGetTypeInfo_(hProcess, modBase, typeIndex, TI_FINDCHILDREN, children) || !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, "\n{");
for( i = 0; i < childCount; ++i )
{
DWORD childOffset;
WCHAR *childName = NULL;
DWORD childTypeId;
DWORD_PTR pData;
fprintf(log_file, "\n\t");
// 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;
}
// Calculate the address of the member
pData = (DWORD_PTR)pVariable;
pData += childOffset;
if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_SYMNAME, &childName) ) {
fprintf(log_file, "<child symbol name not found>");
continue;
}
fprintf(log_file, "%ws=", childName);
LocalFree(childName);
if( !SymGetTypeInfo_(hProcess, modBase, children->ChildId[i], TI_GET_TYPEID, &childTypeId) ) {
fprintf(log_file, "<child type id not found>");
continue;
}
// print contents of the child
Dhp__PrintDataContents(childTypeId, (PVOID)pData, pInterData);
}
fprintf(log_file, "\n}");
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;
if( !SymEnumSymbols_(hProcess, 0, 0, Dhp__EnumSymbolsCallback, pInterData) )
fprintf(log_file, "<failed enumerating symbols>");
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;
if( !SymEnumSymbols_(hProcess, 0, 0, Dhp__EnumSymbolsCallback, pInterData) )
fprintf(log_file, "!!failed enumerating symbols!!");
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.
if( !SymInitialize_(GetCurrentProcess(), NULL, TRUE) ) {
printf("Failed to initialize symbols. Error: %u\n", GetLastError());
return FALSE;
}
return TRUE;
}
}
if( dbghelp_dll )
{
FreeLibrary(dbghelp_dll);
dbghelp_dll = NULL;
}
return FALSE;
}
/// Unloads the dbghelp.dll library.
static VOID
Dhp__UnloadDbghlpDll()
{
if( !SymCleanup_(GetCurrentProcess()) )
printf("Failed to cleanup symbols! Error: %u\n", GetLastError());
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;
}
/////////////////////////////////////////////////////////////////////
// Plugin
/// Previous exception filter.
static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter;
/**
* Initializes plugin
* Installs a new unhandled exception filter (Dhp__UnhandledExceptionFilter)
**/
HPExport void plugin_init (void) {
previousFilter = SetUnhandledExceptionFilter(Dhp__UnhandledExceptionFilter);
}
/**
* Finalizes plugin
* Uninstalls the handler
**/
HPExport void plugin_final (void) {
SetUnhandledExceptionFilter(previousFilter);
}