#include "stdafx.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 #include #include #include #include #include "resource.h" #include "crash.h" #include "disasm.h" #include "CrashList.h" // GotoURL pulls in MFC stuff that we really shouldn't have here. // #include "RageUtil.h" // for GotoURL HINSTANCE GotoURL(const char *url); static void DoSave(const EXCEPTION_POINTERS *pExc); /* in StepMania.cpp */ void CleanupForRestart(); /////////////////////////////////////////////////////////////////////////// #define CODE_WINDOW (256) /////////////////////////////////////////////////////////////////////////// extern HINSTANCE g_hInstance; extern unsigned long version_num; static CodeDisassemblyWindow *g_pcdw; struct VDDebugInfoContext { void *pRawBlock; int nBuildNumber; const unsigned char *pRVAHeap; unsigned nFirstRVA; const char *pClassNameHeap; 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 void ReportCrashLog(HWND hwnd, HANDLE hFile); /////////////////////////////////////////////////////////////////////////// #ifdef _DEBUG /* void checkfpustack(const char *file, const int line) throw() { static const char szFPUProblemCaption[]="FPU/MMX internal problem"; static const char szFPUProblemMessage[]="The FPU stack wasn't empty! Tagword = %04x\nFile: %s, line %d"; static bool seenmsg=false; char buf[128]; unsigned short tagword; if (seenmsg) return; __asm fnstenv buf tagword = *(unsigned short *)(buf + 8); if (tagword != 0xffff) { wsprintf(buf, szFPUProblemMessage, tagword, file, line); MessageBox(NULL, buf, szFPUProblemCaption, MB_OK); seenmsg=true; } } static void __declspec(naked) *operator new(size_t bytes) { static const char fname[]="stack trace"; __asm { push ebp mov ebp,esp push [ebp+4] ;return address push offset fname ;'filename' push _NORMAL_BLOCK ;block type push [ebp+8] ;allocation size call _malloc_dbg add esp,16 pop ebp ret } } */ #endif #if 0 void __declspec(naked) stackcheck(void *&sp) { static const char g_szStackHemorrhage[]="WARNING: Thread is hemorrhaging stack space!\n"; __asm { mov eax,[esp+4] mov ecx,[eax] or ecx,ecx jnz started mov [eax],esp ret started: sub ecx,esp mov eax,ecx sar ecx,31 xor eax,ecx sub eax,ecx cmp eax,128 jb ok push offset g_szStackHemorrhage call dword ptr [OutputDebugString] int 3 ok: ret } } #endif /////////////////////////////////////////////////////////////////////////// // // Nina's per-thread debug logs are really handy, so I back-ported // them to 1.x. These are lightweight in comparison, however. // VirtualDubThreadState __declspec(thread) g_PerThreadState; __declspec(thread) struct { ListNode node; VirtualDubThreadState *pState; } g_PerThreadStateNode; class VirtualDubThreadStateNode : public ListNode2 { public: VirtualDubThreadState *pState; }; static CRITICAL_SECTION g_csPerThreadState; static List2 g_listPerThreadState; static LONG g_nThreadsTrackedMinusOne = -1; void VirtualDubInitializeThread(const char *pszName) { DWORD dwThreadId = GetCurrentThreadId(); if (!InterlockedIncrement(&g_nThreadsTrackedMinusOne)) { InitializeCriticalSection(&g_csPerThreadState); } EnterCriticalSection(&g_csPerThreadState); if (DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), (HANDLE *)&g_PerThreadState.hThread, NULL, FALSE, DUPLICATE_SAME_ACCESS)) { g_PerThreadState.pszThreadName = pszName; g_PerThreadState.dwThreadId = dwThreadId; g_PerThreadStateNode.pState = &g_PerThreadState; g_listPerThreadState.AddTail((ListNode2 *)&g_PerThreadStateNode); } LeaveCriticalSection(&g_csPerThreadState); } void VirtualDubDeinitializeThread() { EnterCriticalSection(&g_csPerThreadState); ((ListNode2 *)&g_PerThreadStateNode)->Remove(); LeaveCriticalSection(&g_csPerThreadState); if (g_PerThreadState.pszThreadName) CloseHandle((HANDLE)g_PerThreadState.hThread); InterlockedDecrement(&g_nThreadsTrackedMinusOne); } /////////////////////////////////////////////////////////////////////////// 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 }, }; long VDDebugInfoLookupRVA(VDDebugInfoContext *pctx, unsigned rva, char *buf, int buflen); bool VDDebugInfoInitFromMemory(VDDebugInfoContext *pctx, const void *_src); bool VDDebugInfoInitFromFile(VDDebugInfoContext *pctx, const char *pszFilename); void VDDebugInfoDeinit(VDDebugInfoContext *pctx); 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); } long CrashSymLookup(VDDisassemblyContext *pctx, unsigned long virtAddr, char *buf, int buf_len) { if (!g_debugInfo.pRVAHeap) return -1; return VDDebugInfoLookupRVA(&g_debugInfo, virtAddr, buf, buf_len); } static char szEmergencyDumpName[MAX_PATH]; static void DoSaveEmergencyDump(const char *buf, int siz) { HANDLE hFile; SpliceProgramPath(szEmergencyDumpName, sizeof szEmergencyDumpName, "crashdump.dat"); hFile = CreateFile(szEmergencyDumpName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) return; DWORD dwActual; WriteFile(hFile, buf, siz, &dwActual, NULL); FlushFileBuffers(hFile); CloseHandle(hFile); } static void DoEraseEmergencyDump() { DeleteFile(szEmergencyDumpName); } #include "RageSound.h" extern HWND g_hWndMain; long __stdcall CrashHandler(EXCEPTION_POINTERS *pExc) { ///////////////////////// // // QUICKLY: SUSPEND ALL THREADS THAT AREN'T US. EnterCriticalSection(&g_csPerThreadState); try { DWORD dwCurrentThread = GetCurrentThreadId(); for(List2::fwit it = g_listPerThreadState.begin(); it; ++it) { const VirtualDubThreadState *pState = it->pState; if (pState->dwThreadId && pState->dwThreadId != dwCurrentThread) { SuspendThread((HANDLE)pState->hThread); } } } catch(...) { } LeaveCriticalSection(&g_csPerThreadState); static bool InHere = false; if(InHere) { /* If we get here, then we've been called recursively, which means * we (the disassembler or related code) crashed. If this happens, * we're not in very good shape. We've left the emergency dump; * that's all we have to go by. Tell this to the user, and then die. * * We've already blown up twice, and this is our last line of defense. * If this dies, we have nothing, so keep it simple. */ SetUnhandledExceptionFilter(NULL); MessageBox( NULL, "The error reporting interface has crashed.\n" "Please report a bug and attach the file \"crashdump.dat\"\n" "to the report.", "Fatal Error", MB_OK ); #ifdef _DEBUG DebugBreak(); #endif return EXCEPTION_EXECUTE_HANDLER; } InHere=true; ///////////////////////// static char buf[CODE_WINDOW+16]; HANDLE hprMe = GetCurrentProcess(); void *lpBaseAddress = pExc->ExceptionRecord->ExceptionAddress; char *lpAddr = (char *)((long)lpBaseAddress & -32); memset(buf, 0, sizeof buf); if ((unsigned long)lpAddr > CODE_WINDOW/2) lpAddr -= CODE_WINDOW/2; else lpAddr = NULL; if (!ReadProcessMemory(hprMe, lpAddr, buf, CODE_WINDOW, NULL)) { int i; for(i=0; iExceptionRecord; // fill out bomb reason const struct ExceptionLookup *pel = exceptions; while(pel->code) { if (pel->code == pRecord->ExceptionCode) break; ++pel; } // Unfortunately, EXCEPTION_ACCESS_VIOLATION doesn't seem to provide // us with the read/write flag and virtual address as the docs say... // *sigh* if (!pel->code) { if (hwndReason) SetWindowTitlef(hwndReason, "Crash reason: unknown exception 0x%08lx", pRecord->ExceptionCode); if (hFile) Report(hwnd, hFile, "Crash reason: unknown exception 0x%08lx", pRecord->ExceptionCode); } else { if (hwndReason) SetWindowTitlef(hwndReason, "Crash reason: %s", pel->name); if (hFile) Report(hwnd, hFile, "Crash reason: %s", pel->name); } } static void ReportThreadStacks(HWND hwnd, HANDLE hFile, const EXCEPTION_POINTERS *const pExc) { EnterCriticalSection(&g_csPerThreadState); try { for(List2::fwit it = g_listPerThreadState.begin(); it; ++it) { const VirtualDubThreadState *pState = it->pState; Report(hwnd, hFile, "Thread %08lx (%s)", pState->dwThreadId, pState->pszThreadName?pState->pszThreadName:"unknown"); for(int i=0; icp[(pState->nNextCP+i) & (CHECKPOINT_COUNT-1)]; if (cp.file) { Report(hwnd, hFile, "\t%s:%d %s", cp.file, cp.line, cp.message? cp.message:""); } } } } catch(...) { } LeaveCriticalSection(&g_csPerThreadState); } static void ReportCrashData(HWND hwnd, HANDLE hFile, const EXCEPTION_POINTERS *const pExc) { // const EXCEPTION_RECORD *const pRecord = (const EXCEPTION_RECORD *)pExc->ExceptionRecord; const CONTEXT *const pContext = (const CONTEXT *)pExc->ContextRecord; int i, tos; Report(hwnd, hFile, "EAX = %08lx", pContext->Eax); Report(hwnd, hFile, "EBX = %08lx", pContext->Ebx); Report(hwnd, hFile, "ECX = %08lx", pContext->Ecx); Report(hwnd, hFile, "EDX = %08lx", pContext->Edx); Report(hwnd, hFile, "EBP = %08lx", pContext->Ebp); Report(hwnd, hFile, "DS:ESI = %04x:%08lx", pContext->SegDs, pContext->Esi); Report(hwnd, hFile, "ES:EDI = %04x:%08lx", pContext->SegEs, pContext->Edi); Report(hwnd, hFile, "SS:ESP = %04x:%08lx", pContext->SegSs, pContext->Esp); Report(hwnd, hFile, "CS:EIP = %04x:%08lx", pContext->SegCs, pContext->Eip); Report(hwnd, hFile, "FS = %04x", pContext->SegFs); Report(hwnd, hFile, "GS = %04x", pContext->SegGs); Report(hwnd, hFile, "EFLAGS = %08lx", pContext->EFlags); Report(hwnd, hFile, ""); // extract out MMX registers tos = (pContext->FloatSave.StatusWord & 0x3800)>>11; for(i=0; i<8; i++) { long *pReg = (long *)(pContext->FloatSave.RegisterArea + 10*((i-tos) & 7)); Report(hwnd, hFile, "MM%c = %08lx%08lx", i+'0', pReg[1], pReg[0]); } } 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] == (char)0xE8) return true; // FF 14 xx CALL [reg32+reg32*scale] if (len >= 3 && buf[-3] == (char)0xFF && buf[-2]==0x14) return true; // FF 15 xx xx xx xx CALL disp32 if (len >= 6 && buf[-6] == (char)0xFF && buf[-5]==0x15) return true; // FF 00-3F(!14/15) CALL [reg32] if (len >= 2 && buf[-2] == (char)0xFF && (unsigned char)buf[-1] < 0x40) return true; // FF D0-D7 CALL reg32 if (len >= 2 && buf[-2] == (char)0xFF && (buf[-1]&0xF8) == 0xD0) return true; // FF 50-57 xx CALL [reg32+reg32*scale+disp8] if (len >= 3 && buf[-3] == (char)0xFF && (buf[-2]&0xF8) == 0x50) return true; // FF 90-97 xx xx xx xx xx CALL [reg32+reg32*scale+disp32] if (len >= 7 && buf[-7] == (char)0xFF && (buf[-6]&0xF8) == 0x90) return true; return false; } ////////////////////////////////////////////////////////////////////////////// struct ModuleInfo { const char *name; unsigned long base, size; }; // ARRGH. Where's psapi.h?!? struct Win32ModuleInfo { DWORD base, size, entry; }; typedef BOOL (__stdcall *PENUMPROCESSMODULES)(HANDLE, HMODULE *, DWORD, LPDWORD); typedef DWORD (__stdcall *PGETMODULEBASENAME)(HANDLE, HMODULE, LPTSTR, DWORD); typedef BOOL (__stdcall *PGETMODULEINFORMATION)(HANDLE, HMODULE, Win32ModuleInfo *, DWORD); typedef HANDLE (__stdcall *PCREATETOOLHELP32SNAPSHOT)(DWORD, DWORD); typedef BOOL (WINAPI *PMODULE32FIRST)(HANDLE, LPMODULEENTRY32); typedef BOOL (WINAPI *PMODULE32NEXT)(HANDLE, LPMODULEENTRY32); static ModuleInfo *CrashGetModules(void *&ptr) { void *pMem = VirtualAlloc(NULL, 65536, MEM_COMMIT, PAGE_READWRITE); if (!pMem) { ptr = NULL; return NULL; } // This sucks. If we're running under Windows 9x, we must use // TOOLHELP.DLL to get the module list. Under Windows NT, we must // use PSAPI.DLL. With Windows 2000, we can use both (but prefer // PSAPI.DLL). HMODULE hmodPSAPI = LoadLibrary("psapi.dll"); if (hmodPSAPI) { // Using PSAPI.DLL. Call EnumProcessModules(), then GetModuleFileNameEx() // and GetModuleInformation(). PENUMPROCESSMODULES pEnumProcessModules = (PENUMPROCESSMODULES)GetProcAddress(hmodPSAPI, "EnumProcessModules"); PGETMODULEBASENAME pGetModuleBaseName = (PGETMODULEBASENAME)GetProcAddress(hmodPSAPI, "GetModuleBaseNameA"); PGETMODULEINFORMATION pGetModuleInformation = (PGETMODULEINFORMATION)GetProcAddress(hmodPSAPI, "GetModuleInformation"); HMODULE *pModules, *pModules0 = (HMODULE *)((char *)pMem + 0xF000); DWORD cbNeeded; if (pEnumProcessModules && pGetModuleBaseName && pGetModuleInformation && pEnumProcessModules(GetCurrentProcess(), pModules0, 0x1000, &cbNeeded)) { ModuleInfo *pMod, *pMod0; char *pszHeap = (char *)pMem, *pszHeapLimit; if (cbNeeded > 0x1000) cbNeeded = 0x1000; pModules = (HMODULE *)((char *)pMem + 0x10000 - cbNeeded); memmove(pModules, pModules0, cbNeeded); pMod = pMod0 = (ModuleInfo *)((char *)pMem + 0x10000 - sizeof(ModuleInfo) * (cbNeeded / sizeof(HMODULE) + 1)); pszHeapLimit = (char *)pMod; do { HMODULE hCurMod = *pModules++; Win32ModuleInfo mi; if (pGetModuleBaseName(GetCurrentProcess(), hCurMod, pszHeap, pszHeapLimit - pszHeap) && pGetModuleInformation(GetCurrentProcess(), hCurMod, &mi, sizeof mi)) { char *period = NULL; pMod->name = pszHeap; while(*pszHeap++) if (pszHeap[-1] == '.') period = pszHeap-1; if (period) { *period = 0; pszHeap = period+1; } pMod->base = mi.base; pMod->size = mi.size; ++pMod; } } while((cbNeeded -= sizeof(HMODULE *)) > 0); pMod->name = NULL; FreeLibrary(hmodPSAPI); ptr = pMem; return pMod0; } FreeLibrary(hmodPSAPI); } else { // No PSAPI. Use the ToolHelp functions in KERNEL. HMODULE hmodKERNEL32 = LoadLibrary("kernel32.dll"); PCREATETOOLHELP32SNAPSHOT pCreateToolhelp32Snapshot = (PCREATETOOLHELP32SNAPSHOT)GetProcAddress(hmodKERNEL32, "CreateToolhelp32Snapshot"); PMODULE32FIRST pModule32First = (PMODULE32FIRST)GetProcAddress(hmodKERNEL32, "Module32First"); PMODULE32NEXT pModule32Next = (PMODULE32NEXT)GetProcAddress(hmodKERNEL32, "Module32Next"); HANDLE hSnap; if (pCreateToolhelp32Snapshot && pModule32First && pModule32Next) { if ((HANDLE)-1 != (hSnap = pCreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0))) { ModuleInfo *pModInfo = (ModuleInfo *)((char *)pMem + 65536); char *pszHeap = (char *)pMem; MODULEENTRY32 me; --pModInfo; pModInfo->name = NULL; me.dwSize = sizeof(MODULEENTRY32); if (pModule32First(hSnap, &me)) do { if (pszHeap+strlen(me.szModule) >= (char *)(pModInfo - 1)) break; strcpy(pszHeap, me.szModule); --pModInfo; pModInfo->name = pszHeap; char *period = NULL; while(*pszHeap++); if (pszHeap[-1]=='.') period = pszHeap-1; if (period) { *period = 0; pszHeap = period+1; } pModInfo->base = (unsigned long)me.modBaseAddr; pModInfo->size = me.modBaseSize; } while(pModule32Next(hSnap, &me)); CloseHandle(hSnap); FreeLibrary(hmodKERNEL32); ptr = pMem; return pModInfo; } } FreeLibrary(hmodKERNEL32); } VirtualFree(pMem, 0, MEM_RELEASE); ptr = NULL; return NULL; } /////////////////////////////////////////////////////////////////////////// // // info from Portable Executable/Common Object File Format (PE/COFF) spec typedef unsigned short ushort; typedef unsigned long ulong; struct PEHeader { ulong signature; ushort machine; ushort sections; ulong timestamp; ulong symbol_table; ulong symbols; ushort opthdr_size; ushort characteristics; }; struct PESectionHeader { char name[8]; ulong virtsize; ulong virtaddr; ulong rawsize; ulong rawptr; ulong relocptr; ulong linenoptr; ushort reloc_cnt; ushort lineno_cnt; ulong characteristics; }; struct PEExportDirectory { ulong flags; ulong timestamp; ushort major; ushort minor; ulong nameptr; ulong ordbase; ulong addrtbl_cnt; ulong nametbl_cnt; ulong addrtbl_ptr; ulong nametbl_ptr; ulong ordtbl_ptr; }; struct PE32OptionalHeader { ushort magic; // 0 char major_linker_ver; // 2 char minor_linker_ver; // 3 ulong codesize; // 4 ulong idatasize; // 8 ulong udatasize; // 12 ulong entrypoint; // 16 ulong codebase; // 20 ulong database; // 24 ulong imagebase; // 28 ulong section_align; // 32 ulong file_align; // 36 ushort majoros; // 40 ushort minoros; // 42 ushort majorimage; // 44 ushort minorimage; // 46 ushort majorsubsys; // 48 ushort minorsubsys; // 50 ulong reserved; // 52 ulong imagesize; // 56 ulong hdrsize; // 60 ulong checksum; // 64 ushort subsystem; // 68 ushort characteristics; // 70 ulong stackreserve; // 72 ulong stackcommit; // 76 ulong heapreserve; // 80 ulong heapcommit; // 84 ulong loaderflags; // 88 ulong dictentries; // 92 // Not part of header, but it's convienent here ulong export_RVA; // 96 ulong export_size; // 100 }; struct PE32PlusOptionalHeader { ushort magic; // 0 char major_linker_ver; // 2 char minor_linker_ver; // 3 ulong codesize; // 4 ulong idatasize; // 8 ulong udatasize; // 12 ulong entrypoint; // 16 ulong codebase; // 20 __int64 imagebase; // 24 ulong section_align; // 32 ulong file_align; // 36 ushort majoros; // 40 ushort minoros; // 42 ushort majorimage; // 44 ushort minorimage; // 46 ushort majorsubsys; // 48 ushort minorsubsys; // 50 ulong reserved; // 52 ulong imagesize; // 56 ulong hdrsize; // 60 ulong checksum; // 64 ushort subsystem; // 68 ushort characteristics; // 70 __int64 stackreserve; // 72 __int64 stackcommit; // 80 __int64 heapreserve; // 88 __int64 heapcommit; // 96 ulong loaderflags; // 104 ulong dictentries; // 108 // Not part of header, but it's convienent here ulong export_RVA; // 112 ulong export_size; // 116 }; static const char *CrashLookupExport(HMODULE hmod, unsigned long addr, unsigned long &fnbase) { char *pBase = (char *)hmod; // The PEheader offset is at hmod+0x3c. Add the size of the optional header // to step to the section headers. PEHeader *pHeader = (PEHeader *)(pBase + ((long *)hmod)[15]); if (pHeader->signature != 'EP') return NULL; #if 0 PESectionHeader *pSHdrs = (PESectionHeader *)((char *)pHeader + sizeof(PEHeader) + pHeader->opthdr_size); // Scan down the section headers and look for ".edata" int i; for(i=0; isections; i++) { MessageBox(NULL, pSHdrs[i].name, "section", MB_OK); if (!memcmp(pSHdrs[i].name, ".edata", 6)) break; } if (i >= pHeader->sections) return NULL; #endif // Verify the optional structure. PEExportDirectory *pExportDir; if (pHeader->opthdr_size < 104) return NULL; switch(*(short *)((char *)pHeader + sizeof(PEHeader))) { case 0x10b: // PE32 { PE32OptionalHeader *pOpt = (PE32OptionalHeader *)((char *)pHeader + sizeof(PEHeader)); if (pOpt->dictentries < 1) return NULL; pExportDir = (PEExportDirectory *)(pBase + pOpt->export_RVA); } break; case 0x20b: // PE32+ { PE32PlusOptionalHeader *pOpt = (PE32PlusOptionalHeader *)((char *)pHeader + sizeof(PEHeader)); if (pOpt->dictentries < 1) return NULL; pExportDir = (PEExportDirectory *)(pBase + pOpt->export_RVA); } break; default: return NULL; } // Hmmm... no exports? if ((char *)pExportDir == pBase) return NULL; // Find the location of the export information. ulong *pNameTbl = (ulong *)(pBase + pExportDir->nametbl_ptr); ulong *pAddrTbl = (ulong *)(pBase + pExportDir->addrtbl_ptr); ushort *pOrdTbl = (ushort *)(pBase + pExportDir->ordtbl_ptr); // Scan exports. const char *pszName = NULL; ulong bestdelta = 0xFFFFFFFF; unsigned i; addr -= (ulong)pBase; for(i=0; inametbl_cnt; i++) { ulong fnaddr; int idx; idx = pOrdTbl[i]; fnaddr = pAddrTbl[idx]; if (addr >= fnaddr) { ulong delta = addr - fnaddr; if (delta < bestdelta) { bestdelta = delta; fnbase = fnaddr; if (pNameTbl[i]) pszName = pBase + pNameTbl[i]; else { static char buf[8]; wsprintf(buf, "ord%d", pOrdTbl[i]); pszName = buf; } } } } return pszName; } /////////////////////////////////////////////////////////////////////////// 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; // Check type string if (!memcmp((char *)src + 6, "] StepMania disasm", 18)) src += *(long *)(src + 64) + 72; if (src[0] != '[' || src[3] != '|') return false; if (memcmp((char *)src + 6, "] StepMania symbolic debug information", 38)) return false; // Check version number // int write_version = (src[1]-'0')*10 + (src[2] - '0'); int compat_version = (src[4]-'0')*10 + (src[5] - '0'); if (compat_version > 1) return false; // resource is too new for us to load // Extract fields src += 64; pctx->nBuildNumber = *(int *)src; pctx->pRVAHeap = (const unsigned char *)(src + 24); pctx->nFirstRVA = *(const long *)(src + 20); pctx->pClassNameHeap = (const char *)pctx->pRVAHeap - 4 + *(const long *)(src + 4); pctx->pFuncNameHeap = pctx->pClassNameHeap + *(const long *)(src + 8); pctx->pSegments = (unsigned long (*)[2])(pctx->pFuncNameHeap + *(const long *)(src + 12)); pctx->nSegments = *(const long *)(src + 16); return true; } void VDDebugInfoDeinit(VDDebugInfoContext *pctx) { if (pctx->pRawBlock) { VirtualFree(pctx->pRawBlock, 0, MEM_RELEASE); pctx->pRawBlock = NULL; } } bool VDDebugInfoInitFromFile(VDDebugInfoContext *pctx, const char *pszFilename) { 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; } } while(false); VDDebugInfoDeinit(pctx); CloseHandle(h); return false; } long VDDebugInfoLookupRVA(VDDebugInfoContext *pctx, unsigned rva, char *buf, int buflen) { int i; for(i=0; inSegments; ++i) { if (rva >= pctx->pSegments[i][0] && rva < pctx->pSegments[i][0] + pctx->pSegments[i][1]) break; } if (i >= pctx->nSegments) return -1; const unsigned char *pr = pctx->pRVAHeap; const unsigned char *pr_limit = (const unsigned char *)pctx->pClassNameHeap; 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; } // Decompress name for RVA if (pr < pr_limit) { const char *fn_name = GetNameFromHeap(pctx->pFuncNameHeap, idx); const char *class_name = NULL; const char *prefix = ""; if (*fn_name == 0) return -1; if (*fn_name < 32) { int class_idx; class_idx = ((unsigned)fn_name[0] - 1)*128 + ((unsigned)fn_name[1] - 1); class_name = GetNameFromHeap(pctx->pClassNameHeap, class_idx); fn_name += 2; if (*fn_name == 1) { fn_name = class_name; } else if (*fn_name == 2) { fn_name = class_name; prefix = "~"; } else if (*fn_name < 32) fn_name = "(special)"; } // ehh... where's my wsnprintf? _snprintf() might allocate memory or locks.... return wsprintf(buf, "%s%s%s%s", class_name?class_name:"", class_name?"::":"", prefix, fn_name) >= 0 ? rva : -1; } return -1; } /////////////////////////////////////////////////////////////////////////// static bool ReportCrashCallStack(HWND hwnd, HANDLE hFile, const EXCEPTION_POINTERS *const pExc, const void *pDebugSrc) { const CONTEXT *const pContext = (const CONTEXT *)pExc->ContextRecord; HANDLE hprMe = GetCurrentProcess(); char *lpAddr = (char *)pContext->Esp; int limit = 100; unsigned long data; char buf[512]; if (!g_debugInfo.pRVAHeap) { 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 StepMania -- call stack unavailable.", g_debugInfo.nBuildNumber, int(version_num)); return false; } // Get some module names. void *pModuleMem; ModuleInfo *pModules = CrashGetModules(pModuleMem); // Retrieve stack pointers. // Not sure if NtCurrentTeb() is available on Win95.... NT_TIB *pTib; __asm { mov eax, fs:[0]_NT_TIB.Self mov pTib, eax } char *pStackBase = (char *)pTib->StackBase; // Walk up the stack. Hopefully it wasn't fscked. data = pContext->Eip; do { bool fValid = true; int len; MEMORY_BASIC_INFORMATION meminfo; VirtualQuery((void *)data, &meminfo, sizeof meminfo); if (!IsExecutableProtection(meminfo.Protect) || meminfo.State!=MEM_COMMIT) { // Report(hwnd, hFile, "Rejected: %08lx (%08lx)", data, meminfo.Protect); fValid = false; } if (data != pContext->Eip) { len = 7; *(long *)(buf + 0) = *(long *)(buf + 4) = 0; while(len > 0 && !ReadProcessMemory(GetCurrentProcess(), (void *)(data-len), buf+7-len, len, NULL)) --len; fValid &= IsValidCall(buf+7, len); } if (fValid) { if (VDDebugInfoLookupRVA(&g_debugInfo, data, buf, sizeof buf) >= 0) { Report(hwnd, hFile, "%08lx: %s()", data, buf); --limit; } else { ModuleInfo *pMods = pModules; ModuleInfo mi; char szName[MAX_PATH]; mi.name = NULL; if (pMods) { while(pMods->name) { if (data >= pMods->base && (data - pMods->base) < pMods->size) break; ++pMods; } mi = *pMods; } else { // Well, something failed, or we didn't have either PSAPI.DLL or ToolHelp // to play with. So we'll use a nastier method instead. mi.base = (unsigned long)meminfo.AllocationBase; mi.name = CrashGetModuleBaseName((HMODULE)mi.base, szName); } if (mi.name) { unsigned long fnbase; const char *pExportName = CrashLookupExport((HMODULE)mi.base, data, fnbase); if (pExportName) Report(hwnd, hFile, "%08lx: %s!%s [%08lx+%lx+%lx]", data, mi.name, pExportName, mi.base, fnbase, (data-mi.base-fnbase)); else Report(hwnd, hFile, "%08lx: %s!%08lx", data, mi.name, data - mi.base); } else Report(hwnd, hFile, "%08lx: %08lx", data, data); --limit; } } if (lpAddr >= pStackBase) break; lpAddr += 4; } while(limit > 0 && ReadProcessMemory(hprMe, lpAddr-4, &data, 4, NULL)); // All done, close up shop and exit. if (pModuleMem) VirtualFree(pModuleMem, 0, MEM_RELEASE); return true; } static void ReportDisasm(HWND hwnd, HANDLE hFile, const EXCEPTION_POINTERS *const pExc) { char tbuf[2048]; long idx = 0; Report(hwnd, hFile, "Disassembly:"); while(idx = g_pcdw->getInstruction(tbuf, idx)) { Report(hwnd, hFile, "%s", tbuf); } FlushFileBuffers(hFile); } static void DoSave(const EXCEPTION_POINTERS *pExc) { HANDLE hFile; char szModName2[MAX_PATH]; SpliceProgramPath(szModName2, sizeof szModName2, "crashinfo.txt"); hFile = CreateFile(szModName2, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hFile) return; Report(NULL, hFile, "StepMania crash report -- build %d\r\n" "--------------------------------------" "\r\n", version_num); // Detect operating system. OSVERSIONINFO ovi; ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (GetVersionEx(&ovi)) { Report(NULL, hFile, "Windows %d.%d (Win%s build %d) [%s]", ovi.dwMajorVersion, ovi.dwMinorVersion, ovi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ? (ovi.dwMinorVersion>0 ? "98" : "95") : ovi.dwPlatformId == VER_PLATFORM_WIN32_NT ? (ovi.dwMajorVersion >= 5 ? "2000" : "NT") : "?", ovi.dwBuildNumber & 0xffff, ovi.szCSDVersion); } Report(NULL, hFile, ""); ReportReason(NULL, NULL, hFile, pExc); Report(NULL, hFile, ""); // Dump thread stacks ReportThreadStacks(NULL, hFile, pExc); Report(NULL, hFile, ""); ReportCrashCallStack(NULL, hFile, pExc, g_pcdw->vdc.pExtraData); Report(NULL, hFile, ""); ReportCrashLog(NULL, hFile); Report(NULL, hFile, ""); /* We can do without these for now. People keep saying "here's a bug, * tell me if you want the crash dump" which wastes time (of course we * do!). Let's make the dump smaller to encourage just sending it, which * is what people should be doing. (If we can use the SF BTS more * consistently, instead of forums, this will be less of an issue.) */ /* ReportDisasm(NULL, hFile, pExc); Report(NULL, hFile, ""); ReportCrashData(NULL, hFile, pExc); 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 const EXCEPTION_POINTERS *s_pExc; static bool s_bHaveCallstack; switch(msg) { case WM_INITDIALOG: { HWND hwndList1 = GetDlgItem(hDlg, IDC_ASMBOX); HWND hwndList2 = GetDlgItem(hDlg, IDC_REGDUMP); HWND hwndList3 = GetDlgItem(hDlg, IDC_CALL_STACK); HWND hwndReason = GetDlgItem(hDlg, IDC_STATIC_BOMBREASON); const EXCEPTION_POINTERS *const pExc = (const EXCEPTION_POINTERS *)lParam; s_pExc = pExc; g_pcdw->DoInitListbox(hwndList1); SendMessage(hwndList2, WM_SETFONT, SendMessage(hwndList1, WM_GETFONT, 0, 0), MAKELPARAM(TRUE, 0)); SendMessage(hwndList3, WM_SETFONT, SendMessage(hwndList1, WM_GETFONT, 0, 0), MAKELPARAM(TRUE, 0)); ReportReason(NULL, hwndReason, NULL, pExc); ReportCrashData(hwndList2, NULL, pExc); s_bHaveCallstack = ReportCrashCallStack(hwndList3, NULL, pExc, g_pcdw->vdc.pExtraData); } return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_BUTTON_CLOSE: EndDialog(hDlg, FALSE); return TRUE; case IDOK: EndDialog(hDlg, TRUE); return TRUE; case IDC_VIEW_LOG: ViewWithNotepad("log.txt"); break; case IDC_CRASH_SAVE: if (!s_bHaveCallstack) if (IDOK != MessageBox(hDlg, "StepMania 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.", "StepMania warning", MB_OK|MB_ICONEXCLAMATION)) return TRUE; ViewWithNotepad("crashinfo.txt"); return TRUE; case IDC_BUTTON_RESTART: { /* Clear the startup mutex, since we're starting a new * instance before ending ourself. */ CleanupForRestart(); 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; case WM_MEASUREITEM: return g_pcdw->DoMeasureItem(lParam); case WM_DRAWITEM: return g_pcdw->DoDrawItem(lParam); } return FALSE; } void 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())) { } } /* RageLog feeds us logs; keep track of recent logs in a circular * buffer. This lets us include recent logs in the crash report. * We don't want to include RageLog, since we don't want to pull * in MFC. * * Since this is a static, preallocated region, even if it gets * trampled we'll just print garbage, not crash due to an invalid * pointer. */ static const int BACKLOG_LINES = 10; static char backlog[BACKLOG_LINES][1024]; static int backlog_start=0, backlog_cnt=0; void CrashLog(const char *str) { strncpy(backlog[backlog_start], str, sizeof(backlog[backlog_start])); backlog[backlog_start] [ sizeof(backlog[backlog_start])-1 ] = 0; backlog_start++; if(backlog_start > backlog_cnt) backlog_cnt=backlog_start; backlog_start %= BACKLOG_LINES; } static void ReportCrashLog(HWND hwnd, HANDLE hFile) { Report(NULL, hFile, "Partial log:"); int i; for(i = backlog_start; i < BACKLOG_LINES && i < backlog_cnt; ++i) Report(hwnd, hFile, "%s", backlog[i]); for(i = 0; i < backlog_start && i < backlog_cnt; ++i) Report(hwnd, hFile, "%s", backlog[i]); }