Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
wseh.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * Structured Exception Handling support
25  */
26 
27 #include "precompiled.h"
28 #include "lib/sysdep/os/win/wseh.h"
29 
30 #include "lib/byte_order.h" // FOURCC
31 #include "lib/utf8.h"
32 #include "lib/sysdep/cpu.h"
33 #include "lib/sysdep/os/win/win.h"
35 #include "lib/sysdep/os/win/wdbg_sym.h" // wdbg_sym_WriteMinidump
36 
37 #if MSC_VERSION >= 1400
38 # include <process.h> // __security_init_cookie
39 # define NEED_COOKIE_INIT
40 #endif
41 
42 // note: several excellent references are pointed to by comments below.
43 
44 
45 //-----------------------------------------------------------------------------
46 // analyze an exception (determine description and locus)
47 
48 // VC++ exception handling internals.
49 // see http://www.codeproject.com/cpp/exceptionhandler.asp
50 struct XTypeInfo
51 {
53  const std::type_info* ti;
54  // ..
55 };
56 
58 {
60  const XTypeInfo* types[1];
61 };
62 
63 struct XInfo
64 {
65  DWORD _[3];
67 };
68 
69 
70 // does the given SEH exception look like a C++ exception?
71 // (compiler-specific).
72 static bool IsCppException(const EXCEPTION_RECORD* er)
73 {
74 #if MSC_VERSION
75  // notes:
76  // - value of multibyte character constants (e.g. 'msc') aren't
77  // specified by C++, so use FOURCC instead.
78  // - "MS C" compiler is the only interpretation of this magic value that
79  // makes sense, so it is apparently stored in big-endian format.
80  if(er->ExceptionCode != FOURCC_BE(0xe0, 'm','s','c'))
81  return false;
82 
83  // exception info = (magic, &thrown_Cpp_object, &XInfo)
84  if(er->NumberParameters != 3)
85  return false;
86 
87  // MAGIC_NUMBER1 from exsup.inc
88  if(er->ExceptionInformation[0] != 0x19930520)
89  return false;
90 
91  return true;
92 #else
93 # error "port"
94 #endif
95 }
96 
97 /**
98  * @param er An exception record for which IsCppException returned true.
99  * @param description
100  * @param maxChars
101  **/
102 static const wchar_t* GetCppExceptionDescription(const EXCEPTION_RECORD* er,
103  wchar_t* description, size_t maxChars)
104 {
105  // see above for interpretation
106  const ULONG_PTR* const ei = er->ExceptionInformation;
107 
108  // note: we can't share a __try below - the failure of
109  // one attempt must not abort the others.
110 
111  // get std::type_info
112  char type_buf[100] = {'\0'};
113  const char* type_name = type_buf;
114  __try
115  {
116  const XInfo* xi = (XInfo*)ei[2];
117  const XTypeInfoArray* xta = xi->array;
118  const XTypeInfo* xti = xta->types[0];
119  const std::type_info* ti = xti->ti;
120 
121  // strip "class " from start of string (clutter)
122  strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name());
123  if(!strncmp(type_buf, "class ", 6))
124  type_name += 6;
125  }
126  __except(EXCEPTION_EXECUTE_HANDLER)
127  {
128  }
129 
130  // std::exception.what()
131  char what[160] = {'\0'};
132  __try
133  {
134  std::exception* e = (std::exception*)ei[1];
135  strcpy_s(what, ARRAY_SIZE(what), e->what());
136  }
137  __except(EXCEPTION_EXECUTE_HANDLER)
138  {
139  }
140 
141  // format the info we got (if both are empty, then something is seriously
142  // wrong; it's better to show empty strings than returning 0 to have our
143  // caller display the SEH info)
144  swprintf_s(description, maxChars, L"%hs(\"%hs\")", type_name, what);
145  return description;
146 }
147 
148 
149 static const wchar_t* GetSehExceptionDescription(const EXCEPTION_RECORD* er,
150  wchar_t* description, size_t maxChars)
151 {
152  const DWORD code = er->ExceptionCode;
153  const ULONG_PTR* ei = er->ExceptionInformation;
154 
155  // rationale: we don't use FormatMessage because it is unclear whether
156  // NTDLL's symbol table will always include English-language strings
157  // (we don't want to receive crashlogs in foreign gobbledygook).
158  // it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .).
159 
160  switch(code)
161  {
162  case EXCEPTION_ACCESS_VIOLATION:
163  {
164  // special case: display type and address.
165  const wchar_t* accessType = (ei[0])? L"writing" : L"reading";
166  const ULONG_PTR address = ei[1];
167  swprintf_s(description, maxChars, L"Access violation %ls 0x%08X", accessType, address);
168  return description;
169  }
170  case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment";
171  case EXCEPTION_BREAKPOINT: return L"Breakpoint";
172  case EXCEPTION_SINGLE_STEP: return L"Single step";
173  case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded";
174  case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand";
175  case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero";
176  case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result";
177  case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation";
178  case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow";
179  case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check";
180  case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow";
181  case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero";
182  case EXCEPTION_INT_OVERFLOW: return L"Integer overflow";
183  case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction";
184  case EXCEPTION_IN_PAGE_ERROR: return L"In page error";
185  case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction";
186  case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception";
187  case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow";
188  case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition";
189  case EXCEPTION_GUARD_PAGE: return L"Guard page";
190  case EXCEPTION_INVALID_HANDLE: return L"Invalid handle";
191  }
192 
193  // anything else => unknown; display its exception code.
194  // we don't punt to GetExceptionDescription because anything
195  // we get called for will actually be a SEH exception.
196  swprintf_s(description, maxChars, L"Unknown (0x%08X)", code);
197  return description;
198 }
199 
200 
201 /**
202  * @return a description of the exception type and cause (in English).
203  **/
204 static const wchar_t* GetExceptionDescription(const EXCEPTION_POINTERS* ep,
205  wchar_t* description, size_t maxChars)
206 {
207  const EXCEPTION_RECORD* const er = ep->ExceptionRecord;
208 
209  if(IsCppException(er))
210  return GetCppExceptionDescription(er, description, maxChars);
211  else
212  return GetSehExceptionDescription(er, description, maxChars);
213 }
214 
215 
216 // return location at which the exception <er> occurred.
217 // params: see debug_ResolveSymbol.
218 static void GetExceptionLocus(EXCEPTION_POINTERS* ep,
219  wchar_t* file, int* line, wchar_t* func)
220 {
221  // HACK: <ep> provides no useful information - ExceptionAddress always
222  // points to kernel32!RaiseException. we use debug_GetCaller to
223  // determine the real location.
224 
225  const wchar_t* const lastFuncToSkip = L"RaiseException";
226  void* func_addr = debug_GetCaller(ep->ContextRecord, lastFuncToSkip);
227  (void)debug_ResolveSymbol(func_addr, func, file, line);
228 }
229 
230 
231 //-----------------------------------------------------------------------------
232 // exception filter
233 
234 // called when an exception is detected (see below); provides detailed
235 // debugging information and exits.
236 //
237 // note: keep memory allocs and locking to an absolute minimum, because
238 // they may deadlock the process!
239 long __stdcall wseh_ExceptionFilter(struct _EXCEPTION_POINTERS* ep)
240 {
241  // OutputDebugString raises an exception, which OUGHT to be swallowed
242  // by WaitForDebugEvent but sometimes isn't. if we see it, ignore it.
243  if(ep->ExceptionRecord->ExceptionCode == 0x40010006) // DBG_PRINTEXCEPTION_C
244  return EXCEPTION_CONTINUE_EXECUTION;
245 
246  // if run in a debugger, let it handle exceptions (tends to be more
247  // convenient since it can bring up the crash location)
248  if(IsDebuggerPresent())
249  return EXCEPTION_CONTINUE_SEARCH;
250 
251  // make sure we don't recurse infinitely if this function raises an
252  // SEH exception. (we may only have the guard page's 4 KB worth of
253  // stack space if the exception is EXCEPTION_STACK_OVERFLOW)
254  static intptr_t nestingLevel = 0;
255  cpu_AtomicAdd(&nestingLevel, 1);
256  if(nestingLevel >= 3)
257  return EXCEPTION_CONTINUE_SEARCH;
258 
259  // someone is already holding the dbghelp lock - this is bad.
260  // we'll report this problem first and then try to display the
261  // exception info regardless (maybe dbghelp won't blow up).
262  if(wutil_IsLocked(WDBG_SYM_CS) == 1)
263  DEBUG_DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock..");
264 
265  // a dump file is essential for debugging, so write it before
266  // anything else goes wrong (especially before showing the error
267  // dialog because the user could choose to exit immediately)
269 
270  // extract details from ExceptionRecord.
271  wchar_t descriptionBuf[150];
272  const wchar_t* description = GetExceptionDescription(ep, descriptionBuf, ARRAY_SIZE(descriptionBuf));
273  wchar_t file[DEBUG_FILE_CHARS] = {0};
274  int line = 0;
275  wchar_t func[DEBUG_SYMBOL_CHARS] = {0};
276  GetExceptionLocus(ep, file, &line, func);
277 
278  wchar_t message[500];
279  const wchar_t* messageFormat =
280  L"Much to our regret we must report the program has encountered an error.\r\n"
281  L"\r\n"
282  L"Please let us know at http://trac.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n"
283  L"\r\n"
284  L"Details: unhandled exception (%ls)\r\n";
285  swprintf_s(message, ARRAY_SIZE(message), messageFormat, description);
286 
287  size_t flags = 0;
288  if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
289  flags = DE_NO_CONTINUE;
290  const wchar_t* const lastFuncToSkip = WIDEN(STRINGIZE(DECORATED_NAME(wseh_ExceptionFilter)));
291  ErrorReaction er = debug_DisplayError(message, flags, ep->ContextRecord, lastFuncToSkip, file,line,utf8_from_wstring(func).c_str(), 0);
292  ENSURE(er == ER_CONTINUE); // nothing else possible
293 
294  // invoke the Win32 default handler - it calls ExitProcess for
295  // most exception types.
296  return EXCEPTION_CONTINUE_SEARCH;
297 }
298 
299 
300 //-----------------------------------------------------------------------------
301 // install SEH exception handler
302 
303 /*
304 
305 rationale:
306 we want to replace the OS "program error" dialog box because it is not
307 all too helpful in debugging. to that end, there are 5 ways to make sure
308 unhandled SEH exceptions are caught:
309 - via WaitForDebugEvent; the app is run from a separate debugger process.
310  the exception is in another address space, so we'd have to deal with that
311  and basically implement a full-featured debugger - overkill.
312 - by wrapping all threads (their handler chains are in TLS) in __try.
313  this can be done with the cooperation of wpthread, but threads not under
314  our control aren't covered.
315 - with a vectored exception handler. this works across threads, but it's
316  only available on WinXP (unacceptable). since it is called before __try
317  blocks, we would receive expected/legitimate exceptions.
318  (see http://msdn.microsoft.com/msdnmag/issues/01/09/hood/default.aspx)
319 - by setting the per-process unhandled exception filter. as above, this works
320  across threads and is at least portable across Win32. unfortunately, some
321  Win32 DLLs appear to register their own handlers, so this isn't reliable.
322 - by hooking the exception dispatcher. this isn't future-proof.
323 
324 note that the vectored and unhandled-exception filters aren't called when
325 the process is being debugged (messing with the PEB flag doesn't help;
326 root cause is the Win32 KiUserExceptionDispatcher implementation).
327 however, this is fine since the IDE's debugger is more helpful than our
328 dialog (it is able to jump directly to the offending code).
329 
330 wrapping all threads in a __try appears to be the best choice. unfortunately,
331 we cannot retroactively install an SEH handler: the OS ensures SEH chain
332 nodes are on the thread's stack (as defined by NT_TIB) in ascending order.
333 (see http://www.microsoft.com/msj/0197/exception/exception.aspx)
334 the handler would also have to be marked via .safeseh, but that is doable
335 (see http://blogs.msdn.com/greggm/archive/2004/07/22/191544.aspx)
336 consequently, we'll have to run within a __try; if the init code is to be
337 covered, this must happen within the program entry point.
338 
339 note: since C++ exceptions are implemented via SEH, we can also catch
340 those here; it's nicer than a global try{} and avoids duplicating this code.
341 we can still get at the C++ information (std::exception.what()) by examining
342 the internal exception data structures. these are compiler-specific, but
343 haven't changed from VC5-VC7.1.
344 alternatively, _set_se_translator could to translate all SEH exceptions to
345 C++ classes. this way is more reliable/documented, but has several drawbacks:
346 - it wouldn't work at all in C programs,
347 - a new fat exception class would have to be created to hold the
348  SEH exception information (e.g. CONTEXT for a stack trace), and
349 - this information would not be available for C++ exceptions.
350 (see http://blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx)
351 
352 */
353 
354 #ifdef LIB_STATIC_LINK
355 
356 #include "lib/utf8.h"
357 
358 // disable argument conversion on ICC because it says "nonstandard second parameter "__wchar_t *[]" of "main"" and
359 // "unresolved external symbol _wmain referenced in function ___tmainCRTStartup" (extern "C" { and __cdecl don't help)
360 #if ICC_VERSION
361 #define MAIN_STARTUP mainCRTStartup
362 
363 #else
364 #define MAIN_STARTUP wmainCRTStartup
365 
366 EXTERN_C int main(int argc, char* argv[]);
367 
368 // required because main's argv is in a non-UTF8 encoding
369 int wmain(int argc, wchar_t* argv[])
370 {
371  if(argc == 0)
372  return EXIT_FAILURE; // ensure &utf8_argv[0] is safe
373  std::vector<char*> utf8_argv(argc);
374  for(int i = 0; i < argc; i++)
375  {
376  std::string utf8 = utf8_from_wstring(argv[i]);
377  utf8_argv[i] = strdup(utf8.c_str());
378  }
379 
380  const int ret = main(argc, &utf8_argv[0]);
381 
382  for(int i = 0; i < argc; i++)
383  free(utf8_argv[i]);
384  return ret;
385 }
386 
387 #endif
388 
389 EXTERN_C int MAIN_STARTUP();
390 
391 static int CallStartupWithinTryBlock()
392 {
393  int ret;
394  __try
395  {
396  ret = MAIN_STARTUP();
397  }
398  __except(wseh_ExceptionFilter(GetExceptionInformation()))
399  {
400  ret = -1;
401  }
402  return ret;
403 }
404 
406 {
407 #ifdef NEED_COOKIE_INIT
408  // 2006-02-16 workaround for R6035 on VC8:
409  //
410  // SEH code compiled with /GS pushes a "security cookie" onto the
411  // stack. since we're called before CRT init, the cookie won't have
412  // been initialized yet, which would cause the CRT to FatalAppExit.
413  // to solve this, we must call __security_init_cookie before any
414  // hidden compiler-generated SEH registration code runs,
415  // which means the __try block must be moved into a helper function.
416  //
417  // NB: wseh_EntryPoint() must not contain local string buffers,
418  // either - /GS would install a cookie here as well (same problem).
419  //
420  // see http://msdn2.microsoft.com/en-US/library/ms235603.aspx
421  __security_init_cookie();
422 #endif
423  return CallStartupWithinTryBlock();
424 }
425 
426 #endif
EXTERN_C int wseh_EntryPoint()
static const size_t DEBUG_SYMBOL_CHARS
Maximum number of characters (including null terminator) written to user&#39;s buffers by debug_ResolveSy...
Definition: debug.h:428
#define EXTERN_C
const XTypeInfo * types[1]
Definition: wseh.cpp:60
ErrorReaction debug_DisplayError(const wchar_t *description, size_t flags, void *context, const wchar_t *lastFuncToSkip, const wchar_t *pathname, int line, const char *func, atomic_bool *suppress)
display an error dialog with a message and stack trace.
Definition: debug.cpp:426
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:208
void wdbg_sym_WriteMinidump(EXCEPTION_POINTERS *exception_pointers)
Definition: wdbg_sym.cpp:1755
const XTypeInfoArray * array
Definition: wseh.cpp:66
static const wchar_t * GetExceptionDescription(const EXCEPTION_POINTERS *ep, wchar_t *description, size_t maxChars)
Definition: wseh.cpp:204
ErrorReaction
choices offered by the error dialog that are returned by debug_DisplayError.
Definition: debug.h:131
intptr_t cpu_AtomicAdd(volatile intptr_t *location, intptr_t increment)
add a signed value to a variable without the possibility of interference from other threads/CPUs...
Definition: arm.cpp:31
int swprintf_s(wchar_t *buf, size_t max_chars, const wchar_t *fmt,...) WPRINTF_ARGS(3)
Definition: wseh.cpp:63
const std::type_info * ti
Definition: wseh.cpp:53
#define ARRAY_SIZE(name)
bool wutil_IsLocked(WinLockId id)
Definition: wutil.cpp:91
#define WIDEN(x)
#define DECORATED_NAME(name)
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
unsigned long DWORD
Definition: wgl.h:56
static const size_t DEBUG_FILE_CHARS
Definition: debug.h:429
LIB_API void * debug_GetCaller(void *context, const wchar_t *lastFuncToSkip)
return the caller of a certain function on the call stack.
Definition: bdbg.cpp:38
disallow the Continue button.
Definition: debug.h:86
#define FOURCC_BE(a, b, c, d)
big-endian version of FOURCC
Definition: byte_order.h:61
#define STRINGIZE(id)
static const wchar_t * GetSehExceptionDescription(const EXCEPTION_RECORD *er, wchar_t *description, size_t maxChars)
Definition: wseh.cpp:149
#define DEBUG_DISPLAY_ERROR(description)
Definition: debug.h:197
LIB_API Status debug_ResolveSymbol(void *ptr_of_interest, wchar_t *sym_name, wchar_t *file, int *line)
read and return symbol information for the given address.
Definition: bdbg.cpp:99
int strcpy_s(char *dst, size_t max_dst_chars, const char *src)
static bool IsCppException(const EXCEPTION_RECORD *er)
Definition: wseh.cpp:72
DWORD _[3]
Definition: wseh.cpp:65
DWORD _
Definition: wseh.cpp:52
long __stdcall wseh_ExceptionFilter(struct _EXCEPTION_POINTERS *ep)
Definition: wseh.cpp:239
int main(int argc, char *argv[])
Definition: main.cpp:531
static const wchar_t * GetCppExceptionDescription(const EXCEPTION_RECORD *er, wchar_t *description, size_t maxChars)
Definition: wseh.cpp:102
static void GetExceptionLocus(EXCEPTION_POINTERS *ep, wchar_t *file, int *line, wchar_t *func)
Definition: wseh.cpp:218
DWORD count
Definition: wseh.cpp:59
ignore, continue as if nothing happened.
Definition: debug.h:138