2 * win32_crashrpt.c : provides information after a crash
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* prevent "empty compilation unit" warning on e.g. UNIX */
25 typedef int win32_crashrpt__dummy;
28 #ifdef SVN_USE_WIN32_CRASHHANDLER
38 #include "svn_version.h"
40 #include "win32_crashrpt.h"
41 #include "win32_crashrpt_dll.h"
43 /*** Global variables ***/
44 HANDLE dbghelp_dll = INVALID_HANDLE_VALUE;
46 /* Email address where the crash reports should be sent too. */
47 #define CRASHREPORT_EMAIL "users@subversion.apache.org"
49 #define DBGHELP_DLL "dbghelp.dll"
51 #define LOGFILE_PREFIX "svn-crash-log"
54 #define FORMAT_PTR "0x%08x"
56 #define FORMAT_PTR "0x%016I64x"
61 /* Convert a wide-character string to utf-8. This function will create a buffer
62 * large enough to hold the result string, the caller should free this buffer.
63 * If the string can't be converted, NULL is returned.
66 convert_wbcs_to_utf8(const wchar_t *str)
68 size_t len = wcslen(str);
69 char *utf8_str = malloc(sizeof(wchar_t) * len + 1);
70 len = wcstombs(utf8_str, str, len);
80 /* Convert the exception code to a string */
82 exception_string(int exception)
84 #define EXCEPTION(x) case EXCEPTION_##x: return (#x);
88 EXCEPTION(ACCESS_VIOLATION)
89 EXCEPTION(DATATYPE_MISALIGNMENT)
91 EXCEPTION(SINGLE_STEP)
92 EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
93 EXCEPTION(FLT_DENORMAL_OPERAND)
94 EXCEPTION(FLT_DIVIDE_BY_ZERO)
95 EXCEPTION(FLT_INEXACT_RESULT)
96 EXCEPTION(FLT_INVALID_OPERATION)
97 EXCEPTION(FLT_OVERFLOW)
98 EXCEPTION(FLT_STACK_CHECK)
99 EXCEPTION(FLT_UNDERFLOW)
100 EXCEPTION(INT_DIVIDE_BY_ZERO)
101 EXCEPTION(INT_OVERFLOW)
102 EXCEPTION(PRIV_INSTRUCTION)
103 EXCEPTION(IN_PAGE_ERROR)
104 EXCEPTION(ILLEGAL_INSTRUCTION)
105 EXCEPTION(NONCONTINUABLE_EXCEPTION)
106 EXCEPTION(STACK_OVERFLOW)
107 EXCEPTION(INVALID_DISPOSITION)
108 EXCEPTION(GUARD_PAGE)
109 EXCEPTION(INVALID_HANDLE)
112 return "UNKNOWN_ERROR";
117 /* Write the minidump to file. The callback function will at the same time
118 write the list of modules to the log file. */
120 write_minidump_file(const char *file, PEXCEPTION_POINTERS ptrs,
121 MINIDUMP_CALLBACK_ROUTINE module_callback,
124 /* open minidump file */
125 HANDLE minidump_file = CreateFile(file, GENERIC_WRITE, 0, NULL,
127 FILE_ATTRIBUTE_NORMAL,
130 if (minidump_file != INVALID_HANDLE_VALUE)
132 MINIDUMP_EXCEPTION_INFORMATION expt_info;
133 MINIDUMP_CALLBACK_INFORMATION dump_cb_info;
135 expt_info.ThreadId = GetCurrentThreadId();
136 expt_info.ExceptionPointers = ptrs;
137 expt_info.ClientPointers = FALSE;
139 dump_cb_info.CallbackRoutine = module_callback;
140 dump_cb_info.CallbackParam = data;
142 MiniDumpWriteDump_(GetCurrentProcess(),
143 GetCurrentProcessId(),
146 ptrs ? &expt_info : NULL,
150 CloseHandle(minidump_file);
157 /* Write module information to the log file */
159 write_module_info_callback(void *data,
160 CONST PMINIDUMP_CALLBACK_INPUT callback_input,
161 PMINIDUMP_CALLBACK_OUTPUT callback_output)
164 callback_input != NULL &&
165 callback_input->CallbackType == ModuleCallback)
167 FILE *log_file = (FILE *)data;
168 MINIDUMP_MODULE_CALLBACK module = callback_input->Module;
170 char *buf = convert_wbcs_to_utf8(module.FullPath);
171 fprintf(log_file, FORMAT_PTR, module.BaseOfImage);
172 fprintf(log_file, " %s", buf);
175 fprintf(log_file, " (%d.%d.%d.%d, %d bytes)\n",
176 HIWORD(module.VersionInfo.dwFileVersionMS),
177 LOWORD(module.VersionInfo.dwFileVersionMS),
178 HIWORD(module.VersionInfo.dwFileVersionLS),
179 LOWORD(module.VersionInfo.dwFileVersionLS),
186 /* Write details about the current process, platform and the exception */
188 write_process_info(EXCEPTION_RECORD *exception, CONTEXT *context,
192 const char *cmd_line;
193 char workingdir[8192];
195 /* write the command line */
196 cmd_line = GetCommandLine();
198 "Cmd line: %s\n", cmd_line);
200 _getcwd(workingdir, sizeof(workingdir));
202 "Working Dir: %s\n", workingdir);
204 /* write the svn version number info. */
206 "Version: %s, compiled %s, %s\n",
207 SVN_VERSION, __DATE__, __TIME__);
209 /* write information about the OS */
210 oi.dwOSVersionInfoSize = sizeof(oi);
214 "Platform: Windows OS version %d.%d build %d %s\n\n",
215 oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber,
218 /* write the exception code */
221 exception_string(exception->ExceptionCode));
223 /* write the register info. */
228 "eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n",
229 context->Eax, context->Ebx, context->Ecx,
230 context->Edx, context->Esi, context->Edi);
232 "eip=%08x esp=%08x ebp=%08x efl=%08x\n",
233 context->Eip, context->Esp,
234 context->Ebp, context->EFlags);
236 "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n",
237 context->SegCs, context->SegSs, context->SegDs,
238 context->SegEs, context->SegFs, context->SegGs);
239 #elif defined(_M_X64)
241 "Rax=%016I64x Rcx=%016I64x Rdx=%016I64x Rbx=%016I64x\n",
242 context->Rax, context->Rcx, context->Rdx, context->Rbx);
244 "Rsp=%016I64x Rbp=%016I64x Rsi=%016I64x Rdi=%016I64x\n",
245 context->Rsp, context->Rbp, context->Rsi, context->Rdi);
247 "R8= %016I64x R9= %016I64x R10= %016I64x R11=%016I64x\n",
248 context->R8, context->R9, context->R10, context->R11);
250 "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n",
251 context->R12, context->R13, context->R14, context->R15);
254 "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x ss=%04x\n",
255 context->SegCs, context->SegDs, context->SegEs,
256 context->SegFs, context->SegGs, context->SegSs);
258 #error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
262 /* Formats the value at address based on the specified basic type
263 * (char, int, long ...). */
265 format_basic_type(char *buf, DWORD basic_type, DWORD64 length, void *address)
270 sprintf(buf, "0x%02x", (int)*(unsigned char *)address);
273 sprintf(buf, "0x%04x", (int)*(unsigned short *)address);
280 if (!IsBadStringPtr(*(PSTR*)address, 32))
281 sprintf(buf, "\"%.31s\"", *(const char **)address);
283 sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address);
286 sprintf(buf, "%d", *(int *)address);
288 case 8: /* btFloat */
289 sprintf(buf, "%f", *(float *)address);
292 sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address);
297 if (basic_type == 8) /* btFloat */
298 sprintf(buf, "%lf", *(double *)address);
300 sprintf(buf, "0x%016I64X", *(unsigned __int64 *)address);
303 sprintf(buf, "[unhandled type 0x%08x of length " FORMAT_PTR "]",
309 /* Formats the value at address based on the type (pointer, user defined,
312 format_value(char *value_str, DWORD64 mod_base, DWORD type, void *value_addr)
316 HANDLE proc = GetCurrentProcess();
318 while (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMTAG, &tag))
320 /* SymTagPointerType */
324 SymGetTypeInfo_(proc, mod_base, type, TI_GET_TYPE, &type);
332 case 11: /* SymTagUDT */
334 WCHAR *type_name_wbcs;
335 if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMNAME,
338 char *type_name = convert_wbcs_to_utf8(type_name_wbcs);
339 LocalFree(type_name_wbcs);
342 sprintf(value_str, "(%s) " FORMAT_PTR,
343 type_name, (DWORD_PTR *)value_addr);
345 sprintf(value_str, "(%s *) " FORMAT_PTR,
346 type_name, *(DWORD_PTR *)value_addr);
348 sprintf(value_str, "(%s **) " FORMAT_PTR,
349 type_name, *(DWORD_PTR *)value_addr);
354 sprintf(value_str, "[no symbol tag]");
357 case 16: /* SymTagBaseType */
361 SymGetTypeInfo_(proc, mod_base, type, TI_GET_LENGTH, &length);
363 /* print a char * as a string */
364 if (ptr == 1 && length == 1)
366 sprintf(value_str, FORMAT_PTR " \"%s\"",
367 *(DWORD_PTR *)value_addr, *(const char **)value_addr);
371 sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr);
373 else if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_BASETYPE, &bt))
375 format_basic_type(value_str, bt, length, value_addr);
379 case 12: /* SymTagEnum */
380 sprintf(value_str, "%d", *(DWORD_PTR *)value_addr);
382 case 13: /* SymTagFunctionType */
383 sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr);
386 sprintf(value_str, "[unhandled tag: %d]", tag);
391 /* Internal structure used to pass some data to the enumerate symbols
393 typedef struct symbols_baton_t {
394 STACKFRAME64 *stack_frame;
400 /* Write the details of one parameter or local variable to the log file */
402 write_var_values(PSYMBOL_INFO sym_info, ULONG sym_size, void *baton)
404 static int last_nr_of_frame = 0;
405 DWORD_PTR var_data = 0; /* Will point to the variable's data in memory */
406 STACKFRAME64 *stack_frame = ((symbols_baton_t*)baton)->stack_frame;
407 FILE *log_file = ((symbols_baton_t*)baton)->log_file;
408 int nr_of_frame = ((symbols_baton_t*)baton)->nr_of_frame;
409 BOOL log_params = ((symbols_baton_t*)baton)->log_params;
410 char value_str[256] = "";
412 /* get the variable's data */
413 if (sym_info->Flags & SYMFLAG_REGREL)
415 var_data = (DWORD_PTR)stack_frame->AddrFrame.Offset;
416 var_data += (DWORD_PTR)sym_info->Address;
421 if (log_params && sym_info->Flags & SYMFLAG_PARAMETER)
423 if (last_nr_of_frame == nr_of_frame)
424 fprintf(log_file, ", ", 2);
426 last_nr_of_frame = nr_of_frame;
428 format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
430 fprintf(log_file, "%.*s=%s", (int)sym_info->NameLen, sym_info->Name,
433 if (!log_params && sym_info->Flags & SYMFLAG_LOCAL)
435 format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
437 fprintf(log_file, " %.*s = %s\n", (int)sym_info->NameLen,
438 sym_info->Name, value_str);
444 /* Write the details of one function to the log file */
446 write_function_detail(STACKFRAME64 stack_frame, int nr_of_frame, FILE *log_file)
448 ULONG64 symbolBuffer[(sizeof(SYMBOL_INFO) +
450 sizeof(ULONG64) - 1) /
452 PSYMBOL_INFO pIHS = (PSYMBOL_INFO)symbolBuffer;
455 IMAGEHLP_STACK_FRAME ih_stack_frame;
456 IMAGEHLP_LINE64 ih_line;
459 HANDLE proc = GetCurrentProcess();
461 symbols_baton_t ensym;
463 nr_of_frame++; /* We need a 1 based index here */
465 /* log the function name */
466 pIHS->SizeOfStruct = sizeof(SYMBOL_INFO);
467 pIHS->MaxNameLen = MAX_SYM_NAME;
468 if (SymFromAddr_(proc, stack_frame.AddrPC.Offset, &func_disp, pIHS))
471 "#%d 0x%08I64x in %.*s(",
472 nr_of_frame, stack_frame.AddrPC.Offset,
473 pIHS->NameLen > 200 ? 200 : (int)pIHS->NameLen,
476 /* restrict symbol enumeration to this frame only */
477 ih_stack_frame.InstructionOffset = stack_frame.AddrPC.Offset;
478 SymSetContext_(proc, &ih_stack_frame, 0);
480 ensym.log_file = log_file;
481 ensym.stack_frame = &stack_frame;
482 ensym.nr_of_frame = nr_of_frame;
484 /* log all function parameters */
485 ensym.log_params = TRUE;
486 SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
488 fprintf(log_file, ")");
493 "#%d 0x%08I64x in (unknown function)",
494 nr_of_frame, stack_frame.AddrPC.Offset);
497 /* find the source line for this function. */
498 ih_line.SizeOfStruct = sizeof(IMAGEHLP_LINE);
499 if (SymGetLineFromAddr64_(proc, stack_frame.AddrPC.Offset,
500 &line_disp, &ih_line) != 0)
503 " at %s:%d\n", ih_line.FileName, ih_line.LineNumber);
507 fprintf(log_file, "\n");
510 /* log all function local variables */
511 ensym.log_params = FALSE;
512 SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
515 /* Walk over the stack and log all relevant information to the log file */
517 write_stacktrace(CONTEXT *context, FILE *log_file)
519 #if defined (_M_IX86) || defined(_M_X64) || defined(_M_IA64)
520 HANDLE proc = GetCurrentProcess();
521 STACKFRAME64 stack_frame;
526 /* The thread information - if not supplied. */
529 /* If no context is supplied, skip 1 frame */
532 ctx.ContextFlags = CONTEXT_FULL;
533 if (!GetThreadContext(GetCurrentThread(), &ctx))
544 /* Write the stack trace */
545 ZeroMemory(&stack_frame, sizeof(STACKFRAME64));
546 stack_frame.AddrPC.Mode = AddrModeFlat;
547 stack_frame.AddrStack.Mode = AddrModeFlat;
548 stack_frame.AddrFrame.Mode = AddrModeFlat;
551 machine = IMAGE_FILE_MACHINE_I386;
552 stack_frame.AddrPC.Offset = context->Eip;
553 stack_frame.AddrStack.Offset = context->Esp;
554 stack_frame.AddrFrame.Offset = context->Ebp;
555 #elif defined(_M_X64)
556 machine = IMAGE_FILE_MACHINE_AMD64;
557 stack_frame.AddrPC.Offset = context->Rip;
558 stack_frame.AddrStack.Offset = context->Rsp;
559 stack_frame.AddrFrame.Offset = context->Rbp;
560 #elif defined(_M_IA64)
561 machine = IMAGE_FILE_MACHINE_IA64;
562 stack_frame.AddrPC.Offset = context->StIIP;
563 stack_frame.AddrStack.Offset = context->SP;
564 stack_frame.AddrBStore.Mode = AddrModeFlat;
565 stack_frame.AddrBStore.Offset = context->RsBSP;
567 #error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
572 if (! StackWalk64_(machine, proc, GetCurrentThread(),
573 &stack_frame, &ctx, NULL,
574 SymFunctionTableAccess64_, SymGetModuleBase64_, NULL))
581 /* Try to include symbolic information.
582 Also check that the address is not zero. Sometimes StackWalk
583 returns TRUE with a frame of zero. */
584 if (stack_frame.AddrPC.Offset != 0)
586 write_function_detail(stack_frame, i, log_file);
592 #error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
596 /* Check if a debugger is attached to this process */
598 is_debugger_present()
600 HANDLE kernel32_dll = LoadLibrary("kernel32.dll");
603 ISDEBUGGERPRESENT IsDebuggerPresent_ =
604 (ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent");
606 if (IsDebuggerPresent_ && IsDebuggerPresent_())
611 FreeLibrary(kernel32_dll);
616 /* Load the dbghelp.dll file, try to find a version that matches our
621 dbghelp_dll = LoadLibrary(DBGHELP_DLL);
622 if (dbghelp_dll != INVALID_HANDLE_VALUE)
626 /* load the functions */
628 (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump");
630 (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize");
632 (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions");
634 (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions");
636 (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup");
638 (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo");
639 SymGetLineFromAddr64_ =
640 (SYMGETLINEFROMADDR64)GetProcAddress(dbghelp_dll,
641 "SymGetLineFromAddr64");
643 (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols");
645 (SYMSETCONTEXT)GetProcAddress(dbghelp_dll, "SymSetContext");
646 SymFromAddr_ = (SYMFROMADDR)GetProcAddress(dbghelp_dll, "SymFromAddr");
647 StackWalk64_ = (STACKWALK64)GetProcAddress(dbghelp_dll, "StackWalk64");
648 SymFunctionTableAccess64_ =
649 (SYMFUNCTIONTABLEACCESS64)GetProcAddress(dbghelp_dll,
650 "SymFunctionTableAccess64");
651 SymGetModuleBase64_ =
652 (SYMGETMODULEBASE64)GetProcAddress(dbghelp_dll, "SymGetModuleBase64");
654 if (! (MiniDumpWriteDump_ &&
655 SymInitialize_ && SymSetOptions_ && SymGetOptions_ &&
656 SymCleanup_ && SymGetTypeInfo_ && SymGetLineFromAddr64_ &&
657 SymEnumSymbols_ && SymSetContext_ && SymFromAddr_ &&
658 SymGetModuleBase64_ && StackWalk64_ &&
659 SymFunctionTableAccess64_))
662 /* initialize the symbol loading code */
663 opts = SymGetOptions_();
665 /* Set the 'load lines' option to retrieve line number information;
666 set the Deferred Loads option to map the debug info in memory only
668 SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS);
670 /* Initialize the debughlp DLL with the default path and automatic
671 module enumeration (and loading of symbol tables) for this process.
673 SymInitialize_(GetCurrentProcess(), NULL, TRUE);
680 FreeLibrary(dbghelp_dll);
685 /* Cleanup the dbghelp.dll library */
689 SymCleanup_(GetCurrentProcess());
691 FreeLibrary(dbghelp_dll);
694 /* Create a filename based on a prefix, the timestamp and an extension.
695 check if the filename was already taken, retry 3 times. */
697 get_temp_filename(char *filename, const char *prefix, const char *ext)
699 char temp_dir[MAX_PATH - 64];
702 if (! GetTempPath(MAX_PATH - 64, temp_dir))
705 for (i = 0;i < 3;i++)
712 strftime(time_str, 64, "%Y%m%d%H%M%S", localtime(&now));
713 sprintf(filename, "%s%s%s.%s", temp_dir, prefix, time_str, ext);
715 file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW,
716 FILE_ATTRIBUTE_NORMAL, NULL);
717 if (file != INVALID_HANDLE_VALUE)
728 /* Unhandled exception callback set with SetUnhandledExceptionFilter() */
730 svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs)
732 char dmp_filename[MAX_PATH];
733 char log_filename[MAX_PATH];
736 /* Check if the crash handler was already loaded (crash while handling the
738 if (dbghelp_dll != INVALID_HANDLE_VALUE)
739 return EXCEPTION_CONTINUE_SEARCH;
741 /* don't log anything if we're running inside a debugger ... */
742 if (is_debugger_present())
743 return EXCEPTION_CONTINUE_SEARCH;
745 /* ... or if we can't create the log files ... */
746 if (!get_temp_filename(dmp_filename, LOGFILE_PREFIX, "dmp") ||
747 !get_temp_filename(log_filename, LOGFILE_PREFIX, "log"))
748 return EXCEPTION_CONTINUE_SEARCH;
750 /* If we can't load a recent version of the dbghelp.dll, pass on this
752 if (!load_dbghelp_dll())
753 return EXCEPTION_CONTINUE_SEARCH;
756 log_file = fopen(log_filename, "w+");
758 /* write information about the process */
759 fprintf(log_file, "\nProcess info:\n");
760 write_process_info(ptrs ? ptrs->ExceptionRecord : NULL,
761 ptrs ? ptrs->ContextRecord : NULL,
764 /* write the stacktrace, if available */
765 fprintf(log_file, "\nStacktrace:\n");
766 write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, log_file);
768 /* write the minidump file and use the callback to write the list of modules
770 fprintf(log_file, "\n\nLoaded modules:\n");
771 write_minidump_file(dmp_filename, ptrs,
772 write_module_info_callback, (void *)log_file);
776 /* inform the user */
777 fprintf(stderr, "This application has halted due to an unexpected error.\n"
778 "A crash report and minidump file were saved to disk, you"
779 " can find them here:\n"
781 "Please send the log file to %s to help us analyze\nand "
782 "solve this problem.\n\n"
783 "NOTE: The crash report and minidump files can contain some"
784 " sensitive information\n(filenames, partial file content, "
785 "usernames and passwords etc.)\n",
790 if (getenv("SVN_DBG_STACKTRACES_TO_STDERR") != NULL)
792 fprintf(stderr, "\nProcess info:\n");
793 write_process_info(ptrs ? ptrs->ExceptionRecord : NULL,
794 ptrs ? ptrs->ContextRecord : NULL,
796 fprintf(stderr, "\nStacktrace:\n");
797 write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, stderr);
805 /* terminate the application */
806 return EXCEPTION_EXECUTE_HANDLER;
808 #endif /* SVN_USE_WIN32_CRASHHANDLER */