/*++ Copyright (c) 1998 Microsoft Corporation Module Name: crash.c Abstract: This file contains the code necessary to generate a a user mode dump of the current app. The code will essentially take a non-fatal snapshot and allow the debugging session to continue. Author: Carlos Klapp (Sept. 24, 98) Environment: Win32, User Mode --*/ #include "precomp.h" #pragma hdrstop #include #include "dbugexcp.h" /************************** Data declaration *************************/ #define MEM_SIZE (64*1024) /****** Publics ********/ extern LPPD LppdCommand; extern LPTD LptdCommand; extern LPSHF Lpshf; // vector table for symbol handler /****** Externs from ??? *******/ // these are here only so that we can link // with crashlib. they are only referenced // when reading a kernel mode crash dump extern "C" { ULONG64 KiProcessors[1]; ULONG64 KiPcrBaseAddress[1]; }; /************************** Private Structures *************************/ typedef struct _tagMODULELIST { LPMODULE_LIST pModList; LPMODULE_ENTRY pModEntry; DWORD dwModIdx; BOOL bFinished; } MODULELIST_WCRASH, * PMODULELIST_WCRASH; // private data structure use for communcating // crash dump data to the callback function typedef struct _tagDEBUGPACKET { //HWND hwnd; //HANDLE hEvent; //OPTIONS options; //DWORD dwPidToDebug; //HANDLE hEventToSignal; //DWORD dwProcessId; HPID hpid; HTID htid; TCHAR szProcessName[_MAX_PATH]; DWORD dwBaseOfImage; DWORD dwImageSize; LPTD lptdThreadListHead; //LIST_ENTRY ThreadList; //PTHREADCONTEXT tctx; //DWORD stackBase; //DWORD stackRA; DEBUG_EVENT DebugEvent; //DWORD ExitStatus; } DEBUGPACKET, *PDEBUGPACKET; typedef struct _CRASH_DUMP_INFO { // TRUE - stop all processing // FALSE - continue processing BOOL bFatalError; // Module specific data MODULELIST_WCRASH modlst; DEBUGPACKET dp; EXCEPTION_DEBUG_INFO *pExceptionInfo; DWORD MemoryCount; DWORD_PTR Address; PUCHAR pMemoryData; MEMORY_BASIC_INFORMATION mbi; SIZE_T MbiOffset; SIZE_T MbiRemaining; LPTD lptdCurThread; // LIST_ENTRY ThreadList; PCRASH_MODULE CrashModule; } CRASH_DUMP_INFO, *PCRASH_DUMP_INFO; // Used by the sort algorithm to make sure that the // main module is the first in the list static TCHAR szMainModule[MAX_PATH]; /******************* Func Declarations *************************/ BOOL GetThreadContext_CrashDump(LPTD, LPCONTEXT, UINT); void ConvertMemInfoFromOSDFmtToSysFmt(MEMORY_BASIC_INFORMATION *, MEMINFO *); int __cdecl SkewedSortModEntryByAddress(const void *, const void *); UINT WINAPI DHGetDebuggeeBytes(ADDR addr, UINT cb, void * lpb); /************************** Code *************************/ BOOL WindbgCrashDumpCallback( DWORD dwDataType, PVOID *ppvDumpData, LPDWORD pdwDumpDataLength, PVOID pvData ) /*++ Routine Description: This function is the callback used by crashlib. Its purpose is to provide data to DmpCreateUserDump() for writting to the crashdump file. Arguments: dwDataType - requested data type ppvDumpData - pointer to a pointer to the data pdwDumpDataLength - pointer to the data length CrashdumpInfo - DrWatson private data Return Value: TRUE - continue calling back for the requested data type FALSE - stop calling back and go on to the next data type --*/ { PCRASH_DUMP_INFO pCrashdumpInfo = (PCRASH_DUMP_INFO) pvData; DWORD cb; if (pCrashdumpInfo->bFatalError) { *pdwDumpDataLength = 0; return FALSE; } switch( dwDataType ) { case DMP_DEBUG_EVENT: Assert(sizeof(pCrashdumpInfo->dp.DebugEvent) == sizeof(DEBUG_EVENT)); *ppvDumpData = &pCrashdumpInfo->dp.DebugEvent; *pdwDumpDataLength = sizeof(DEBUG_EVENT); break; case DMP_THREAD_STATE: { static CRASH_THREAD CrashThread; TST tst = {0}; // BUGBUG no 64 bit support *ppvDumpData = &CrashThread; if (pCrashdumpInfo->lptdCurThread == NULL) { pCrashdumpInfo->lptdCurThread = pCrashdumpInfo->dp.lptdThreadListHead; } else { pCrashdumpInfo->lptdCurThread = pCrashdumpInfo->lptdCurThread->lptdNext; } if (NULL == pCrashdumpInfo->lptdCurThread) { pCrashdumpInfo->lptdCurThread = NULL; return FALSE; } ZeroMemory(&CrashThread, sizeof(CrashThread)); CrashThread.ThreadId = HandleToUlong(pCrashdumpInfo->lptdCurThread->htid); if (xosdNone != OSDGetThreadStatus( pCrashdumpInfo->lptdCurThread->lppd->hpid, pCrashdumpInfo->lptdCurThread->htid, &tst)) { CmdLogFmt("Crash warning: No status for thread %d\r\n", pCrashdumpInfo->lptdCurThread->itid); CrashThread.SuspendCount = (DWORD) -1; } else { CrashThread.SuspendCount = tst.dwSuspendCount; /*if (CrashThread.SuspendCount != (DWORD)-1) { ResumeThread(ptctx->hThread); }*/ // BUGBUG 64 bit CrashThread.Teb = (DWORD)tst.dwTeb; Assert(tst.dwPriorityMax <= 31); if (tst.dwPriorityMax < 16) { // NOT realtime if (tst.dwPriority <= 6) { CrashThread.PriorityClass = IDLE_PRIORITY_CLASS; CrashThread.Priority = tst.dwPriority - 4; } else if (tst.dwPriority <= 11) { CrashThread.PriorityClass = NORMAL_PRIORITY_CLASS; CrashThread.Priority = tst.dwPriority - 9; } else if (tst.dwPriority <= 15) { CrashThread.PriorityClass = HIGH_PRIORITY_CLASS; if (15 == tst.dwPriority) { CrashThread.Priority = THREAD_PRIORITY_TIME_CRITICAL; } else { CrashThread.Priority = tst.dwPriority - 13; } } } else { CrashThread.PriorityClass = REALTIME_PRIORITY_CLASS; if (15 == tst.dwPriority) { CrashThread.Priority = THREAD_PRIORITY_TIME_CRITICAL; } else { CrashThread.Priority = tst.dwPriority - 4; } } } *pdwDumpDataLength = sizeof(CRASH_THREAD); } break; case DMP_MEMORY_BASIC_INFORMATION: while( TRUE ) { MEMINFO meminfo = {0}; pCrashdumpInfo->Address += pCrashdumpInfo->mbi.RegionSize; meminfo.addr.addr.off = pCrashdumpInfo->Address; if (xosdNone != OSDGetMemoryInformation(pCrashdumpInfo->dp.hpid, NULL, &meminfo)) { return FALSE; } ConvertMemInfoFromOSDFmtToSysFmt(&pCrashdumpInfo->mbi, &meminfo); if ((pCrashdumpInfo->mbi.AllocationProtect & PAGE_GUARD) || (pCrashdumpInfo->mbi.AllocationProtect & PAGE_NOACCESS)) { continue; } if ((pCrashdumpInfo->mbi.State & MEM_FREE) || (pCrashdumpInfo->mbi.State & MEM_RESERVE)) { continue; } break; } *ppvDumpData = &pCrashdumpInfo->mbi; *pdwDumpDataLength = sizeof(MEMORY_BASIC_INFORMATION); break; case DMP_THREAD_CONTEXT: { static CONTEXT context; if (pCrashdumpInfo->lptdCurThread == NULL) { pCrashdumpInfo->lptdCurThread = pCrashdumpInfo->dp.lptdThreadListHead; } else { pCrashdumpInfo->lptdCurThread = pCrashdumpInfo->lptdCurThread->lptdNext; } if (NULL == pCrashdumpInfo->lptdCurThread) { pCrashdumpInfo->lptdCurThread = NULL; return FALSE; } ZeroMemory(&context, sizeof(context)); if (!GetThreadContext_CrashDump(pCrashdumpInfo->lptdCurThread, &context, sizeof(context))) { CmdLogFmt("!!!Crash error: No context for thread %d\r\n", pCrashdumpInfo->lptdCurThread->itid); pCrashdumpInfo->bFatalError = TRUE; } *ppvDumpData = &context; *pdwDumpDataLength = sizeof(CONTEXT); } break; case DMP_MODULE: // Initialize module list if (!pCrashdumpInfo->modlst.pModList) { if (xosdNone != OSDGetModuleList( LppdCur->hpid, LptdCur->htid, NULL, &pCrashdumpInfo->modlst.pModList)) { // Error obtaining mod list CmdLogFmt("!!!Crash error: Unable to obtain module list\r\n"); pCrashdumpInfo->bFatalError = TRUE; // Fall thru to cleanup pCrashdumpInfo->modlst.bFinished = TRUE; } else { // Obtained mod list if (ModuleListCount(pCrashdumpInfo->modlst.pModList) > 0) { // Necessary for everything to function. This sort will // always place the process before the modules. It then // sorts the modules by mem address. qsort(FirstModuleEntry(pCrashdumpInfo->modlst.pModList), ModuleListCount(pCrashdumpInfo->modlst.pModList), sizeof(MODULE_ENTRY), SkewedSortModEntryByAddress); // Get the first module entry pCrashdumpInfo->modlst.pModEntry = FirstModuleEntry(pCrashdumpInfo->modlst.pModList); pCrashdumpInfo->modlst.dwModIdx = 0; } else { CmdLogFmt("!!!Crash error: Module list is empty\r\n"); pCrashdumpInfo->bFatalError = TRUE; // Fall thru to cleanup pCrashdumpInfo->modlst.bFinished = TRUE; } } } // Cleanup ?? if (pCrashdumpInfo->modlst.bFinished) { // Cleanup if (pCrashdumpInfo->modlst.pModList) { MHFree(pCrashdumpInfo->modlst.pModList); } ZeroMemory(&pCrashdumpInfo->modlst, sizeof(pCrashdumpInfo->modlst)); return FALSE; } // Process the current module // BUGBUG 64 bit pCrashdumpInfo->CrashModule->BaseOfImage = (DWORD)ModuleEntryBase(pCrashdumpInfo->modlst.pModEntry); pCrashdumpInfo->CrashModule->SizeOfImage = (DWORD)ModuleEntryLimit(pCrashdumpInfo->modlst.pModEntry); // Get module name { PSTR pszImgName = SHGetExeName( (HEXE)ModuleEntryEmi(pCrashdumpInfo->modlst.pModEntry) ); Assert(pszImgName); pCrashdumpInfo->CrashModule->ImageNameLength = strlen(pszImgName) + 1; strcpy(pCrashdumpInfo->CrashModule->ImageName, pszImgName); } *pdwDumpDataLength = sizeof(CRASH_MODULE) + pCrashdumpInfo->CrashModule->ImageNameLength; *ppvDumpData = pCrashdumpInfo->CrashModule; // Get the next module pCrashdumpInfo->modlst.dwModIdx++; if (pCrashdumpInfo->modlst.dwModIdx < ModuleListCount(pCrashdumpInfo->modlst.pModList)) { pCrashdumpInfo->modlst.pModEntry = NextModuleEntry(pCrashdumpInfo->modlst.pModEntry); } else { pCrashdumpInfo->modlst.bFinished = TRUE; } break; case DMP_MEMORY_DATA: { MEMINFO meminfo = {0}; if (!pCrashdumpInfo->MemoryCount) { pCrashdumpInfo->Address = 0; pCrashdumpInfo->MbiOffset = 0; pCrashdumpInfo->MbiRemaining = 0; ZeroMemory( &pCrashdumpInfo->mbi, sizeof(MEMORY_BASIC_INFORMATION) ); pCrashdumpInfo->pMemoryData = (PUCHAR) VirtualAlloc( NULL, MEM_SIZE, MEM_COMMIT, PAGE_READWRITE ); } if (!pCrashdumpInfo->MbiRemaining) { while( TRUE ) { pCrashdumpInfo->Address += pCrashdumpInfo->mbi.RegionSize; meminfo.addr.addr.off = pCrashdumpInfo->Address; if (xosdNone != OSDGetMemoryInformation(pCrashdumpInfo->dp.hpid, NULL, &meminfo)) { if (pCrashdumpInfo->pMemoryData) { VirtualFree( pCrashdumpInfo->pMemoryData, MEM_SIZE, MEM_RELEASE ); } return FALSE; } ConvertMemInfoFromOSDFmtToSysFmt(&pCrashdumpInfo->mbi, &meminfo); if ((pCrashdumpInfo->mbi.Protect & PAGE_GUARD) || (pCrashdumpInfo->mbi.Protect & PAGE_NOACCESS)) { continue; } if ((pCrashdumpInfo->mbi.State & MEM_FREE) || (pCrashdumpInfo->mbi.State & MEM_RESERVE)) { continue; } pCrashdumpInfo->MbiOffset = 0; pCrashdumpInfo->MbiRemaining = pCrashdumpInfo->mbi.RegionSize; pCrashdumpInfo->MemoryCount += 1; break; } } *pdwDumpDataLength = (DWORD)__min( pCrashdumpInfo->MbiRemaining, MEM_SIZE ); pCrashdumpInfo->MbiRemaining -= *pdwDumpDataLength; meminfo.addr.addr.off = ((UOFFSET)pCrashdumpInfo->mbi.BaseAddress + pCrashdumpInfo->MbiOffset); if (xosdNone != OSDReadMemory(pCrashdumpInfo->dp.hpid, pCrashdumpInfo->dp.htid, &meminfo.addr, pCrashdumpInfo->pMemoryData, *pdwDumpDataLength, &cb)) { CmdLogFmt( "Crash warning: could not read memory at 0x%I64X\n", meminfo.addr.addr.off); } *ppvDumpData = pCrashdumpInfo->pMemoryData; pCrashdumpInfo->MbiOffset += *pdwDumpDataLength; } break; } return TRUE; } LOGERR LogCrash( LPSTR pszFileNameArg, DWORD /*dwUnused*/ ) { LOGERR logerr = LOGERROR_NOERROR; PTSTR pszDumpName = NULL; // This should have been handled by the "LogDotCommand" function Assert(!g_contWorkspace_WkSp.m_bKernelDebugger); // Error checking CmdInsertInit(); // don't generate a dump of a dump if (g_contWorkspace_WkSp.m_bUserCrashDump || (g_contWorkspace_WkSp.m_bKernelDebugger && g_contKernelDbgPreferences_WkSp.m_bUseCrashDump)) { CmdLogFmt( "Crash: <.crash> is not allowed for crash dumps\n" ); return LOGERROR_QUIET; } // Process must be stopped if (IsProcRunning( LppdCur )) { CmdLogFmt( "Crash: process must first be stopped\n" ); return LOGERROR_QUIET; } PreRunInvalid(); PDWildInvalid(); TDWildInvalid(); // debuggee has to be active if (!DebuggeeActive()) { CmdLogFmt("Crash: There is not process to dump\r\n"); return LOGERROR_QUIET; } // Skip over any leading blanks pszDumpName = CPSkipWhitespace(pszFileNameArg); // Check for no argument if (!*pszDumpName) { CmdLogFmt("Crash: Must specify a file name\r\n"); return LOGERROR_QUIET; } // We finally get to do something // Initialize structures CRASH_DUMP_INFO CrashdumpInfo = {0}; // Init CrashdumpInfo *szMainModule = NULL; // Get the stopped thread { TCHAR szExt[_MAX_EXT] = {0}; LPTD lptd = LppdCur->lptdList; for (; lptd; lptd = lptd->lptdNext) { if (tsStopped == lptd->tstate || tsException2 == lptd->tstate) { CrashdumpInfo.dp.hpid = lptd->lppd->hpid;; CrashdumpInfo.dp.htid = lptd->htid; CrashdumpInfo.dp.lptdThreadListHead = lptd->lppd->lptdList; _tsplitpath(lptd->lppd->lpBaseExeName, NULL, NULL, szMainModule, szExt); _tcscat(szMainModule, szExt); break; } } if (NULL == CrashdumpInfo.dp.hpid) { CmdLogFmt("Crash error: There is no stopped thread on which to base the dump\r\n"); goto CLEANUP; } } // Get the last debug event for the stopped thread { PIOCTLGENERIC pig; DWORD dw; pig = (PIOCTLGENERIC)calloc(sizeof(IOCTLGENERIC) + sizeof(DEBUG_EVENT), 1); if (!pig) { CmdLogFmt("!!!!!Crash error: Unable to allocate memory\r\n"); logerr = LOGERROR_QUIET; goto CLEANUP; } pig->ioctlSubType = IG_DEBUG_EVENT; pig->length = sizeof(DEBUG_EVENT); if (xosdNone != OSDSystemService( CrashdumpInfo.dp.hpid, CrashdumpInfo.dp.htid, (SSVC) ssvcGeneric, (LPV)pig, sizeof(IOCTLGENERIC) + sizeof(DEBUG_EVENT), &dw )) { CmdLogFmt("!!!!!Crash error: Unable to obtain debug event\r\n"); logerr = LOGERROR_QUIET; goto CLEANUP; } Assert(sizeof(DEBUG_EVENT) == pig->length); memcpy(&CrashdumpInfo.dp.DebugEvent, pig->data, sizeof(DEBUG_EVENT)); free( pig ); } CrashdumpInfo.pExceptionInfo = &CrashdumpInfo.dp.DebugEvent.u.Exception; CrashdumpInfo.CrashModule = (PCRASH_MODULE) calloc( 4096, 1); // Write the dump file out CmdLogFmt("Crash: generating dump file. This could take a while.\r\n"); if (DmpCreateUserDump(pszDumpName, WindbgCrashDumpCallback, &CrashdumpInfo)) { CmdLogFmt("Crash: success\r\n"); } else { CmdLogFmt("Crash: error generating dump file\r\n"); logerr = LOGERROR_QUIET; } CLEANUP: if (CrashdumpInfo.CrashModule) { free(CrashdumpInfo.CrashModule); } *szMainModule = NULL; return logerr; } BOOL GetThreadContext_CrashDump( LPTD lptd, LPCONTEXT lpContext, UINT uSize ) /*++ Routine Description: This function gets thread context. Arguments: lpContext - Supplies pointer to CONTEXT strructure. cbSizeOfContext - Supplies the size of CONTEXT structure. Return Value: TRUE for success; FALSE for failure. --*/ { DWORD dw = 0; Assert(uSize < ULONG_MAX); return OSDSystemService( lptd->lppd->hpid, lptd->htid, (SSVC) ssvcGetThreadContext, lpContext, (DWORD) uSize, &dw ) == xosdNone; Assert(dw == uSize); } void ConvertMemInfoFromOSDFmtToSysFmt( MEMORY_BASIC_INFORMATION * pmiSys, MEMINFO * pmiOSD ) { Assert(pmiOSD); Assert(pmiSys); ZeroMemory(pmiSys, sizeof(*pmiSys)); // BUGBUG 64 bit pmiSys->BaseAddress = (PVOID) pmiOSD->addr.addr.off; pmiSys->AllocationBase = (PVOID) pmiOSD->addrAllocBase.addr.off; pmiSys->RegionSize = (DWORD)pmiOSD->uRegionSize; pmiSys->Protect = pmiOSD->dwProtect; pmiSys->State = pmiOSD->dwState; pmiSys->Type = pmiOSD->dwType; pmiSys->AllocationProtect = pmiOSD->dwAllocationProtect; } int __cdecl SkewedSortModEntryByAddress( const void *pMod1, const void *pMod2 ) /*++ The main module will always be first --*/ { TCHAR szFullName[_MAX_PATH] = {0}; TCHAR szExt[_MAX_EXT] = {0}; // Build the full name of the modules _tsplitpath(SHGetExeName( (HEXE)ModuleEntryEmi((LPMODULE_ENTRY)pMod1) ), NULL, NULL, szFullName, szExt); _tcscat(szFullName, szExt); // Found the main module? if (!_tcsicmp(szMainModule, szFullName)) { return -1; } _tsplitpath(SHGetExeName( (HEXE)ModuleEntryEmi((LPMODULE_ENTRY)pMod2) ), NULL, NULL, szFullName, szExt); _tcscat(szFullName, szExt); // Found the main module? if (!_tcsicmp(szMainModule, szFullName)) { return 1; } return ModuleEntryBase((LPMODULE_ENTRY)pMod1) < ModuleEntryBase((LPMODULE_ENTRY)pMod2) ? -1 : 1; }