Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

452 lines
12 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name:
  4. IgnoreException.cpp
  5. Abstract:
  6. This shim is for handling exceptions that get thrown by bad apps.
  7. The primary causes of unhandled exceptions are:
  8. 1. Priviliged mode instructions: cli, sti, out etc
  9. 2. Access violations
  10. In most cases, ignoring an Access Violation will be fatal for the app,
  11. but it works in some cases, eg:
  12. Deer Hunter 2 - their 3d algorithm reads too far back in a lookup
  13. buffer. This is a game bug and doesn't crash win9x because that memory
  14. is usually allocated.
  15. Interstate 76 also requires a Divide by Zero exception to be ignored.
  16. Notes:
  17. This is a general purpose shim.
  18. History:
  19. 02/10/2000 linstev Created
  20. 10/17/2000 maonis Bug fix - now it ignores AVs correctly.
  21. 02/27/2001 robkenny Converted to use CString
  22. --*/
  23. #include "precomp.h"
  24. IMPLEMENT_SHIM_BEGIN(IgnoreException)
  25. #include "ShimHookMacro.h"
  26. APIHOOK_ENUM_BEGIN
  27. APIHOOK_ENUM_END
  28. // Exception code for OutputDebugString
  29. #define DBG_EXCEPTION 0x40010000L
  30. // Determine how to manage second chance exceptions
  31. BOOL g_bWin2000 = FALSE;
  32. DWORD g_dwLastEip = 0;
  33. extern DWORD GetInstructionLengthFromAddress(LPBYTE pEip);
  34. /*++
  35. This is the list of all the exceptions that this shim can handle. The fields are
  36. 1. cName - the name of the exception as accepted as a parameter and
  37. displayed in debug spew
  38. 2. dwCode - the exception code
  39. 3. dwSubCode - parameters specified by the exception: -1 = don't care
  40. 4. dwIgnore - ignore this exception:
  41. 0 = don't ignore
  42. 1 = ignore 1st chance
  43. 2 = ignore 2nd chance
  44. 3 = exit process on 2nd chance.
  45. --*/
  46. typedef enum
  47. {
  48. eActive = 0,
  49. eFirstChance,
  50. eSecondChance,
  51. eExitProcess
  52. } EMODE;
  53. WCHAR * ToWchar(EMODE emode)
  54. {
  55. switch (emode)
  56. {
  57. case eActive:
  58. return L"Active";
  59. case eFirstChance:
  60. return L"FirstChance";
  61. case eSecondChance:
  62. return L"SecondChance";
  63. case eExitProcess:
  64. return L"ExitProcess";
  65. };
  66. return L"ERROR";
  67. }
  68. // Convert a text version of EMODE to a EMODE value
  69. EMODE ToEmode(const CString & csMode)
  70. {
  71. if (csMode.Compare(L"0") == 0 || csMode.Compare(ToWchar(eActive)) == 0)
  72. {
  73. return eActive;
  74. }
  75. else if (csMode.Compare(L"1") == 0 || csMode.Compare(ToWchar(eFirstChance)) == 0)
  76. {
  77. return eFirstChance;
  78. }
  79. else if (csMode.Compare(L"2") == 0 || csMode.Compare(ToWchar(eSecondChance)) == 0)
  80. {
  81. return eSecondChance;
  82. }
  83. else if (csMode.Compare(L"3") == 0 || csMode.Compare(ToWchar(eExitProcess)) == 0)
  84. {
  85. return eExitProcess;
  86. }
  87. // Default value
  88. return eFirstChance;
  89. }
  90. struct EXCEPT
  91. {
  92. WCHAR * cName;
  93. DWORD dwCode;
  94. DWORD dwSubCode;
  95. EMODE dwIgnore;
  96. };
  97. static EXCEPT g_eList[] =
  98. {
  99. {L"ACCESS_VIOLATION_READ" , (DWORD)EXCEPTION_ACCESS_VIOLATION , 0 , eActive},
  100. {L"ACCESS_VIOLATION_WRITE" , (DWORD)EXCEPTION_ACCESS_VIOLATION , 1 , eActive},
  101. {L"ARRAY_BOUNDS_EXCEEDED" , (DWORD)EXCEPTION_ARRAY_BOUNDS_EXCEEDED , -1, eActive},
  102. {L"BREAKPOINT" , (DWORD)EXCEPTION_BREAKPOINT , -1, eActive},
  103. {L"DATATYPE_MISALIGNMENT" , (DWORD)EXCEPTION_DATATYPE_MISALIGNMENT , -1, eActive},
  104. {L"FLT_DENORMAL_OPERAND" , (DWORD)EXCEPTION_FLT_DENORMAL_OPERAND , -1, eActive},
  105. {L"FLT_DIVIDE_BY_ZERO" , (DWORD)EXCEPTION_FLT_DIVIDE_BY_ZERO , -1, eActive},
  106. {L"FLT_INEXACT_RESULT" , (DWORD)EXCEPTION_FLT_INEXACT_RESULT , -1, eActive},
  107. {L"FLT_INVALID_OPERATION" , (DWORD)EXCEPTION_FLT_INVALID_OPERATION , -1, eActive},
  108. {L"FLT_OVERFLOW" , (DWORD)EXCEPTION_FLT_OVERFLOW , -1, eActive},
  109. {L"FLT_STACK_CHECK" , (DWORD)EXCEPTION_FLT_STACK_CHECK , -1, eActive},
  110. {L"FLT_UNDERFLOW" , (DWORD)EXCEPTION_FLT_UNDERFLOW , -1, eActive},
  111. {L"ILLEGAL_INSTRUCTION" , (DWORD)EXCEPTION_ILLEGAL_INSTRUCTION , -1, eActive},
  112. {L"IN_PAGE_ERROR" , (DWORD)EXCEPTION_IN_PAGE_ERROR , -1, eActive},
  113. {L"INT_DIVIDE_BY_ZERO" , (DWORD)EXCEPTION_INT_DIVIDE_BY_ZERO , -1, eActive},
  114. {L"INT_OVERFLOW" , (DWORD)EXCEPTION_INT_OVERFLOW , -1, eActive},
  115. {L"INVALID_DISPOSITION" , (DWORD)EXCEPTION_INVALID_DISPOSITION , -1, eActive},
  116. {L"NONCONTINUABLE_EXCEPTION" , (DWORD)EXCEPTION_NONCONTINUABLE_EXCEPTION, -1, eActive},
  117. {L"PRIV_INSTRUCTION" , (DWORD)EXCEPTION_PRIV_INSTRUCTION , -1, eFirstChance},
  118. {L"SINGLE_STEP" , (DWORD)EXCEPTION_SINGLE_STEP , -1, eActive},
  119. {L"STACK_OVERFLOW" , (DWORD)EXCEPTION_STACK_OVERFLOW , -1, eActive},
  120. {L"INVALID_HANDLE" , (DWORD)EXCEPTION_INVALID_HANDLE , -1, eActive}
  121. };
  122. #define ELISTSIZE sizeof(g_eList) / sizeof(g_eList[0])
  123. /*++
  124. Custom exception handler.
  125. --*/
  126. LONG
  127. ExceptionFilter(
  128. struct _EXCEPTION_POINTERS *ExceptionInfo
  129. )
  130. {
  131. DWORD dwCode = ExceptionInfo->ExceptionRecord->ExceptionCode;
  132. if ((dwCode & DBG_EXCEPTION) == DBG_EXCEPTION) // for the DebugPrints
  133. {
  134. return EXCEPTION_CONTINUE_SEARCH;
  135. }
  136. CONTEXT *lpContext = ExceptionInfo->ContextRecord;
  137. WCHAR szException[64];
  138. BOOL bIgnore = FALSE;
  139. //
  140. // Run the list of exceptions to see if we're ignoring it
  141. //
  142. EXCEPT *pE = &g_eList[0];
  143. for (int i = 0; i < ELISTSIZE; i++, pE++)
  144. {
  145. // Matched the major exception code
  146. if (dwCode == pE->dwCode)
  147. {
  148. // See if we care about the subcode
  149. if ((pE->dwSubCode != -1) &&
  150. (ExceptionInfo->ExceptionRecord->ExceptionInformation[0] != pE->dwSubCode))
  151. {
  152. continue;
  153. }
  154. wcscpy(szException, pE->cName);
  155. // Determine how to handle the exception
  156. switch (pE->dwIgnore)
  157. {
  158. case eActive:
  159. bIgnore = FALSE;
  160. break;
  161. case eFirstChance:
  162. bIgnore = TRUE;
  163. break;
  164. case eSecondChance:
  165. bIgnore = g_bWin2000 || (g_dwLastEip == lpContext->Eip);
  166. g_dwLastEip = lpContext->Eip;
  167. break;
  168. case eExitProcess:
  169. // Try using unhandled exception filters to catch this
  170. bIgnore = TRUE;//g_bWin2000 || IsBadCodePtr((FARPROC)lpContext->Eip);
  171. if (bIgnore)
  172. {
  173. ExitProcess(0);
  174. TerminateProcess(GetCurrentProcess(), 0);
  175. }
  176. g_dwLastEip = lpContext->Eip;
  177. break;
  178. }
  179. if (bIgnore) break;
  180. }
  181. }
  182. //
  183. // Dump out the exception
  184. //
  185. DPFN( eDbgLevelWarning, "Exception %S (%08lx)\n",
  186. szException,
  187. dwCode);
  188. #ifdef DBG
  189. DPFN( eDbgLevelWarning, "eip=%08lx\n",
  190. lpContext->Eip);
  191. DPFN( eDbgLevelWarning, "eax=%08lx, ebx=%08lx, ecx=%08lx, edx=%08lx\n",
  192. lpContext->Eax,
  193. lpContext->Ebx,
  194. lpContext->Ecx,
  195. lpContext->Edx);
  196. DPFN( eDbgLevelWarning, "esi=%08lx, edi=%08lx, esp=%08lx, ebp=%08lx\n",
  197. lpContext->Esi,
  198. lpContext->Edi,
  199. lpContext->Esp,
  200. lpContext->Ebp);
  201. DPFN( eDbgLevelWarning, "cs=%04lx, ss=%04lx, ds=%04lx, es=%04lx, fs=%04lx, gs=%04lx\n",
  202. lpContext->SegCs,
  203. lpContext->SegSs,
  204. lpContext->SegDs,
  205. lpContext->SegEs,
  206. lpContext->SegFs,
  207. lpContext->SegGs);
  208. #endif
  209. LONG lRet;
  210. if (bIgnore)
  211. {
  212. if ((DWORD)lpContext->Eip <= (DWORD)0xFFFF)
  213. {
  214. LOGN( eDbgLevelError, "[ExceptionFilter] Exception %S (%08X), stuck at bad address, killing current thread.", szException, dwCode);
  215. lRet = EXCEPTION_CONTINUE_SEARCH;
  216. return lRet;
  217. }
  218. LOGN( eDbgLevelWarning, "[ExceptionFilter] Exception %S (%08X) ignored.", szException, dwCode);
  219. lpContext->Eip += GetInstructionLengthFromAddress((LPBYTE)lpContext->Eip);
  220. g_dwLastEip = 0;
  221. lRet = EXCEPTION_CONTINUE_EXECUTION;
  222. }
  223. else
  224. {
  225. DPFN( eDbgLevelWarning, "Exception NOT handled\n\n");
  226. lRet = EXCEPTION_CONTINUE_SEARCH;
  227. }
  228. return lRet;
  229. }
  230. /*++
  231. Parse the command line for particular exceptions. The format of the command
  232. line is:
  233. [EXCEPTION_NAME[:0|1|2]];[EXCEPTION_NAME[:0|1|2]]...
  234. or "*" which ignores all first chance exceptions.
  235. Eg:
  236. ACCESS_VIOLATION:2;PRIV_INSTRUCTION:0;BREAKPOINT
  237. Will ignore:
  238. 1. Access violations - second chance
  239. 2. Priviliged mode instructions - do not ignore
  240. 3. Breakpoints - ignore
  241. --*/
  242. BOOL
  243. ParseCommandLine(
  244. LPCSTR lpCommandLine
  245. )
  246. {
  247. CSTRING_TRY
  248. {
  249. CStringToken csTok(lpCommandLine, L" ;");
  250. int iLast = -1;
  251. //
  252. // Run the string, looking for exception names
  253. //
  254. CString token;
  255. // Each cl token may be followed by a : and an exception type
  256. // Forms can be:
  257. // *
  258. // *:SecondChance
  259. // INVALID_DISPOSITION
  260. // INVALID_DISPOSITION:Active
  261. // INVALID_DISPOSITION:0
  262. //
  263. while (csTok.GetToken(token))
  264. {
  265. CStringToken csSingleTok(token, L":");
  266. CString csExcept;
  267. CString csType;
  268. // grab the exception name and the exception type
  269. csSingleTok.GetToken(csExcept);
  270. csSingleTok.GetToken(csType);
  271. // Convert ignore value to emode (defaults to eFirstChance)
  272. EMODE emode = ToEmode(csType);
  273. if (token.Compare(L"*") == 0)
  274. {
  275. for (int i = 0; i < ELISTSIZE; i++)
  276. {
  277. g_eList[i].dwIgnore = emode;
  278. }
  279. }
  280. else
  281. {
  282. // Find the exception specified
  283. for (int i = 0; i < ELISTSIZE; i++)
  284. {
  285. if (csExcept.CompareNoCase(g_eList[i].cName) == 0)
  286. {
  287. g_eList[i].dwIgnore = emode;
  288. break;
  289. }
  290. }
  291. }
  292. }
  293. }
  294. CSTRING_CATCH
  295. {
  296. return FALSE;
  297. }
  298. //
  299. // Dump results of command line parse
  300. //
  301. DPFN( eDbgLevelInfo, "===================================\n");
  302. DPFN( eDbgLevelInfo, " Ignore Exception \n");
  303. DPFN( eDbgLevelInfo, "===================================\n");
  304. DPFN( eDbgLevelInfo, " 1 = First chance \n");
  305. DPFN( eDbgLevelInfo, " 2 = Second chance \n");
  306. DPFN( eDbgLevelInfo, " 3 = ExitProcess on second chance \n");
  307. DPFN( eDbgLevelInfo, "-----------------------------------\n");
  308. for (int i = 0; i < ELISTSIZE; i++)
  309. {
  310. if (g_eList[i].dwIgnore != eActive)
  311. {
  312. DPFN( eDbgLevelInfo, "%S %S\n", ToWchar(g_eList[i].dwIgnore), g_eList[i].cName);
  313. }
  314. }
  315. DPFN( eDbgLevelInfo, "-----------------------------------\n");
  316. return TRUE;
  317. }
  318. /*++
  319. Register hooked functions
  320. --*/
  321. BOOL
  322. NOTIFY_FUNCTION(
  323. DWORD fdwReason)
  324. {
  325. if (fdwReason == DLL_PROCESS_ATTACH)
  326. {
  327. // Run the command line to check for adjustments to defaults
  328. if (!ParseCommandLine(COMMAND_LINE))
  329. {
  330. return FALSE;
  331. }
  332. // Try to find new exception handler
  333. _pfn_RtlAddVectoredExceptionHandler pfnExcept;
  334. pfnExcept = (_pfn_RtlAddVectoredExceptionHandler)
  335. GetProcAddress(
  336. GetModuleHandle(L"NTDLL.DLL"),
  337. "RtlAddVectoredExceptionHandler");
  338. if (pfnExcept)
  339. {
  340. (_pfn_RtlAddVectoredExceptionHandler) pfnExcept(
  341. 0,
  342. (PVOID)ExceptionFilter);
  343. g_bWin2000 = FALSE;
  344. }
  345. else
  346. {
  347. // Windows 2000 reverts back to the old method which unluckily
  348. // doesn't get called for C++ exceptions
  349. SetUnhandledExceptionFilter(ExceptionFilter);
  350. g_bWin2000 = TRUE;
  351. }
  352. }
  353. return TRUE;
  354. }
  355. HOOK_BEGIN
  356. CALL_NOTIFY_FUNCTION
  357. HOOK_END
  358. IMPLEMENT_SHIM_END