Windows2003-3790/windows/appcompat/shims/layer/ignoreexception.cpp

456 lines
13 KiB
C++
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*++
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