/*++ Copyright (c) 2000-2002 Microsoft Corporation Module Name: IgnoreException.cpp Abstract: This shim is for handling exceptions that get thrown by bad apps. The primary causes of unhandled exceptions are: 1. Priviliged mode instructions: cli, sti, out etc 2. Access violations In most cases, ignoring an Access Violation will be fatal for the app, but it works in some cases, eg: Deer Hunter 2 - their 3d algorithm reads too far back in a lookup buffer. This is a game bug and doesn't crash win9x because that memory is usually allocated. Interstate 76 also requires a Divide by Zero exception to be ignored. Notes: This is a general purpose shim. History: 02/10/2000 linstev Created 10/17/2000 maonis Bug fix - now it ignores AVs correctly. 02/27/2001 robkenny Converted to use CString 02/15/2002 robkenny Shim was copying data into a temp buffer without verifying that the buffer was large enough. Cleaned up some signed/unsigned comparison mismatch. --*/ #include "precomp.h" IMPLEMENT_SHIM_BEGIN(IgnoreException) #include "ShimHookMacro.h" APIHOOK_ENUM_BEGIN APIHOOK_ENUM_END // Exception code for OutputDebugString #define DBG_EXCEPTION 0x40010000L // Determine how to manage second chance exceptions BOOL g_bWin2000 = FALSE; DWORD g_dwLastEip = 0; extern DWORD GetInstructionLengthFromAddress(LPBYTE pEip); typedef enum { eActive = 0, eFirstChance, eSecondChance, eExitProcess } EMODE; WCHAR * ToWchar(EMODE emode) { switch (emode) { case eActive: return L"Active"; case eFirstChance: return L"FirstChance"; case eSecondChance: return L"SecondChance"; case eExitProcess: return L"ExitProcess"; }; return L"ERROR"; } // Convert a text version of EMODE to a EMODE value EMODE ToEmode(const CString & csMode) { if (csMode.Compare(L"0") == 0 || csMode.Compare(ToWchar(eActive)) == 0) { return eActive; } else if (csMode.Compare(L"1") == 0 || csMode.Compare(ToWchar(eFirstChance)) == 0) { return eFirstChance; } else if (csMode.Compare(L"2") == 0 || csMode.Compare(ToWchar(eSecondChance)) == 0) { return eSecondChance; } else if (csMode.Compare(L"3") == 0 || csMode.Compare(ToWchar(eExitProcess)) == 0) { return eExitProcess; } // Default value return eFirstChance; } static const DWORD DONT_CARE = 0xFFFFFFFF; /*++ This is the list of all the exceptions that this shim can handle. The fields are 1. cName - the name of the exception as accepted as a parameter and displayed in debug spew 2. dwCode - the exception code 3. dwSubCode - parameters specified by the exception: -1 = don't care 4. dwIgnore - ignore this exception: 0 = don't ignore 1 = ignore 1st chance 2 = ignore 2nd chance 3 = exit process on 2nd chance. --*/ struct EXCEPT { WCHAR * cName; DWORD dwCode; DWORD dwSubCode; EMODE dwIgnore; }; static EXCEPT g_eList[] = { {L"ACCESS_VIOLATION_READ" , (DWORD)EXCEPTION_ACCESS_VIOLATION , 0 , eActive}, {L"ACCESS_VIOLATION_WRITE" , (DWORD)EXCEPTION_ACCESS_VIOLATION , 1 , eActive}, {L"ARRAY_BOUNDS_EXCEEDED" , (DWORD)EXCEPTION_ARRAY_BOUNDS_EXCEEDED , DONT_CARE, eActive}, {L"BREAKPOINT" , (DWORD)EXCEPTION_BREAKPOINT , DONT_CARE, eActive}, {L"DATATYPE_MISALIGNMENT" , (DWORD)EXCEPTION_DATATYPE_MISALIGNMENT , DONT_CARE, eActive}, {L"FLT_DENORMAL_OPERAND" , (DWORD)EXCEPTION_FLT_DENORMAL_OPERAND , DONT_CARE, eActive}, {L"FLT_DIVIDE_BY_ZERO" , (DWORD)EXCEPTION_FLT_DIVIDE_BY_ZERO , DONT_CARE, eActive}, {L"FLT_INEXACT_RESULT" , (DWORD)EXCEPTION_FLT_INEXACT_RESULT , DONT_CARE, eActive}, {L"FLT_INVALID_OPERATION" , (DWORD)EXCEPTION_FLT_INVALID_OPERATION , DONT_CARE, eActive}, {L"FLT_OVERFLOW" , (DWORD)EXCEPTION_FLT_OVERFLOW , DONT_CARE, eActive}, {L"FLT_STACK_CHECK" , (DWORD)EXCEPTION_FLT_STACK_CHECK , DONT_CARE, eActive}, {L"FLT_UNDERFLOW" , (DWORD)EXCEPTION_FLT_UNDERFLOW , DONT_CARE, eActive}, {L"ILLEGAL_INSTRUCTION" , (DWORD)EXCEPTION_ILLEGAL_INSTRUCTION , DONT_CARE, eActive}, {L"IN_PAGE_ERROR" , (DWORD)EXCEPTION_IN_PAGE_ERROR , DONT_CARE, eActive}, {L"INT_DIVIDE_BY_ZERO" , (DWORD)EXCEPTION_INT_DIVIDE_BY_ZERO , DONT_CARE, eActive}, {L"INT_OVERFLOW" , (DWORD)EXCEPTION_INT_OVERFLOW , DONT_CARE, eActive}, {L"INVALID_DISPOSITION" , (DWORD)EXCEPTION_INVALID_DISPOSITION , DONT_CARE, eActive}, {L"NONCONTINUABLE_EXCEPTION" , (DWORD)EXCEPTION_NONCONTINUABLE_EXCEPTION, DONT_CARE, eActive}, {L"PRIV_INSTRUCTION" , (DWORD)EXCEPTION_PRIV_INSTRUCTION , DONT_CARE, eFirstChance}, {L"SINGLE_STEP" , (DWORD)EXCEPTION_SINGLE_STEP , DONT_CARE, eActive}, {L"STACK_OVERFLOW" , (DWORD)EXCEPTION_STACK_OVERFLOW , DONT_CARE, eActive}, {L"INVALID_HANDLE" , (DWORD)EXCEPTION_INVALID_HANDLE , DONT_CARE, eActive} }; #define ELISTSIZE sizeof(g_eList) / sizeof(g_eList[0]) /*++ Custom exception handler. --*/ LONG ExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) { DWORD dwCode = ExceptionInfo->ExceptionRecord->ExceptionCode; if ((dwCode & DBG_EXCEPTION) == DBG_EXCEPTION) // for the DebugPrints { return EXCEPTION_CONTINUE_SEARCH; } CONTEXT *lpContext = ExceptionInfo->ContextRecord; const WCHAR * szException = L"Unknown"; BOOL bIgnore = FALSE; // // Run the list of exceptions to see if we're ignoring it // for (int i = 0; i < ELISTSIZE; i++) { const EXCEPT *pE = g_eList + i; // Matched the major exception code if (dwCode == pE->dwCode) { // See if we care about the subcode if ((pE->dwSubCode != DONT_CARE) && (ExceptionInfo->ExceptionRecord->ExceptionInformation[0] != pE->dwSubCode)) { continue; } szException = pE->cName; // Determine how to handle the exception switch (pE->dwIgnore) { case eActive: bIgnore = FALSE; break; case eFirstChance: bIgnore = TRUE; break; case eSecondChance: bIgnore = g_bWin2000 || (g_dwLastEip == lpContext->Eip); g_dwLastEip = lpContext->Eip; break; case eExitProcess: // Try using unhandled exception filters to catch this bIgnore = TRUE;//g_bWin2000 || IsBadCodePtr((FARPROC)lpContext->Eip); if (bIgnore) { ExitProcess(0); } g_dwLastEip = lpContext->Eip; break; } if (bIgnore) break; } } // // Dump out the exception // DPFN( eDbgLevelWarning, "Exception %S (%08lx)\n", szException, dwCode); #ifdef DBG DPFN( eDbgLevelWarning, "eip=%08lx\n", lpContext->Eip); DPFN( eDbgLevelWarning, "eax=%08lx, ebx=%08lx, ecx=%08lx, edx=%08lx\n", lpContext->Eax, lpContext->Ebx, lpContext->Ecx, lpContext->Edx); DPFN( eDbgLevelWarning, "esi=%08lx, edi=%08lx, esp=%08lx, ebp=%08lx\n", lpContext->Esi, lpContext->Edi, lpContext->Esp, lpContext->Ebp); DPFN( eDbgLevelWarning, "cs=%04lx, ss=%04lx, ds=%04lx, es=%04lx, fs=%04lx, gs=%04lx\n", lpContext->SegCs, lpContext->SegSs, lpContext->SegDs, lpContext->SegEs, lpContext->SegFs, lpContext->SegGs); #endif LONG lRet; if (bIgnore) { if ((DWORD)lpContext->Eip <= (DWORD)0xFFFF) { LOGN( eDbgLevelError, "[ExceptionFilter] Exception %S (%08X), stuck at bad address, killing current thread.", szException, dwCode); lRet = EXCEPTION_CONTINUE_SEARCH; return lRet; } LOGN( eDbgLevelWarning, "[ExceptionFilter] Exception %S (%08X) ignored.", szException, dwCode); lpContext->Eip += GetInstructionLengthFromAddress((LPBYTE)lpContext->Eip); g_dwLastEip = 0; lRet = EXCEPTION_CONTINUE_EXECUTION; } else { DPFN( eDbgLevelWarning, "Exception NOT handled\n\n"); lRet = EXCEPTION_CONTINUE_SEARCH; } return lRet; } /*++ Parse the command line for particular exceptions. The format of the command line is: [EXCEPTION_NAME[:0|1|2]];[EXCEPTION_NAME[:0|1|2]]... or "*" which ignores all first chance exceptions. Eg: ACCESS_VIOLATION:2;PRIV_INSTRUCTION:0;BREAKPOINT Will ignore: 1. Access violations - second chance 2. Priviliged mode instructions - do not ignore 3. Breakpoints - ignore --*/ BOOL ParseCommandLine( LPCSTR lpCommandLine ) { CSTRING_TRY { CStringToken csTok(lpCommandLine, L" ;"); // // Run the string, looking for exception names // CString token; // Each cl token may be followed by a : and an exception type // Forms can be: // * // *:SecondChance // INVALID_DISPOSITION // INVALID_DISPOSITION:Active // INVALID_DISPOSITION:0 // while (csTok.GetToken(token)) { CStringToken csSingleTok(token, L":"); CString csExcept; CString csType; // grab the exception name and the exception type csSingleTok.GetToken(csExcept); csSingleTok.GetToken(csType); // Convert ignore value to emode (defaults to eFirstChance) EMODE emode = ToEmode(csType); if (token.Compare(L"*") == 0) { for (int i = 0; i < ELISTSIZE; i++) { g_eList[i].dwIgnore = emode; } } else { // Find the exception specified for (int i = 0; i < ELISTSIZE; i++) { if (csExcept.CompareNoCase(g_eList[i].cName) == 0) { g_eList[i].dwIgnore = emode; break; } } } } } CSTRING_CATCH { return FALSE; } // // Dump results of command line parse // DPFN( eDbgLevelInfo, "===================================\n"); DPFN( eDbgLevelInfo, " Ignore Exception \n"); DPFN( eDbgLevelInfo, "===================================\n"); DPFN( eDbgLevelInfo, " 1 = First chance \n"); DPFN( eDbgLevelInfo, " 2 = Second chance \n"); DPFN( eDbgLevelInfo, " 3 = ExitProcess on second chance \n"); DPFN( eDbgLevelInfo, "-----------------------------------\n"); for (int i = 0; i < ELISTSIZE; i++) { if (g_eList[i].dwIgnore != eActive) { DPFN( eDbgLevelInfo, "%S %S\n", ToWchar(g_eList[i].dwIgnore), g_eList[i].cName); } } DPFN( eDbgLevelInfo, "-----------------------------------\n"); return TRUE; } /*++ Register hooked functions --*/ BOOL NOTIFY_FUNCTION( DWORD fdwReason) { if (fdwReason == DLL_PROCESS_ATTACH) { // Run the command line to check for adjustments to defaults if (!ParseCommandLine(COMMAND_LINE)) { return FALSE; } // Try to find new exception handler _pfn_RtlAddVectoredExceptionHandler pfnExcept; pfnExcept = (_pfn_RtlAddVectoredExceptionHandler) GetProcAddress( GetModuleHandle(L"NTDLL.DLL"), "RtlAddVectoredExceptionHandler"); if (pfnExcept) { (_pfn_RtlAddVectoredExceptionHandler) pfnExcept( 0, (PVOID)ExceptionFilter); g_bWin2000 = FALSE; } else { // Windows 2000 reverts back to the old method which unluckily // doesn't get called for C++ exceptions SetUnhandledExceptionFilter(ExceptionFilter); g_bWin2000 = TRUE; } } return TRUE; } HOOK_BEGIN CALL_NOTIFY_FUNCTION HOOK_END IMPLEMENT_SHIM_END