2011-03-01 12:28:13 +00:00
/*
* OpenClonk , http : //www.openclonk.org
*
2013-12-17 20:01:09 +00:00
* Copyright ( c ) 1998 - 2000 , Matthes Bender
* Copyright ( c ) 2001 - 2009 , RedWolf Design GmbH , http : //www.clonk.de/
2016-04-03 18:18:29 +00:00
* Copyright ( c ) 2009 - 2016 , The OpenClonk Team and contributors
2011-03-01 12:28:13 +00:00
*
2013-12-17 20:01:09 +00:00
* Distributed under the terms of the ISC license ; see accompanying file
* " COPYING " for details .
2011-03-01 12:28:13 +00:00
*
2013-12-17 20:01:09 +00:00
* " Clonk " is a registered trademark of Matthes Bender , used with permission .
* See accompanying file " TRADEMARK " for details .
2011-03-01 12:28:13 +00:00
*
2013-12-17 20:01:09 +00:00
* To redistribute this file separately , substitute the full license texts
* for the above references .
2011-03-01 12:28:13 +00:00
*/
// Crash handler, Win32 version
# include "C4Include.h"
2011-10-03 11:09:51 +00:00
# ifdef HAVE_DBGHELP
2011-03-01 12:28:13 +00:00
// Dump generation on crash
2016-04-03 18:07:56 +00:00
# include "C4Version.h"
# include "platform/C4windowswrapper.h"
2011-03-01 12:28:13 +00:00
# include <dbghelp.h>
# include <tlhelp32.h>
2017-05-03 18:28:00 +00:00
# include <cinttypes>
2015-09-18 12:46:19 +00:00
# if defined(__CRT_WIDE) || (defined(_MSC_VER) && _MSC_VER >= 1900)
# define USE_WIDE_ASSERT
2015-01-15 09:13:18 +00:00
# endif
2011-03-01 12:28:13 +00:00
static bool FirstCrash = true ;
namespace {
# define OC_MACHINE_UNKNOWN 0x0
# define OC_MACHINE_X86 0x1
# define OC_MACHINE_X64 0x2
# if defined(_M_X64) || defined(__amd64)
# define OC_MACHINE OC_MACHINE_X64
# elif defined(_M_IX86) || defined(__i386__)
# define OC_MACHINE OC_MACHINE_X86
# else
# define OC_MACHINE OC_MACHINE_UNKNOWN
# endif
const size_t DumpBufferSize = 2048 ;
char DumpBuffer [ DumpBufferSize ] ;
char SymbolBuffer [ DumpBufferSize ] ;
// Dump crash info in a human readable format. Uses a static buffer to avoid heap allocations
// from an exception handler. For the same reason, this also doesn't use Log/LogF etc.
2013-11-10 16:30:15 +00:00
void SafeTextDump ( LPEXCEPTION_POINTERS exc , int fd , const wchar_t * dump_filename )
2011-03-01 12:28:13 +00:00
{
# if defined(_MSC_VER)
# define LOG_SNPRINTF _snprintf
# else
# define LOG_SNPRINTF snprintf
# endif
# define LOG_STATIC_TEXT(text) write(fd, text, sizeof(text) - 1)
# define LOG_DYNAMIC_TEXT(...) write(fd, DumpBuffer, LOG_SNPRINTF(DumpBuffer, DumpBufferSize-1, __VA_ARGS__))
2013-02-20 17:20:46 +00:00
// Figure out which kind of format string will output a pointer in hex
2015-01-15 09:13:18 +00:00
# if defined(PRIdPTR)
# define POINTER_FORMAT_SUFFIX PRIdPTR
# elif defined(_MSC_VER)
2013-02-20 17:20:46 +00:00
# define POINTER_FORMAT_SUFFIX "Ix"
# elif defined(__GNUC__)
# define POINTER_FORMAT_SUFFIX "zx"
# else
# define POINTER_FORMAT_SUFFIX "p"
# endif
2011-03-01 12:28:13 +00:00
# if OC_MACHINE == OC_MACHINE_X64
2013-02-20 17:20:46 +00:00
# define POINTER_FORMAT "0x%016" POINTER_FORMAT_SUFFIX
2011-03-01 12:28:13 +00:00
# elif OC_MACHINE == OC_MACHINE_X86
2013-02-20 17:20:46 +00:00
# define POINTER_FORMAT "0x%08" POINTER_FORMAT_SUFFIX
2011-03-01 12:28:13 +00:00
# else
2013-02-20 17:20:46 +00:00
# define POINTER_FORMAT "0x%" POINTER_FORMAT_SUFFIX
2015-01-18 20:55:16 +00:00
# endif
# ifndef STATUS_ASSERTION_FAILURE
# define STATUS_ASSERTION_FAILURE ((DWORD)0xC0000420L)
2011-03-01 12:28:13 +00:00
# endif
2013-02-20 17:27:07 +00:00
LOG_STATIC_TEXT ( " ********************************************************************** \n " ) ;
LOG_STATIC_TEXT ( " * UNHANDLED EXCEPTION \n " ) ;
if ( OC_BUILD_ID [ 0 ] ! = ' \0 ' )
LOG_STATIC_TEXT ( " * Build Identifier: " OC_BUILD_ID " \n " ) ;
2013-11-10 16:30:15 +00:00
if ( exc - > ExceptionRecord - > ExceptionCode ! = STATUS_ASSERTION_FAILURE & & dump_filename & & dump_filename [ 0 ] ! = L ' \0 ' )
{
int cch = WideCharToMultiByte ( CP_UTF8 , 0 , dump_filename , - 1 , SymbolBuffer , sizeof ( SymbolBuffer ) , nullptr , nullptr ) ;
if ( cch > 0 )
{
LOG_STATIC_TEXT ( " * A crash dump may have been written to " ) ;
write ( fd , SymbolBuffer , cch - 1 ) ;
LOG_STATIC_TEXT ( " \n " ) ;
LOG_STATIC_TEXT ( " * If this file exists, please send it to a developer for investigation. \n " ) ;
}
}
2013-02-20 17:27:07 +00:00
LOG_STATIC_TEXT ( " ********************************************************************** \n " ) ;
2011-03-01 12:28:13 +00:00
// Log exception type
switch ( exc - > ExceptionRecord - > ExceptionCode )
{
# define LOG_EXCEPTION(code, text) case code: LOG_STATIC_TEXT(#code ": " text "\n"); break
LOG_EXCEPTION ( EXCEPTION_ACCESS_VIOLATION , " The thread tried to read from or write to a virtual address for which it does not have the appropriate access. " ) ;
LOG_EXCEPTION ( EXCEPTION_ILLEGAL_INSTRUCTION , " The thread tried to execute an invalid instruction. " ) ;
LOG_EXCEPTION ( EXCEPTION_IN_PAGE_ERROR , " The thread tried to access a page that was not present, and the system was unable to load the page. " ) ;
LOG_EXCEPTION ( EXCEPTION_NONCONTINUABLE_EXCEPTION , " The thread tried to continue execution after a noncontinuable exception occurred. " ) ;
LOG_EXCEPTION ( EXCEPTION_PRIV_INSTRUCTION , " The thread tried to execute an instruction whose operation is not allowed in the current machine mode. " ) ;
LOG_EXCEPTION ( EXCEPTION_STACK_OVERFLOW , " The thread used up its stack. " ) ;
LOG_EXCEPTION ( EXCEPTION_GUARD_PAGE , " The thread accessed memory allocated with the PAGE_GUARD modifier. " ) ;
2011-08-14 19:17:39 +00:00
LOG_EXCEPTION ( STATUS_ASSERTION_FAILURE , " The thread specified a pre- or postcondition that did not hold. " ) ;
2011-03-01 12:28:13 +00:00
# undef LOG_EXCEPTION
default :
2011-03-03 23:40:17 +00:00
LOG_DYNAMIC_TEXT ( " %#08x: The thread raised an unknown exception. \n " , static_cast < unsigned int > ( exc - > ExceptionRecord - > ExceptionCode ) ) ;
2011-03-01 12:28:13 +00:00
break ;
}
if ( exc - > ExceptionRecord - > ExceptionFlags = = EXCEPTION_NONCONTINUABLE )
LOG_STATIC_TEXT ( " This is a non-continuable exception. \n " ) ;
else
LOG_STATIC_TEXT ( " This is a continuable exception. \n " ) ;
// For some exceptions, there is a defined meaning to the ExceptionInformation field
switch ( exc - > ExceptionRecord - > ExceptionCode )
{
case EXCEPTION_ACCESS_VIOLATION :
case EXCEPTION_IN_PAGE_ERROR :
if ( exc - > ExceptionRecord - > NumberParameters < 2 )
{
LOG_STATIC_TEXT ( " Additional information for the exception was not provided. \n " ) ;
break ;
}
LOG_STATIC_TEXT ( " Additional information for the exception: The thread " ) ;
switch ( exc - > ExceptionRecord - > ExceptionInformation [ 0 ] )
{
2011-03-03 23:08:11 +00:00
# ifndef EXCEPTION_READ_FAULT
# define EXCEPTION_READ_FAULT 0
# define EXCEPTION_WRITE_FAULT 1
# define EXCEPTION_EXECUTE_FAULT 8
# endif
2011-03-01 12:28:13 +00:00
case EXCEPTION_READ_FAULT : LOG_STATIC_TEXT ( " tried to read from memory " ) ; break ;
case EXCEPTION_WRITE_FAULT : LOG_STATIC_TEXT ( " tried to write to memory " ) ; break ;
case EXCEPTION_EXECUTE_FAULT : LOG_STATIC_TEXT ( " caused an user-mode DEP violation " ) ; break ;
2011-03-03 23:40:17 +00:00
default : LOG_DYNAMIC_TEXT ( " tried to access (%#x) memory " , static_cast < unsigned int > ( exc - > ExceptionRecord - > ExceptionInformation [ 0 ] ) ) ; break ;
2011-03-01 12:28:13 +00:00
}
2011-03-03 23:40:17 +00:00
LOG_DYNAMIC_TEXT ( " at address " POINTER_FORMAT " . \n " , static_cast < size_t > ( exc - > ExceptionRecord - > ExceptionInformation [ 1 ] ) ) ;
2011-03-01 12:28:13 +00:00
if ( exc - > ExceptionRecord - > ExceptionCode = = EXCEPTION_IN_PAGE_ERROR )
{
if ( exc - > ExceptionRecord - > NumberParameters > = 3 )
2011-03-03 23:40:17 +00:00
LOG_DYNAMIC_TEXT ( " The NTSTATUS code that resulted in this exception was " POINTER_FORMAT " . \n " , static_cast < size_t > ( exc - > ExceptionRecord - > ExceptionInformation [ 2 ] ) ) ;
2011-03-01 12:28:13 +00:00
else
LOG_STATIC_TEXT ( " The NTSTATUS code that resulted in this exception was not provided. \n " ) ;
}
break ;
2011-08-14 19:17:39 +00:00
case STATUS_ASSERTION_FAILURE :
if ( exc - > ExceptionRecord - > NumberParameters < 3 )
{
LOG_STATIC_TEXT ( " Additional information for the exception was not provided. \n " ) ;
break ;
}
2015-09-18 12:46:19 +00:00
# ifdef USE_WIDE_ASSERT
2011-08-14 19:17:39 +00:00
# define ASSERTION_INFO_FORMAT "%ls"
2015-08-29 12:33:00 +00:00
# define ASSERTION_INFO_TYPE wchar_t *
2011-08-14 19:17:39 +00:00
# else
# define ASSERTION_INFO_FORMAT "%s"
2015-08-29 12:33:00 +00:00
# define ASSERTION_INFO_TYPE char *
2011-08-14 19:17:39 +00:00
# endif
LOG_DYNAMIC_TEXT ( " Additional information for the exception: \n Assertion that failed: " ASSERTION_INFO_FORMAT " \n File: " ASSERTION_INFO_FORMAT " \n Line: %d \n " ,
2015-08-29 12:33:00 +00:00
reinterpret_cast < ASSERTION_INFO_TYPE > ( exc - > ExceptionRecord - > ExceptionInformation [ 0 ] ) ,
reinterpret_cast < ASSERTION_INFO_TYPE > ( exc - > ExceptionRecord - > ExceptionInformation [ 1 ] ) ,
2015-09-02 02:18:39 +00:00
( int ) exc - > ExceptionRecord - > ExceptionInformation [ 2 ] ) ;
2011-08-14 19:17:39 +00:00
break ;
2011-03-01 12:28:13 +00:00
}
// Dump registers
# if OC_MACHINE == OC_MACHINE_X64
LOG_STATIC_TEXT ( " \n Processor registers (x86_64): \n " ) ;
LOG_DYNAMIC_TEXT ( " RAX: " POINTER_FORMAT " , RBX: " POINTER_FORMAT " , RCX: " POINTER_FORMAT " , RDX: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > Rax ) , static_cast < size_t > ( exc - > ContextRecord - > Rbx ) ,
static_cast < size_t > ( exc - > ContextRecord - > Rcx ) , static_cast < size_t > ( exc - > ContextRecord - > Rdx ) ) ;
2011-03-01 12:28:13 +00:00
LOG_DYNAMIC_TEXT ( " RBP: " POINTER_FORMAT " , RSI: " POINTER_FORMAT " , RDI: " POINTER_FORMAT " , R8: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > Rbp ) , static_cast < size_t > ( exc - > ContextRecord - > Rsi ) ,
static_cast < size_t > ( exc - > ContextRecord - > Rdi ) , static_cast < size_t > ( exc - > ContextRecord - > R8 ) ) ;
2011-03-01 12:28:13 +00:00
LOG_DYNAMIC_TEXT ( " R9: " POINTER_FORMAT " , R10: " POINTER_FORMAT " , R11: " POINTER_FORMAT " , R12: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > R9 ) , static_cast < size_t > ( exc - > ContextRecord - > R10 ) ,
static_cast < size_t > ( exc - > ContextRecord - > R11 ) , static_cast < size_t > ( exc - > ContextRecord - > R12 ) ) ;
2011-03-01 12:28:13 +00:00
LOG_DYNAMIC_TEXT ( " R13: " POINTER_FORMAT " , R14: " POINTER_FORMAT " , R15: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > R13 ) , static_cast < size_t > ( exc - > ContextRecord - > R14 ) ,
static_cast < size_t > ( exc - > ContextRecord - > R15 ) ) ;
2011-03-01 12:28:13 +00:00
LOG_DYNAMIC_TEXT ( " RSP: " POINTER_FORMAT " , RIP: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > Rsp ) , static_cast < size_t > ( exc - > ContextRecord - > Rip ) ) ;
2011-03-01 12:28:13 +00:00
# elif OC_MACHINE == OC_MACHINE_X86
LOG_STATIC_TEXT ( " \n Processor registers (x86): \n " ) ;
LOG_DYNAMIC_TEXT ( " EAX: " POINTER_FORMAT " , EBX: " POINTER_FORMAT " , ECX: " POINTER_FORMAT " , EDX: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > Eax ) , static_cast < size_t > ( exc - > ContextRecord - > Ebx ) ,
static_cast < size_t > ( exc - > ContextRecord - > Ecx ) , static_cast < size_t > ( exc - > ContextRecord - > Edx ) ) ;
2011-03-01 12:28:13 +00:00
LOG_DYNAMIC_TEXT ( " ESI: " POINTER_FORMAT " , EDI: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > Esi ) , static_cast < size_t > ( exc - > ContextRecord - > Edi ) ) ;
2011-03-01 12:28:13 +00:00
LOG_DYNAMIC_TEXT ( " EBP: " POINTER_FORMAT " , ESP: " POINTER_FORMAT " , EIP: " POINTER_FORMAT " \n " ,
2011-03-03 23:40:17 +00:00
static_cast < size_t > ( exc - > ContextRecord - > Ebp ) , static_cast < size_t > ( exc - > ContextRecord - > Esp ) ,
static_cast < size_t > ( exc - > ContextRecord - > Eip ) ) ;
2011-03-01 12:28:13 +00:00
# endif
# if OC_MACHINE == OC_MACHINE_X64 || OC_MACHINE == OC_MACHINE_X86
2013-02-20 17:19:51 +00:00
LOG_DYNAMIC_TEXT ( " EFLAGS: 0x%08x (%c%c%c%c%c%c%c) \n " , static_cast < unsigned int > ( exc - > ContextRecord - > EFlags ) ,
exc - > ContextRecord - > EFlags & 0x800 ? ' O ' : ' . ' , // overflow
exc - > ContextRecord - > EFlags & 0x400 ? ' D ' : ' . ' , // direction
exc - > ContextRecord - > EFlags & 0x80 ? ' S ' : ' . ' , // sign
exc - > ContextRecord - > EFlags & 0x40 ? ' Z ' : ' . ' , // zero
exc - > ContextRecord - > EFlags & 0x10 ? ' A ' : ' . ' , // auxiliary carry
exc - > ContextRecord - > EFlags & 0x4 ? ' P ' : ' . ' , // parity
exc - > ContextRecord - > EFlags & 0x1 ? ' C ' : ' . ' ) ; // carry
2011-03-01 12:28:13 +00:00
# endif
// Dump stack
LOG_STATIC_TEXT ( " \n Stack contents: \n " ) ;
MEMORY_BASIC_INFORMATION stack_info ;
intptr_t stack_pointer =
# if OC_MACHINE == OC_MACHINE_X64
exc - > ContextRecord - > Rsp
# elif OC_MACHINE == OC_MACHINE_X86
exc - > ContextRecord - > Esp
# endif
;
if ( VirtualQuery ( reinterpret_cast < LPCVOID > ( stack_pointer ) , & stack_info , sizeof ( stack_info ) ) )
{
intptr_t stack_base = reinterpret_cast < intptr_t > ( stack_info . BaseAddress ) ;
intptr_t dump_min = std : : max < intptr_t > ( stack_base , ( stack_pointer - 256 ) & ~ 0xF ) ;
intptr_t dump_max = std : : min < intptr_t > ( stack_base + stack_info . RegionSize , ( stack_pointer + 256 ) | 0xF ) ;
for ( intptr_t dump_row_base = dump_min & ~ 0xF ; dump_row_base < dump_max ; dump_row_base + = 0x10 )
{
LOG_DYNAMIC_TEXT ( POINTER_FORMAT " : " , dump_row_base ) ;
// Hex dump
for ( intptr_t dump_row_cursor = dump_row_base ; dump_row_cursor < dump_row_base + 16 ; + + dump_row_cursor )
{
if ( dump_row_cursor < dump_min | | dump_row_cursor > dump_max )
LOG_STATIC_TEXT ( " " ) ;
else
2011-03-03 23:40:17 +00:00
LOG_DYNAMIC_TEXT ( " %02x " , ( unsigned int ) * reinterpret_cast < unsigned char * > ( dump_row_cursor ) ) ; // Safe, since it's inside the VM of our process
2011-03-01 12:28:13 +00:00
}
LOG_STATIC_TEXT ( " " ) ;
// Text dump
for ( intptr_t dump_row_cursor = dump_row_base ; dump_row_cursor < dump_row_base + 16 ; + + dump_row_cursor )
{
if ( dump_row_cursor < dump_min | | dump_row_cursor > dump_max )
LOG_STATIC_TEXT ( " " ) ;
else
{
2011-03-03 23:40:17 +00:00
unsigned char c = * reinterpret_cast < unsigned char * > ( dump_row_cursor ) ; // Safe, since it's inside the VM of our process
2011-03-01 12:28:13 +00:00
if ( c < 0x20 | | ( c > 0x7e & & c < 0xa1 ) )
LOG_STATIC_TEXT ( " . " ) ;
else
2011-03-03 23:40:17 +00:00
LOG_DYNAMIC_TEXT ( " %c " , static_cast < char > ( c ) ) ;
2011-03-01 12:28:13 +00:00
}
}
LOG_STATIC_TEXT ( " \n " ) ;
}
}
else
{
LOG_STATIC_TEXT ( " [Failed to access stack memory] \n " ) ;
}
// Initialize DbgHelp.dll symbol functions
SymSetOptions ( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES ) ;
HANDLE process = GetCurrentProcess ( ) ;
2017-05-03 18:28:00 +00:00
if ( SymInitialize ( process , nullptr , true ) )
2011-03-01 12:28:13 +00:00
{
LOG_STATIC_TEXT ( " \n Stack trace: \n " ) ;
2015-09-02 01:37:16 +00:00
auto frame = STACKFRAME64 ( ) ;
2011-03-01 12:28:13 +00:00
DWORD image_type ;
CONTEXT context = * exc - > ContextRecord ;
// Setup frame info
frame . AddrPC . Mode = AddrModeFlat ;
frame . AddrStack . Mode = AddrModeFlat ;
frame . AddrFrame . Mode = AddrModeFlat ;
# if OC_MACHINE == OC_MACHINE_X64
image_type = IMAGE_FILE_MACHINE_AMD64 ;
frame . AddrPC . Offset = context . Rip ;
frame . AddrStack . Offset = context . Rsp ;
// Some compilers use rdi for their frame pointer instead. Let's hope they're in the minority.
frame . AddrFrame . Offset = context . Rbp ;
# elif OC_MACHINE == OC_MACHINE_X86
image_type = IMAGE_FILE_MACHINE_I386 ;
frame . AddrPC . Offset = context . Eip ;
frame . AddrStack . Offset = context . Esp ;
frame . AddrFrame . Offset = context . Ebp ;
# endif
// Dump stack trace
SYMBOL_INFO * symbol = reinterpret_cast < SYMBOL_INFO * > ( SymbolBuffer ) ;
static_assert ( DumpBufferSize > = sizeof ( * symbol ) , " SYMBOL_INFO too large to fit into buffer " ) ;
IMAGEHLP_MODULE64 * module = reinterpret_cast < IMAGEHLP_MODULE64 * > ( SymbolBuffer ) ;
static_assert ( DumpBufferSize > = sizeof ( * module ) , " IMAGEHLP_MODULE64 too large to fit into buffer " ) ;
IMAGEHLP_LINE64 * line = reinterpret_cast < IMAGEHLP_LINE64 * > ( SymbolBuffer ) ;
static_assert ( DumpBufferSize > = sizeof ( * line ) , " IMAGEHLP_LINE64 too large to fit into buffer " ) ;
int frame_number = 0 ;
2017-05-03 18:28:00 +00:00
while ( StackWalk64 ( image_type , process , GetCurrentThread ( ) , & frame , & context , nullptr , SymFunctionTableAccess64 , SymGetModuleBase64 , nullptr ) )
2011-03-01 12:28:13 +00:00
{
LOG_DYNAMIC_TEXT ( " #%3d " , frame_number ) ;
2013-02-20 17:19:07 +00:00
module - > SizeOfStruct = sizeof ( * module ) ;
DWORD64 image_base = 0 ;
2011-03-01 12:28:13 +00:00
if ( SymGetModuleInfo64 ( process , frame . AddrPC . Offset , module ) )
{
2013-02-20 17:19:07 +00:00
LOG_DYNAMIC_TEXT ( " %s " , module - > ModuleName ) ;
image_base = module - > BaseOfImage ;
2011-03-01 12:28:13 +00:00
}
DWORD64 disp64 ;
symbol - > MaxNameLen = DumpBufferSize - sizeof ( * symbol ) ;
2013-02-20 17:19:07 +00:00
symbol - > SizeOfStruct = sizeof ( * symbol ) ;
2011-03-01 12:28:13 +00:00
if ( SymFromAddr ( process , frame . AddrPC . Offset , & disp64 , symbol ) )
{
2013-02-20 17:19:07 +00:00
LOG_DYNAMIC_TEXT ( " !%s+%#lx " , symbol - > Name , static_cast < long > ( disp64 ) ) ;
}
else if ( image_base > 0 )
{
2015-09-02 02:18:39 +00:00
LOG_DYNAMIC_TEXT ( " +%#lx " , static_cast < long > ( frame . AddrPC . Offset - image_base ) ) ;
2011-03-01 12:28:13 +00:00
}
else
{
2015-09-02 02:18:39 +00:00
LOG_DYNAMIC_TEXT ( " %#lx " , static_cast < long > ( frame . AddrPC . Offset ) ) ;
2011-03-01 12:28:13 +00:00
}
DWORD disp ;
2013-02-20 17:19:07 +00:00
line - > SizeOfStruct = sizeof ( * line ) ;
2011-03-01 12:28:13 +00:00
if ( SymGetLineFromAddr64 ( process , frame . AddrPC . Offset , & disp , line ) )
{
2013-02-20 17:19:07 +00:00
LOG_DYNAMIC_TEXT ( " [%s @ %u] " , line - > FileName , static_cast < unsigned int > ( line - > LineNumber ) ) ;
2011-03-01 12:28:13 +00:00
}
LOG_STATIC_TEXT ( " \n " ) ;
+ + frame_number ;
}
SymCleanup ( process ) ;
}
else
{
LOG_STATIC_TEXT ( " [Stack trace not available: failed to initialize Debugging Help Library] \n " ) ;
}
// Dump loaded modules
HANDLE snapshot ;
while ( ( snapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPMODULE , 0 ) ) = = INVALID_HANDLE_VALUE )
if ( GetLastError ( ) ! = ERROR_BAD_LENGTH ) break ;
if ( snapshot ! = INVALID_HANDLE_VALUE )
{
LOG_STATIC_TEXT ( " \n Loaded modules: \n " ) ;
MODULEENTRY32 * module = reinterpret_cast < MODULEENTRY32 * > ( SymbolBuffer ) ;
static_assert ( DumpBufferSize > = sizeof ( * module ) , " MODULEENTRY32 too large to fit into buffer " ) ;
module - > dwSize = sizeof ( * module ) ;
for ( BOOL success = Module32First ( snapshot , module ) ; success ; success = Module32Next ( snapshot , module ) )
{
2011-08-14 19:16:12 +00:00
LOG_DYNAMIC_TEXT ( " %32ls loaded at " POINTER_FORMAT " - " POINTER_FORMAT " (%ls) \n " , module - > szModule ,
2011-03-03 23:40:17 +00:00
reinterpret_cast < size_t > ( module - > modBaseAddr ) , reinterpret_cast < size_t > ( module - > modBaseAddr + module - > modBaseSize ) ,
module - > szExePath ) ;
2011-03-01 12:28:13 +00:00
}
CloseHandle ( snapshot ) ;
}
2013-02-20 17:20:46 +00:00
# undef POINTER_FORMAT_SUFFIX
# undef POINTER_FORMAT
2011-03-01 12:28:13 +00:00
# undef LOG_SNPRINTF
# undef LOG_DYNAMIC_TEXT
# undef LOG_STATIC_TEXT
}
}
LONG WINAPI GenerateDump ( EXCEPTION_POINTERS * pExceptionPointers )
{
2013-02-20 17:27:07 +00:00
enum
{
MDST_BuildId = LastReservedStream + 1
} ;
2011-03-01 12:28:13 +00:00
if ( ! FirstCrash ) return EXCEPTION_EXECUTE_HANDLER ;
FirstCrash = false ;
// Open dump file
2013-11-10 16:30:15 +00:00
// Work on the assumption that the config isn't corrupted
wchar_t * filename = reinterpret_cast < wchar_t * > ( DumpBuffer ) ;
const size_t filename_buffer_size = DumpBufferSize / sizeof ( wchar_t ) ;
if ( ! MultiByteToWideChar ( CP_UTF8 , MB_ERR_INVALID_CHARS , : : Config . General . UserDataPath , strnlen ( : : Config . General . UserDataPath , sizeof ( : : Config . General . UserDataPath ) ) , filename , filename_buffer_size ) )
{
// Conversion failed; the likely reason for this is a corrupted config.
assert ( GetLastError ( ) = = ERROR_NO_UNICODE_TRANSLATION ) ;
// Fall back to the temporary files directory to write dump.
DWORD temp_size = GetTempPath ( filename_buffer_size , filename ) ;
if ( temp_size = = 0 | | temp_size > filename_buffer_size )
{
// Getting the temp path failed as well; dump to current working directory as a last resort.
temp_size = GetCurrentDirectory ( filename_buffer_size , filename ) ;
if ( temp_size = = 0 | | temp_size > filename_buffer_size )
{
// We don't really have any directory where we can store the dump, so just
// write the text log (we already have a FD for that)
filename [ 0 ] = L ' \0 ' ;
}
}
}
HANDLE file = INVALID_HANDLE_VALUE ;
2011-03-01 12:28:13 +00:00
2013-11-10 16:30:15 +00:00
if ( filename [ 0 ] ! = L ' \0 ' )
{
// There is some path where we want to store our data
const wchar_t tmpl [ ] = TEXT ( C4ENGINENICK ) L " -crash-YYYY-MM-DD-HH-MM-SS.dmp " ;
size_t path_len = wcslen ( filename ) ;
if ( path_len + sizeof ( tmpl ) / sizeof ( * tmpl ) > filename_buffer_size )
{
// Somehow the length of the required path is too long to fit in
// our buffer. Don't dump anything then.
filename [ 0 ] = L ' \0 ' ;
}
else
{
// Make sure the path ends in a backslash.
if ( filename [ path_len - 1 ] ! = L ' \\ ' )
{
filename [ path_len ] = L ' \\ ' ;
filename [ + + path_len ] = L ' \0 ' ;
}
SYSTEMTIME st ;
GetSystemTime ( & st ) ;
wsprintf ( & filename [ path_len ] , L " %s-crash-%04d-%02d-%02d-%02d-%02d-%02d.dmp " ,
TEXT ( C4ENGINENICK ) , st . wYear , st . wMonth , st . wDay , st . wHour , st . wMinute , st . wSecond ) ;
}
}
2011-03-01 12:28:13 +00:00
2013-11-10 16:30:15 +00:00
if ( filename [ 0 ] ! = L ' \0 ' )
2013-02-20 17:27:07 +00:00
{
2016-11-02 23:58:02 +00:00
file = CreateFile ( filename , GENERIC_WRITE , FILE_SHARE_READ | FILE_SHARE_DELETE , nullptr , CREATE_NEW , FILE_ATTRIBUTE_NORMAL , nullptr ) ;
2013-11-10 16:30:15 +00:00
// If we can't create a *new* file to dump into, don't dump at all.
if ( file = = INVALID_HANDLE_VALUE )
filename [ 0 ] = L ' \0 ' ;
2013-02-20 17:27:07 +00:00
}
2013-11-10 16:30:15 +00:00
// Write dump (human readable format)
2015-02-16 17:14:29 +00:00
if ( GetLogFD ( ) ! = - 1 )
SafeTextDump ( pExceptionPointers , GetLogFD ( ) , filename ) ;
2013-11-10 16:30:15 +00:00
if ( file ! = INVALID_HANDLE_VALUE )
{
2015-09-02 01:37:16 +00:00
auto user_stream_info = MINIDUMP_USER_STREAM_INFORMATION ( ) ;
auto user_stream = MINIDUMP_USER_STREAM ( ) ;
2013-11-10 16:30:15 +00:00
char build_id [ ] = OC_BUILD_ID ;
if ( OC_BUILD_ID [ 0 ] ! = ' \0 ' )
{
user_stream . Type = MDST_BuildId ;
user_stream . Buffer = build_id ;
user_stream . BufferSize = sizeof ( build_id ) - 1 ; // don't need the terminating NUL
user_stream_info . UserStreamCount = 1 ;
user_stream_info . UserStreamArray = & user_stream ;
}
MINIDUMP_EXCEPTION_INFORMATION ExpParam ;
ExpParam . ThreadId = GetCurrentThreadId ( ) ;
ExpParam . ExceptionPointers = pExceptionPointers ;
ExpParam . ClientPointers = true ;
MiniDumpWriteDump ( GetCurrentProcess ( ) , GetCurrentProcessId ( ) ,
2016-11-02 23:58:02 +00:00
file , MiniDumpNormal , & ExpParam , & user_stream_info , nullptr ) ;
2013-11-10 16:30:15 +00:00
CloseHandle ( file ) ;
}
2011-03-01 12:28:13 +00:00
// Pass exception
return EXCEPTION_EXECUTE_HANDLER ;
}
2011-08-19 10:53:48 +00:00
# ifndef NDEBUG
2011-08-14 19:17:39 +00:00
namespace {
// Assertion logging hook. This will replace the prologue of the standard assertion
// handler with a trampoline to assertion_handler(), which logs the assertion, then
// replaces the trampoline with the original prologue, and calls the handler.
// If the standard handler returns control to assertion_handler(), it will then
// restore the hook.
2015-09-18 12:46:19 +00:00
# ifdef USE_WIDE_ASSERT
2011-08-14 19:17:39 +00:00
typedef void ( __cdecl * ASSERT_FUNC ) ( const wchar_t * , const wchar_t * , unsigned ) ;
const ASSERT_FUNC assert_func =
& _wassert ;
# else
2011-08-15 00:27:47 +00:00
typedef void ( __cdecl * ASSERT_FUNC ) ( const char * , const char * , int ) ;
2011-08-14 19:17:39 +00:00
const ASSERT_FUNC assert_func =
& _assert ;
# endif
unsigned char trampoline [ ] = {
# if OC_MACHINE == OC_MACHINE_X64
// MOV rax, 0xCCCCCCCCCCCCCCCC
0x48 /* REX.W */ , 0xB8 , 0xCC , 0xCC , 0xCC , 0xCC , 0xCC , 0xCC , 0xCC , 0xCC ,
// JMP rax
0xFF , 0xE0
# elif OC_MACHINE == OC_MACHINE_X86
// NOP ; to align jump target
0x90 ,
// MOV eax, 0xCCCCCCCC
0xB8 , 0xCC , 0xCC , 0xCC , 0xCC ,
// JMP eax
0xFF , 0xE0
# endif
} ;
unsigned char trampoline_backup [ sizeof ( trampoline ) ] ;
void HookAssert ( ASSERT_FUNC hook )
{
// Write hook function address to trampoline
memcpy ( trampoline + 2 , ( void * ) & hook , sizeof ( void * ) ) ;
// Make target location writable
DWORD old_protect = 0 ;
if ( ! VirtualProtect ( ( LPVOID ) assert_func , sizeof ( trampoline ) , PAGE_EXECUTE_READWRITE , & old_protect ) )
return ;
// Take backup of old target function and replace it with trampoline
memcpy ( trampoline_backup , ( void * ) assert_func , sizeof ( trampoline_backup ) ) ;
memcpy ( ( void * ) assert_func , trampoline , sizeof ( trampoline ) ) ;
// Restore memory protection
VirtualProtect ( ( LPVOID ) assert_func , sizeof ( trampoline ) , old_protect , & old_protect ) ;
// Flush processor caches. Not strictly necessary on x86 and x64.
FlushInstructionCache ( GetCurrentProcess ( ) , ( LPCVOID ) assert_func , sizeof ( trampoline ) ) ;
}
void UnhookAssert ( )
{
DWORD old_protect = 0 ;
if ( ! VirtualProtect ( ( LPVOID ) assert_func , sizeof ( trampoline_backup ) , PAGE_EXECUTE_READWRITE , & old_protect ) )
// Couldn't make assert function writable. Abort program (it's what assert() is supposed to do anyway).
abort ( ) ;
// Replace function with backup
memcpy ( ( void * ) assert_func , trampoline_backup , sizeof ( trampoline_backup ) ) ;
VirtualProtect ( ( LPVOID ) assert_func , sizeof ( trampoline_backup ) , old_protect , & old_protect ) ;
FlushInstructionCache ( GetCurrentProcess ( ) , ( LPCVOID ) assert_func , sizeof ( trampoline_backup ) ) ;
}
struct dump_thread_t {
HANDLE thread ;
2015-09-18 12:46:19 +00:00
# ifdef USE_WIDE_ASSERT
2011-08-14 19:17:39 +00:00
const wchar_t
# else
const char
# endif
* expression , * file ;
size_t line ;
} ;
// Helper function to get a valid thread context for the main thread
static DWORD WINAPI dump_thread ( LPVOID t )
{
dump_thread_t * data = static_cast < dump_thread_t * > ( t ) ;
// Stop calling thread so we can take a snapshot
if ( SuspendThread ( data - > thread ) = = - 1 )
return FALSE ;
// Get thread info
2015-09-02 01:37:16 +00:00
auto ctx = CONTEXT ( ) ;
2011-08-14 23:09:58 +00:00
# ifndef CONTEXT_ALL
# define CONTEXT_ALL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | \
CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS )
# endif
2011-08-14 19:17:39 +00:00
ctx . ContextFlags = CONTEXT_ALL ;
BOOL result = GetThreadContext ( data - > thread , & ctx ) ;
// Setup a fake exception to log
2015-09-02 01:37:16 +00:00
auto erec = EXCEPTION_RECORD ( ) ;
2011-08-14 19:17:39 +00:00
erec . ExceptionCode = STATUS_ASSERTION_FAILURE ;
erec . ExceptionFlags = 0L ;
erec . ExceptionInformation [ 0 ] = ( ULONG_PTR ) data - > expression ;
erec . ExceptionInformation [ 1 ] = ( ULONG_PTR ) data - > file ;
erec . ExceptionInformation [ 2 ] = ( ULONG_PTR ) data - > line ;
erec . NumberParameters = 3 ;
erec . ExceptionAddress = ( LPVOID )
# if OC_MACHINE == OC_MACHINE_X64
ctx . Rip
# elif OC_MACHINE == OC_MACHINE_X86
ctx . Eip
# else
0
# endif
;
EXCEPTION_POINTERS eptr ;
eptr . ContextRecord = & ctx ;
eptr . ExceptionRecord = & erec ;
// Log
2015-02-16 17:14:29 +00:00
if ( GetLogFD ( ) ! = - 1 )
SafeTextDump ( & eptr , GetLogFD ( ) , nullptr ) ;
2011-08-14 19:17:39 +00:00
// Continue caller
if ( ResumeThread ( data - > thread ) = = - 1 )
abort ( ) ;
return result ;
}
// Replacement assertion handler
2015-09-18 12:46:19 +00:00
# ifdef USE_WIDE_ASSERT
2011-08-14 19:17:39 +00:00
void __cdecl assertion_handler ( const wchar_t * expression , const wchar_t * file , unsigned line )
# else
2011-08-15 00:27:47 +00:00
void __cdecl assertion_handler ( const char * expression , const char * file , int line )
2011-08-14 19:17:39 +00:00
# endif
{
// Dump thread status on a different thread because we can't get a valid thread context otherwise
HANDLE this_thread ;
DuplicateHandle ( GetCurrentProcess ( ) , GetCurrentThread ( ) , GetCurrentProcess ( ) , & this_thread , 0 , FALSE , DUPLICATE_SAME_ACCESS ) ;
dump_thread_t dump_thread_data = {
this_thread ,
expression , file , line
} ;
2016-11-02 23:58:02 +00:00
HANDLE ctx_thread = CreateThread ( nullptr , 0L , & dump_thread , & dump_thread_data , 0L , nullptr ) ;
2011-08-14 19:17:39 +00:00
WaitForSingleObject ( ctx_thread , INFINITE ) ;
CloseHandle ( this_thread ) ;
CloseHandle ( ctx_thread ) ;
// Unhook _wassert/_assert
UnhookAssert ( ) ;
// Call old _wassert/_assert
assert_func ( expression , file , line ) ;
// If we get here: rehook
HookAssert ( & assertion_handler ) ;
}
}
2011-08-19 10:53:48 +00:00
# endif
2011-08-14 19:17:39 +00:00
2011-03-01 12:28:13 +00:00
void InstallCrashHandler ( )
{
2013-11-10 18:20:29 +00:00
// Disable process-wide callback filter for exceptions on Windows Vista.
// Newer versions of Windows already get this disabled by the application
// manifest. Without turning this off, we won't be able to handle crashes
// inside window procedures on 64-bit Windows, regardless of whether we
// are 32 or 64 bit ourselves.
typedef BOOL ( WINAPI * SetProcessUserModeExceptionPolicyProc ) ( DWORD ) ;
typedef BOOL ( WINAPI * GetProcessUserModeExceptionPolicyProc ) ( LPDWORD ) ;
HMODULE kernel32 = LoadLibrary ( TEXT ( " kernel32 " ) ) ;
const SetProcessUserModeExceptionPolicyProc SetProcessUserModeExceptionPolicy =
( SetProcessUserModeExceptionPolicyProc ) GetProcAddress ( kernel32 , " SetProcessUserModeExceptionPolicy " ) ;
const GetProcessUserModeExceptionPolicyProc GetProcessUserModeExceptionPolicy =
( GetProcessUserModeExceptionPolicyProc ) GetProcAddress ( kernel32 , " GetProcessUserModeExceptionPolicy " ) ;
# ifndef PROCESS_CALLBACK_FILTER_ENABLED
# define PROCESS_CALLBACK_FILTER_ENABLED 0x1
# endif
if ( SetProcessUserModeExceptionPolicy & & GetProcessUserModeExceptionPolicy )
{
DWORD flags ;
if ( GetProcessUserModeExceptionPolicy ( & flags ) )
{
SetProcessUserModeExceptionPolicy ( flags & ~ PROCESS_CALLBACK_FILTER_ENABLED ) ;
}
}
FreeLibrary ( kernel32 ) ;
2011-03-01 12:28:13 +00:00
SetUnhandledExceptionFilter ( GenerateDump ) ;
2011-08-19 10:53:48 +00:00
# ifndef NDEBUG
2015-02-21 18:40:35 +00:00
// Hook _wassert/_assert, unless we're running under a debugger
if ( ! IsDebuggerPresent ( ) )
HookAssert ( & assertion_handler ) ;
2011-08-19 10:53:48 +00:00
# endif
2011-03-01 12:28:13 +00:00
}
# else
void InstallCrashHandler ( )
{
// no-op
}
2011-10-03 11:09:51 +00:00
# endif // HAVE_DBGHELP