979 lines
25 KiB
C++
979 lines
25 KiB
C++
#include "global.h"
|
|
|
|
// from VirtualDub
|
|
// Copyright (C) 1998-2001 Avery Lee
|
|
//
|
|
// This program 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 2 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, write to the Free Software
|
|
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
// DO NOT USE stdio.h! printf() calls malloc()!
|
|
//#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <crtdbg.h>
|
|
|
|
#include <windows.h>
|
|
#include <tlhelp32.h>
|
|
|
|
#include "resource.h"
|
|
#include "crash.h"
|
|
#include "ProductInfo.h"
|
|
|
|
#include "RageLog.h" /* for RageLog::GetAdditionalLog and Flush */
|
|
#include "RageThreads.h" /* for GetCheckpointLogs */
|
|
#include "PrefsManager.h" /* for g_bAutoRestart */
|
|
#include "RestartProgram.h"
|
|
|
|
#include "GotoURL.h"
|
|
|
|
static HFONT hFontMono = NULL;
|
|
|
|
static void DoSave();
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
#define CODE_WINDOW (256)
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
extern HINSTANCE g_hInstance;
|
|
extern unsigned long version_num;
|
|
|
|
|
|
|
|
extern HINSTANCE g_hInstance;
|
|
#define BACKTRACE_MAX_SIZE 100
|
|
|
|
// WARNING: This is called from crash-time conditions! No malloc() or new!!!
|
|
|
|
#define malloc not_allowed_here
|
|
#define new not_allowed_here
|
|
|
|
|
|
|
|
static void SpliceProgramPath(char *buf, int bufsiz, const char *fn) {
|
|
char tbuf[MAX_PATH];
|
|
char *pszFile;
|
|
|
|
GetModuleFileName(NULL, tbuf, sizeof tbuf);
|
|
GetFullPathName(tbuf, bufsiz, buf, &pszFile);
|
|
strcpy(pszFile, fn);
|
|
}
|
|
|
|
struct VDDebugInfoContext
|
|
{
|
|
VDDebugInfoContext() { pRVAHeap=NULL; }
|
|
bool Loaded() const { return pRVAHeap != NULL; }
|
|
void *pRawBlock;
|
|
|
|
int nBuildNumber;
|
|
|
|
const unsigned char *pRVAHeap;
|
|
unsigned nFirstRVA;
|
|
|
|
const char *pFuncNameHeap;
|
|
const unsigned long (*pSegments)[2];
|
|
int nSegments;
|
|
};
|
|
|
|
|
|
static VDDebugInfoContext g_debugInfo;
|
|
|
|
BOOL APIENTRY CrashDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
static const struct ExceptionLookup {
|
|
DWORD code;
|
|
const char *name;
|
|
} exceptions[]={
|
|
{ EXCEPTION_ACCESS_VIOLATION, "Access Violation" },
|
|
{ EXCEPTION_BREAKPOINT, "Breakpoint" },
|
|
{ EXCEPTION_FLT_DENORMAL_OPERAND, "FP Denormal Operand" },
|
|
{ EXCEPTION_FLT_DIVIDE_BY_ZERO, "FP Divide-by-Zero" },
|
|
{ EXCEPTION_FLT_INEXACT_RESULT, "FP Inexact Result" },
|
|
{ EXCEPTION_FLT_INVALID_OPERATION, "FP Invalid Operation" },
|
|
{ EXCEPTION_FLT_OVERFLOW, "FP Overflow", },
|
|
{ EXCEPTION_FLT_STACK_CHECK, "FP Stack Check", },
|
|
{ EXCEPTION_FLT_UNDERFLOW, "FP Underflow", },
|
|
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Integer Divide-by-Zero", },
|
|
{ EXCEPTION_INT_OVERFLOW, "Integer Overflow", },
|
|
{ EXCEPTION_PRIV_INSTRUCTION, "Privileged Instruction", },
|
|
{ EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction" },
|
|
{ EXCEPTION_INVALID_HANDLE, "Invalid handle" },
|
|
{ 0xe06d7363, "Unhandled Microsoft C++ Exception", },
|
|
// hmm... '_msc'... gee, who would have thought?
|
|
{ NULL },
|
|
};
|
|
|
|
static const char *LookupException( DWORD code )
|
|
{
|
|
for( int i = 0; exceptions[i].code; ++i )
|
|
if( exceptions[i].code == code )
|
|
return exceptions[i].name;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct CrashInfo
|
|
{
|
|
char m_CrashReason[256];
|
|
const void *m_BacktracePointers[BACKTRACE_MAX_SIZE];
|
|
|
|
const void *m_AlternateThreadBacktrace[BACKTRACE_MAX_SIZE];
|
|
|
|
CrashInfo()
|
|
{
|
|
m_CrashReason[0] = 0;
|
|
m_BacktracePointers[0] = m_AlternateThreadBacktrace[0] = NULL;
|
|
}
|
|
};
|
|
|
|
static CrashInfo g_CrashInfo;
|
|
static void GetReason( const EXCEPTION_RECORD *pRecord, CrashInfo *crash )
|
|
{
|
|
// fill out bomb reason
|
|
const char *reason = LookupException( pRecord->ExceptionCode );
|
|
|
|
if( reason == NULL )
|
|
wsprintf( crash->m_CrashReason, "Crash reason: unknown exception 0x%08lx", pRecord->ExceptionCode );
|
|
else
|
|
{
|
|
strcpy( crash->m_CrashReason, "Crash reason: " );
|
|
strcat( crash->m_CrashReason, reason );
|
|
}
|
|
}
|
|
|
|
long VDDebugInfoLookupRVA(VDDebugInfoContext *pctx, unsigned rva, char *buf, int buflen);
|
|
bool VDDebugInfoInitFromMemory(VDDebugInfoContext *pctx, const void *_src);
|
|
bool VDDebugInfoInitFromFile( VDDebugInfoContext *pctx );
|
|
void VDDebugInfoDeinit(VDDebugInfoContext *pctx);
|
|
|
|
|
|
extern HWND g_hWndMain;
|
|
long __stdcall CrashHandler(EXCEPTION_POINTERS *pExc)
|
|
{
|
|
/* Flush the log it isn't cut off at the end. */
|
|
/* 1. We can't do regular file access in the crash handler.
|
|
* 2. We can't access LOG itself at all, since it may not be set up or the pointer might
|
|
* be munged. We must only ever use the RageLog:: methods that access static data, that
|
|
* we're being very careful to null-terminate as needed.
|
|
*
|
|
* Logs are rarely important, anyway. Only info.txt and crashinfo.txt are needed 99%
|
|
* of the time. */
|
|
// LOG->Flush();
|
|
|
|
/* We aren't supposed to receive these exceptions. For example, if you do
|
|
* a floating point divide by zero, you should receive a result of #INF. Only
|
|
* if the floating point exception for _EM_ZERODIVIDE is unmasked does this
|
|
* exception occur, and we never unmask it.
|
|
*
|
|
* However, once in a while some driver or library turns evil and unmasks an
|
|
* exception flag on us. If this happens, re-mask it and continue execution. */
|
|
switch( pExc->ExceptionRecord->ExceptionCode )
|
|
{
|
|
case EXCEPTION_FLT_INVALID_OPERATION:
|
|
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
|
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
|
case EXCEPTION_FLT_OVERFLOW:
|
|
case EXCEPTION_FLT_UNDERFLOW:
|
|
case EXCEPTION_FLT_INEXACT_RESULT:
|
|
pExc->ContextRecord->FloatSave.ControlWord |= 0x3F;
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
|
|
RageThread::HaltAllThreads( false );
|
|
|
|
static int InHere = 0;
|
|
if( InHere > 0 )
|
|
{
|
|
/* If we get here, then we've been called recursively, which means we crashed.
|
|
* If InHere is greater than 1, then we crashed after writing the crash dump;
|
|
* say so. */
|
|
SetUnhandledExceptionFilter(NULL);
|
|
MessageBox( NULL,
|
|
InHere == 1?
|
|
"The error reporting interface has crashed.\n":
|
|
"The error reporting interface has crashed. However, crashinfo.txt was"
|
|
"written successfully to the program directory.\n",
|
|
"Fatal Error", MB_OK );
|
|
#ifdef DEBUG
|
|
DebugBreak();
|
|
#endif
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
++InHere;
|
|
/////////////////////////
|
|
|
|
hFontMono = CreateFont(
|
|
10, // nHeight
|
|
0, // nWidth
|
|
0, // nEscapement
|
|
0, // nOrientation
|
|
FW_DONTCARE, // fnWeight
|
|
FALSE, // fdwItalic
|
|
FALSE, // fdwUnderline
|
|
FALSE, // fdwStrikeOut
|
|
ANSI_CHARSET, // fdwCharSet
|
|
OUT_DEFAULT_PRECIS, // fdwOutputPrecision
|
|
CLIP_DEFAULT_PRECIS, // fdwClipPrecision
|
|
DEFAULT_QUALITY, // fdwQuality
|
|
DEFAULT_PITCH | FF_DONTCARE, // fdwPitchAndFamily
|
|
"Lucida Console"
|
|
);
|
|
|
|
if (!hFontMono)
|
|
hFontMono = (HFONT)GetStockObject(ANSI_FIXED_FONT);
|
|
|
|
// Attempt to read debug file.
|
|
|
|
VDDebugInfoInitFromFile( &g_debugInfo );
|
|
|
|
/* In case something goes amiss before the user can view the crash
|
|
* dump, save it now. */
|
|
if( !g_CrashInfo.m_CrashReason[0] )
|
|
GetReason( pExc->ExceptionRecord, &g_CrashInfo );
|
|
do_backtrace( g_CrashInfo.m_BacktracePointers, BACKTRACE_MAX_SIZE, GetCurrentProcess(), GetCurrentThread(), pExc->ContextRecord );
|
|
DoSave();
|
|
|
|
++InHere;
|
|
|
|
/* Now things get more risky. If we're fullscreen, the window will obscure the
|
|
* crash dialog. Try to hide the window. Things might blow up here; do this
|
|
* after DoSave, so we always write a crash dump. */
|
|
if( GetWindowThreadProcessId( g_hWndMain, NULL ) == GetCurrentThreadId() )
|
|
{
|
|
/* The thread that crashed was the thread that created the main window. Hide
|
|
* the window. This will also restore the video mode, if necessary. */
|
|
ShowWindow( g_hWndMain, SW_HIDE );
|
|
} else {
|
|
/* A different thread crashed. Simply kill all other windows. We can't safely
|
|
* call ShowWindow; the main thread might be deadlocked. */
|
|
RageThread::HaltAllThreads( true );
|
|
ChangeDisplaySettings(NULL, 0);
|
|
}
|
|
|
|
if( g_bAutoRestart )
|
|
Win32RestartProgram();
|
|
|
|
/* Little trick to get an HINSTANCE of ourself without having access to the hwnd ... */
|
|
{
|
|
TCHAR szFullAppPath[MAX_PATH];
|
|
GetModuleFileName(NULL, szFullAppPath, MAX_PATH);
|
|
HINSTANCE handle = LoadLibrary(szFullAppPath);
|
|
|
|
DialogBoxParam(handle, MAKEINTRESOURCE(IDD_DISASM_CRASH), NULL, CrashDlgProc, (LPARAM)pExc);
|
|
}
|
|
|
|
VDDebugInfoDeinit(&g_debugInfo);
|
|
|
|
InHere = false;
|
|
|
|
SetUnhandledExceptionFilter(NULL);
|
|
|
|
/* Forcibly terminate; if we keep going, we'll try to shut down threads and do other
|
|
* things that may deadlock, which is confusing for users. */
|
|
TerminateProcess( GetCurrentProcess(), 0 );
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
static void Report(HWND hwndList, HANDLE hFile, const char *format, ...) {
|
|
char buf[10240];
|
|
va_list val;
|
|
int ch;
|
|
|
|
va_start(val, format);
|
|
ch = wvsprintf(buf, format, val);
|
|
va_end(val);
|
|
|
|
if (hwndList)
|
|
SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)buf);
|
|
|
|
if (hFile) {
|
|
DWORD dwActual;
|
|
|
|
buf[ch] = '\r';
|
|
buf[ch+1] = '\n';
|
|
WriteFile(hFile, buf, ch+2, &dwActual, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void ReportReason( HWND hwndReason, HANDLE hFile, const CrashInfo *pCrash )
|
|
{
|
|
if( hwndReason )
|
|
SetWindowText( hwndReason, pCrash->m_CrashReason );
|
|
|
|
if( hFile )
|
|
Report( NULL, hFile, pCrash->m_CrashReason );
|
|
}
|
|
|
|
static const char *GetNameFromHeap(const char *heap, int idx)
|
|
{
|
|
while(idx--)
|
|
while(*heap++);
|
|
|
|
return heap;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
static bool IsValidCall(char *buf, int len)
|
|
{
|
|
// Permissible CALL sequences that we care about:
|
|
//
|
|
// E8 xx xx xx xx CALL near relative
|
|
// FF (group 2) CALL near absolute indirect
|
|
//
|
|
// Minimum sequence is 2 bytes (call eax).
|
|
// Maximum sequence is 7 bytes (call dword ptr [eax+disp32]).
|
|
|
|
if (len >= 5 && buf[-5] == '\xe8')
|
|
return true;
|
|
|
|
// FF 14 xx CALL [reg32+reg32*scale]
|
|
|
|
if (len >= 3 && buf[-3] == '\xff' && buf[-2]=='\x14')
|
|
return true;
|
|
|
|
// FF 15 xx xx xx xx CALL disp32
|
|
|
|
if (len >= 6 && buf[-6] == '\xff' && buf[-5]=='\x15')
|
|
return true;
|
|
|
|
// FF 00-3F(!14/15) CALL [reg32]
|
|
|
|
if (len >= 2 && buf[-2] == '\xff' && (unsigned char)buf[-1] < '\x40')
|
|
return true;
|
|
|
|
// FF D0-D7 CALL reg32
|
|
|
|
if (len >= 2 && buf[-2] == '\xff' && (buf[-1]&0xF8) == '\xd0')
|
|
return true;
|
|
|
|
// FF 50-57 xx CALL [reg32+reg32*scale+disp8]
|
|
|
|
if (len >= 3 && buf[-3] == '\xff' && (buf[-2]&0xF8) == '\x50')
|
|
return true;
|
|
|
|
// FF 90-97 xx xx xx xx xx CALL [reg32+reg32*scale+disp32]
|
|
|
|
if (len >= 7 && buf[-7] == '\xff' && (buf[-6]&0xF8) == '\x90')
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
static bool IsExecutableProtection(DWORD dwProtect) {
|
|
MEMORY_BASIC_INFORMATION meminfo;
|
|
|
|
// Windows NT/2000 allows Execute permissions, but Win9x seems to
|
|
// rip it off. So we query the permissions on our own code block,
|
|
// and use it to determine if READONLY/READWRITE should be
|
|
// considered 'executable.'
|
|
|
|
VirtualQuery(IsExecutableProtection, &meminfo, sizeof meminfo);
|
|
|
|
switch((unsigned char)dwProtect) {
|
|
case PAGE_READONLY: // *sigh* Win9x...
|
|
case PAGE_READWRITE: // *sigh*
|
|
return meminfo.Protect==PAGE_READONLY || meminfo.Protect==PAGE_READWRITE;
|
|
|
|
case PAGE_EXECUTE:
|
|
case PAGE_EXECUTE_READ:
|
|
case PAGE_EXECUTE_READWRITE:
|
|
case PAGE_EXECUTE_WRITECOPY:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const char *CrashGetModuleBaseName(HMODULE hmod, char *pszBaseName) {
|
|
char szPath1[MAX_PATH];
|
|
char szPath2[MAX_PATH];
|
|
|
|
__try {
|
|
DWORD dw;
|
|
char *pszFile, *period = NULL;
|
|
|
|
if (!GetModuleFileName(hmod, szPath1, sizeof szPath1))
|
|
return NULL;
|
|
|
|
dw = GetFullPathName(szPath1, sizeof szPath2, szPath2, &pszFile);
|
|
|
|
if (!dw || dw>sizeof szPath2)
|
|
return NULL;
|
|
|
|
strcpy(pszBaseName, pszFile);
|
|
|
|
pszFile = pszBaseName;
|
|
|
|
while(*pszFile++)
|
|
if (pszFile[-1]=='.')
|
|
period = pszFile-1;
|
|
|
|
if (period)
|
|
*period = 0;
|
|
} __except(1) {
|
|
return NULL;
|
|
}
|
|
|
|
return pszBaseName;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool VDDebugInfoInitFromMemory(VDDebugInfoContext *pctx, const void *_src) {
|
|
const unsigned char *src = (const unsigned char *)_src;
|
|
|
|
pctx->pRVAHeap = NULL;
|
|
|
|
if (memcmp((char *)src, "StepMania symbolic debug information", 36))
|
|
return false;
|
|
|
|
// Extract fields
|
|
|
|
src += 64;
|
|
|
|
pctx->nBuildNumber = *(int *)src;
|
|
pctx->pRVAHeap = (const unsigned char *)(src + 20);
|
|
pctx->nFirstRVA = *(const long *)(src + 16);
|
|
pctx->pFuncNameHeap = (const char *)pctx->pRVAHeap - 4 + *(const long *)(src + 4);
|
|
pctx->pSegments = (unsigned long (*)[2])(pctx->pFuncNameHeap + *(const long *)(src + 8));
|
|
pctx->nSegments = *(const long *)(src + 12);
|
|
|
|
return true;
|
|
}
|
|
|
|
void VDDebugInfoDeinit(VDDebugInfoContext *pctx) {
|
|
if (pctx->pRawBlock) {
|
|
VirtualFree(pctx->pRawBlock, 0, MEM_RELEASE);
|
|
pctx->pRawBlock = NULL;
|
|
}
|
|
}
|
|
|
|
bool VDDebugInfoInitFromFile( VDDebugInfoContext *pctx )
|
|
{
|
|
if( pctx->Loaded() )
|
|
return true;
|
|
|
|
char pszFilename[1024];
|
|
SpliceProgramPath(pszFilename, sizeof(pszFilename), "StepMania.vdi");
|
|
|
|
pctx->pRawBlock = NULL;
|
|
pctx->pRVAHeap = NULL;
|
|
|
|
HANDLE h = CreateFile(pszFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (INVALID_HANDLE_VALUE == h)
|
|
return false;
|
|
|
|
do {
|
|
DWORD dwFileSize = GetFileSize(h, NULL);
|
|
|
|
if (dwFileSize == 0xFFFFFFFF)
|
|
break;
|
|
|
|
pctx->pRawBlock = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
|
|
if (!pctx->pRawBlock)
|
|
break;
|
|
|
|
DWORD dwActual;
|
|
if (!ReadFile(h, pctx->pRawBlock, dwFileSize, &dwActual, NULL) || dwActual != dwFileSize)
|
|
break;
|
|
|
|
if (VDDebugInfoInitFromMemory(pctx, pctx->pRawBlock)) {
|
|
CloseHandle(h);
|
|
return true;
|
|
}
|
|
|
|
VirtualFree(pctx->pRawBlock, 0, MEM_RELEASE);
|
|
|
|
} while(false);
|
|
|
|
VDDebugInfoDeinit(pctx);
|
|
CloseHandle(h);
|
|
return false;
|
|
}
|
|
|
|
static bool PointerIsInAnySegment( const VDDebugInfoContext *pctx, unsigned rva )
|
|
{
|
|
for( int i=0; i<pctx->nSegments; ++i )
|
|
{
|
|
if (rva >= pctx->pSegments[i][0] && rva < pctx->pSegments[i][0] + pctx->pSegments[i][1])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
long VDDebugInfoLookupRVA(VDDebugInfoContext *pctx, unsigned rva, char *buf, int buflen)
|
|
{
|
|
if ( !PointerIsInAnySegment(pctx, rva) )
|
|
return -1;
|
|
|
|
const unsigned char *pr = pctx->pRVAHeap;
|
|
const unsigned char *pr_limit = (const unsigned char *)pctx->pFuncNameHeap;
|
|
int idx = 0;
|
|
|
|
// Linearly unpack RVA deltas and find lower_bound
|
|
|
|
rva -= pctx->nFirstRVA;
|
|
|
|
if ((signed)rva < 0)
|
|
return -1;
|
|
|
|
while(pr < pr_limit) {
|
|
unsigned char c;
|
|
unsigned diff = 0;
|
|
|
|
do {
|
|
c = *pr++;
|
|
|
|
diff = (diff << 7) | (c & 0x7f);
|
|
} while(c & 0x80);
|
|
|
|
rva -= diff;
|
|
|
|
if ((signed)rva < 0) {
|
|
rva += diff;
|
|
break;
|
|
}
|
|
|
|
++idx;
|
|
}
|
|
if (pr >= pr_limit)
|
|
return -1;
|
|
|
|
// Decompress name for RVA
|
|
const char *fn_name = GetNameFromHeap(pctx->pFuncNameHeap, idx);
|
|
|
|
if (!*fn_name)
|
|
fn_name = "(special)";
|
|
|
|
strncpy( buf, fn_name, buflen );
|
|
buf[buflen-1]=0;
|
|
|
|
return rva;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
#include "ddk/dbghelp.h"
|
|
#pragma comment(lib, "ddk/dbghelp.lib")
|
|
|
|
static bool InitDbghelp()
|
|
{
|
|
static bool initted = false;
|
|
if( !initted )
|
|
{
|
|
SymSetOptions( SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS );
|
|
|
|
if (!SymInitialize(GetCurrentProcess(), NULL, TRUE))
|
|
return false;
|
|
|
|
initted = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static SYMBOL_INFO *GetSym( unsigned long ptr, DWORD64 &disp )
|
|
{
|
|
InitDbghelp();
|
|
|
|
static BYTE buffer[1024];
|
|
SYMBOL_INFO *pSymbol = (PSYMBOL_INFO)buffer;
|
|
|
|
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
pSymbol->MaxNameLen = sizeof(buffer) - sizeof(SYMBOL_INFO) + 1;
|
|
|
|
if (!SymFromAddr(GetCurrentProcess(), ptr, &disp, pSymbol))
|
|
return NULL;
|
|
|
|
return pSymbol;
|
|
}
|
|
|
|
static const char *Demangle( const char *buf )
|
|
{
|
|
if( !InitDbghelp() )
|
|
return buf;
|
|
|
|
static char obuf[1024];
|
|
if( !UnDecorateSymbolName(buf, obuf, sizeof(obuf),
|
|
UNDNAME_COMPLETE
|
|
| UNDNAME_NO_CV_THISTYPE
|
|
| UNDNAME_NO_ALLOCATION_MODEL
|
|
| UNDNAME_NO_ACCESS_SPECIFIERS // no public:
|
|
| UNDNAME_NO_MS_KEYWORDS // no __cdecl
|
|
) )
|
|
{
|
|
return buf;
|
|
}
|
|
|
|
if( obuf[0] == '_' )
|
|
{
|
|
strcat( obuf, "()" ); /* _main -> _main() */
|
|
return obuf+1; /* _main -> main */
|
|
}
|
|
|
|
return obuf;
|
|
}
|
|
|
|
static bool PointsToValidCall( unsigned long ptr )
|
|
{
|
|
char buf[7];
|
|
int len = 7;
|
|
|
|
memset( buf, 0, sizeof(buf) );
|
|
|
|
while(len > 0 && !ReadProcessMemory(GetCurrentProcess(), (void *)(ptr-len), buf+7-len, len, NULL))
|
|
--len;
|
|
|
|
return IsValidCall(buf+7, len);
|
|
}
|
|
|
|
void do_backtrace( const void **buf, size_t size,
|
|
HANDLE hProcess, HANDLE hThread, const CONTEXT *pContext )
|
|
{
|
|
// Retrieve stack pointers.
|
|
const char *pStackBase;
|
|
{
|
|
LDT_ENTRY sel;
|
|
if( !GetThreadSelectorEntry( hThread, pContext->SegFs, &sel ) )
|
|
{
|
|
buf[0] = NULL;
|
|
return;
|
|
}
|
|
|
|
const NT_TIB *tib = (NT_TIB *) ((sel.HighWord.Bits.BaseHi<<24)+(sel.HighWord.Bits.BaseMid<<16)+sel.BaseLow);
|
|
const NT_TIB *pTib = tib->Self;
|
|
pStackBase = (char *)pTib->StackBase;
|
|
}
|
|
|
|
// Walk up the stack.
|
|
const char *lpAddr = (const char *)pContext->Esp;
|
|
|
|
const void *data = (void *) pContext->Eip;
|
|
size_t i = 0;
|
|
do {
|
|
if( i+1 >= size )
|
|
break;
|
|
|
|
bool fValid = true;
|
|
MEMORY_BASIC_INFORMATION meminfo;
|
|
|
|
VirtualQuery((void *)data, &meminfo, sizeof meminfo);
|
|
|
|
if (!IsExecutableProtection(meminfo.Protect) || meminfo.State!=MEM_COMMIT)
|
|
fValid = false;
|
|
|
|
if ( data != (void *) pContext->Eip && !PointsToValidCall((unsigned long)data) )
|
|
fValid = false;
|
|
|
|
if( fValid )
|
|
buf[i++] = data;
|
|
|
|
if (lpAddr >= pStackBase)
|
|
break;
|
|
|
|
lpAddr += 4;
|
|
} while( ReadProcessMemory(hProcess, lpAddr-4, &data, 4, NULL));
|
|
|
|
buf[i++] = NULL;
|
|
}
|
|
|
|
void SymLookup( const void *ptr, char *buf )
|
|
{
|
|
VDDebugInfoInitFromFile( &g_debugInfo );
|
|
if( !g_debugInfo.Loaded() )
|
|
{
|
|
strcpy( buf, "error" );
|
|
return;
|
|
}
|
|
|
|
MEMORY_BASIC_INFORMATION meminfo;
|
|
VirtualQuery( ptr, &meminfo, sizeof meminfo );
|
|
|
|
char tmp[512];
|
|
if( VDDebugInfoLookupRVA(&g_debugInfo, (unsigned int)ptr, tmp, sizeof(tmp)) >= 0 )
|
|
{
|
|
wsprintf( buf, "%08x: %s", ptr, Demangle(tmp) );
|
|
return;
|
|
}
|
|
|
|
char szName[MAX_PATH];
|
|
if( !CrashGetModuleBaseName((HMODULE)meminfo.AllocationBase, szName) )
|
|
strcpy( szName, "???" );
|
|
|
|
DWORD64 disp;
|
|
SYMBOL_INFO *pSymbol = GetSym( (unsigned int)ptr, disp );
|
|
|
|
if( pSymbol )
|
|
{
|
|
wsprintf( buf, "%08lx: %s!%s [%08lx+%lx+%lx]",
|
|
(unsigned long) ptr, szName, pSymbol->Name,
|
|
(unsigned long) meminfo.AllocationBase,
|
|
(unsigned long) (pSymbol->Address) - (unsigned long) (meminfo.AllocationBase),
|
|
(unsigned long) disp);
|
|
return;
|
|
}
|
|
|
|
wsprintf( buf, "%08lx: %s!%08lx",
|
|
(unsigned long) ptr, szName,
|
|
(unsigned long) meminfo.AllocationBase );
|
|
}
|
|
|
|
static bool ReportCallStack( HWND hwnd, HANDLE hFile, const void **Backtrace )
|
|
{
|
|
VDDebugInfoInitFromFile( &g_debugInfo );
|
|
if( !g_debugInfo.Loaded() )
|
|
{
|
|
Report(hwnd, hFile, "Could not open debug resource file (StepMania.vdi).");
|
|
return false;
|
|
}
|
|
|
|
if( g_debugInfo.nBuildNumber != int(version_num) )
|
|
{
|
|
Report(hwnd, hFile, "Incorrect StepMania.vdi file (build %d, expected %d) for this version of " PRODUCT_NAME " -- call stack unavailable.", g_debugInfo.nBuildNumber, int(version_num));
|
|
return false;
|
|
}
|
|
|
|
for( int i = 0; Backtrace[i]; ++i )
|
|
{
|
|
char buf[10240];
|
|
SymLookup( Backtrace[i], buf );
|
|
Report( hwnd, hFile, "%s", buf );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WriteBuf( HANDLE hFile, const char *buf )
|
|
{
|
|
DWORD dwActual;
|
|
WriteFile(hFile, buf, strlen(buf), &dwActual, NULL);
|
|
}
|
|
|
|
static void DoSave()
|
|
{
|
|
char szModName2[MAX_PATH];
|
|
|
|
SpliceProgramPath(szModName2, sizeof szModName2, "../crashinfo.txt");
|
|
|
|
HANDLE hFile = CreateFile(szModName2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (INVALID_HANDLE_VALUE == hFile)
|
|
return;
|
|
|
|
Report(NULL, hFile,
|
|
"%s crash report (build %d)\r\n"
|
|
"--------------------------------------"
|
|
"\r\n", PRODUCT_NAME_VER, version_num);
|
|
|
|
ReportReason( NULL, hFile, &g_CrashInfo );
|
|
Report(NULL, hFile, "");
|
|
|
|
// Dump thread stacks
|
|
WriteBuf( hFile, Checkpoints::GetLogs("\r\n") );
|
|
Report(NULL, hFile, "");
|
|
|
|
ReportCallStack( NULL, hFile, g_CrashInfo.m_BacktracePointers );
|
|
Report(NULL, hFile, "");
|
|
|
|
if( g_CrashInfo.m_AlternateThreadBacktrace[0] )
|
|
{
|
|
Report( NULL, hFile, "Deadlocked with:" );
|
|
Report( NULL, hFile, "" );
|
|
|
|
ReportCallStack( NULL, hFile, g_CrashInfo.m_AlternateThreadBacktrace );
|
|
Report(NULL, hFile, "");
|
|
}
|
|
|
|
Report(NULL, hFile, "Static log:");
|
|
WriteBuf( hFile, RageLog::GetInfo() );
|
|
WriteBuf( hFile, RageLog::GetAdditionalLog() );
|
|
Report(NULL, hFile, "");
|
|
|
|
Report(NULL, hFile, "Partial log:");
|
|
int i = 0;
|
|
while( const char *p = RageLog::GetRecentLog( i++ ) )
|
|
Report(NULL, hFile, "%s", p);
|
|
Report(NULL, hFile, "");
|
|
|
|
Report(NULL, hFile, "-- End of report");
|
|
CloseHandle(hFile);
|
|
}
|
|
|
|
void ViewWithNotepad(const char *str)
|
|
{
|
|
char buf[256] = "";
|
|
strcat(buf, "notepad.exe ");
|
|
strcat(buf, str);
|
|
|
|
char cwd[MAX_PATH];
|
|
SpliceProgramPath(cwd, MAX_PATH, "");
|
|
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFO si;
|
|
ZeroMemory( &si, sizeof(si) );
|
|
|
|
CreateProcess(
|
|
NULL, // pointer to name of executable module
|
|
buf, // pointer to command line string
|
|
NULL, // process security attributes
|
|
NULL, // thread security attributes
|
|
false, // handle inheritance flag
|
|
0, // creation flags
|
|
NULL, // pointer to new environment block
|
|
cwd, // pointer to current directory name
|
|
&si, // pointer to STARTUPINFO
|
|
&pi // pointer to PROCESS_INFORMATION
|
|
);
|
|
}
|
|
|
|
BOOL APIENTRY CrashDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static bool s_bHaveCallstack;
|
|
|
|
switch(msg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
HWND hwndList = GetDlgItem(hDlg, IDC_CALL_STACK);
|
|
HWND hwndReason = GetDlgItem(hDlg, IDC_STATIC_BOMBREASON);
|
|
|
|
if (hFontMono)
|
|
SendMessage(hwndList, WM_SETFONT, (WPARAM)hFontMono, MAKELPARAM(TRUE, 0));
|
|
|
|
ReportReason( hwndReason, NULL, &g_CrashInfo );
|
|
s_bHaveCallstack = ReportCallStack( hwndList, NULL, g_CrashInfo.m_BacktracePointers );
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
switch(LOWORD(wParam)) {
|
|
case IDC_BUTTON_CLOSE:
|
|
EndDialog(hDlg, FALSE);
|
|
return TRUE;
|
|
case IDOK:
|
|
// EndDialog(hDlg, TRUE); /* don't always exit on ENTER */
|
|
return TRUE;
|
|
case IDC_VIEW_LOG:
|
|
ViewWithNotepad("../log.txt");
|
|
break;
|
|
case IDC_CRASH_SAVE:
|
|
if (!s_bHaveCallstack)
|
|
if (IDOK != MessageBox(hDlg,
|
|
PRODUCT_NAME " cannot load its crash resource file, and thus the crash dump will be "
|
|
"missing the most important part, the call stack. Crash dumps are much less useful "
|
|
"without the call stack.",
|
|
PRODUCT_NAME " warning", MB_OK|MB_ICONEXCLAMATION))
|
|
return TRUE;
|
|
|
|
ViewWithNotepad("../crashinfo.txt");
|
|
return TRUE;
|
|
case IDC_BUTTON_RESTART:
|
|
{
|
|
char cwd[MAX_PATH];
|
|
SpliceProgramPath(cwd, MAX_PATH, "");
|
|
|
|
TCHAR szFullAppPath[MAX_PATH];
|
|
GetModuleFileName(NULL, szFullAppPath, MAX_PATH);
|
|
|
|
// Launch StepMania
|
|
PROCESS_INFORMATION pi;
|
|
STARTUPINFO si;
|
|
ZeroMemory( &si, sizeof(si) );
|
|
|
|
CreateProcess(
|
|
NULL, // pointer to name of executable module
|
|
szFullAppPath, // pointer to command line string
|
|
NULL, // process security attributes
|
|
NULL, // thread security attributes
|
|
false, // handle inheritance flag
|
|
0, // creation flags
|
|
NULL, // pointer to new environment block
|
|
cwd, // pointer to current directory name
|
|
&si, // pointer to STARTUPINFO
|
|
&pi // pointer to PROCESS_INFORMATION
|
|
);
|
|
}
|
|
EndDialog( hDlg, FALSE );
|
|
break;
|
|
case IDC_BUTTON_REPORT:
|
|
GotoURL( "http://sourceforge.net/tracker/?func=add&group_id=37892&atid=421366" );
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void NORETURN debug_crash()
|
|
{
|
|
__try {
|
|
__asm xor ebx,ebx
|
|
__asm mov eax,dword ptr [ebx]
|
|
// __asm mov dword ptr [ebx],eax
|
|
// __asm lock add dword ptr cs:[00000000h], 12345678h
|
|
} __except(CrashHandler((EXCEPTION_POINTERS*)_exception_info())) {
|
|
}
|
|
}
|
|
|
|
/* Get a stack trace of the current thread and the specified thread. */
|
|
void NORETURN Crash_BacktraceThread( HANDLE hThread )
|
|
{
|
|
if( hThread == NULL )
|
|
{
|
|
strcpy( g_CrashInfo.m_CrashReason, "Crash reason: Thread deadlock (with NULL?)" );
|
|
debug_crash();
|
|
}
|
|
|
|
#if 0
|
|
/* Ugh. GetThreadId is XP-only or 2003 Server-only or something. How can we do this? */
|
|
if( GetThreadId( hThread ) == GetCurrentThreadId() )
|
|
{
|
|
/* hThread is the current thread. This shouldn't happen. */
|
|
strcpy( g_CrashInfo.m_CrashReason, "Crash reason: Thread deadlock (with the current thread?)" );
|
|
debug_crash();
|
|
}
|
|
#endif
|
|
|
|
/* Suspend the other thread we're going to backtrace. (We need to at least suspend
|
|
* hThread, for GetThreadContext to work.) */
|
|
RageThread::HaltAllThreads( false );
|
|
// SuspendThread( hThread );
|
|
|
|
CONTEXT context;
|
|
context.ContextFlags = CONTEXT_FULL;
|
|
if( !GetThreadContext( hThread, &context ) )
|
|
{
|
|
strcpy( g_CrashInfo.m_CrashReason, "Crash reason: Thread deadlock (GetThreadContext failed)" );
|
|
debug_crash();
|
|
}
|
|
|
|
static const void *BacktracePointers[BACKTRACE_MAX_SIZE];
|
|
do_backtrace( g_CrashInfo.m_AlternateThreadBacktrace, BACKTRACE_MAX_SIZE, GetCurrentProcess(), hThread, &context );
|
|
|
|
strcpy( g_CrashInfo.m_CrashReason, "Crash reason: Thread deadlock" );
|
|
debug_crash();
|
|
}
|
|
|