456 lines
13 KiB
C++
456 lines
13 KiB
C++
|
/*++
|
||
|
|
||
|
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
|
||
|
|