/* * win32_crashrpt.c : provides information after a crash * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ /* prevent "empty compilation unit" warning on e.g. UNIX */ typedef int win32_crashrpt__dummy; #ifdef WIN32 #ifdef SVN_USE_WIN32_CRASHHANDLER /*** Includes. ***/ #include #include #include #include #include #include #include "svn_version.h" #include "sysinfo.h" #include "win32_crashrpt.h" #include "win32_crashrpt_dll.h" /*** Global variables ***/ static HANDLE dbghelp_dll = INVALID_HANDLE_VALUE; #define DBGHELP_DLL "dbghelp.dll" #define LOGFILE_PREFIX "svn-crash-log" #if defined(_M_IX86) #define FORMAT_PTR "0x%08Ix" #elif defined(_M_X64) #define FORMAT_PTR "0x%016Ix" #endif /*** Code. ***/ /* Convert a wide-character string to the current windows locale, suitable * for directly using stdio. This function will create a buffer large * enough to hold the result string, the caller should free this buffer. * If the string can't be converted, NULL is returned. */ static char * convert_wbcs_to_ansi(const wchar_t *str) { size_t len = wcslen(str); char *utf8_str = malloc(sizeof(wchar_t) * len + 1); len = wcstombs(utf8_str, str, len); if (len == -1) return NULL; utf8_str[len] = '\0'; return utf8_str; } /* Convert the exception code to a string */ static const char * exception_string(int exception) { #define EXCEPTION(x) case x: return (#x); switch (exception) { EXCEPTION(EXCEPTION_ACCESS_VIOLATION) EXCEPTION(EXCEPTION_DATATYPE_MISALIGNMENT) EXCEPTION(EXCEPTION_BREAKPOINT) EXCEPTION(EXCEPTION_SINGLE_STEP) EXCEPTION(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) EXCEPTION(EXCEPTION_FLT_DENORMAL_OPERAND) EXCEPTION(EXCEPTION_FLT_DIVIDE_BY_ZERO) EXCEPTION(EXCEPTION_FLT_INEXACT_RESULT) EXCEPTION(EXCEPTION_FLT_INVALID_OPERATION) EXCEPTION(EXCEPTION_FLT_OVERFLOW) EXCEPTION(EXCEPTION_FLT_STACK_CHECK) EXCEPTION(EXCEPTION_FLT_UNDERFLOW) EXCEPTION(EXCEPTION_INT_DIVIDE_BY_ZERO) EXCEPTION(EXCEPTION_INT_OVERFLOW) EXCEPTION(EXCEPTION_PRIV_INSTRUCTION) EXCEPTION(EXCEPTION_IN_PAGE_ERROR) EXCEPTION(EXCEPTION_ILLEGAL_INSTRUCTION) EXCEPTION(EXCEPTION_NONCONTINUABLE_EXCEPTION) EXCEPTION(EXCEPTION_STACK_OVERFLOW) EXCEPTION(EXCEPTION_INVALID_DISPOSITION) EXCEPTION(EXCEPTION_GUARD_PAGE) EXCEPTION(EXCEPTION_INVALID_HANDLE) EXCEPTION(STATUS_NO_MEMORY) default: return "UNKNOWN_ERROR"; } #undef EXCEPTION } /* Write the minidump to file. The callback function will at the same time write the list of modules to the log file. */ static BOOL write_minidump_file(const char *file, PEXCEPTION_POINTERS ptrs, MINIDUMP_CALLBACK_ROUTINE module_callback, void *data) { /* open minidump file */ HANDLE minidump_file = CreateFile(file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (minidump_file != INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION expt_info; MINIDUMP_CALLBACK_INFORMATION dump_cb_info; expt_info.ThreadId = GetCurrentThreadId(); expt_info.ExceptionPointers = ptrs; expt_info.ClientPointers = FALSE; dump_cb_info.CallbackRoutine = module_callback; dump_cb_info.CallbackParam = data; MiniDumpWriteDump_(GetCurrentProcess(), GetCurrentProcessId(), minidump_file, MiniDumpNormal, ptrs ? &expt_info : NULL, NULL, &dump_cb_info); CloseHandle(minidump_file); return TRUE; } return FALSE; } /* Write module information to the log file */ static BOOL CALLBACK write_module_info_callback(void *data, CONST PMINIDUMP_CALLBACK_INPUT callback_input, PMINIDUMP_CALLBACK_OUTPUT callback_output) { if (data != NULL && callback_input != NULL && callback_input->CallbackType == ModuleCallback) { FILE *log_file = (FILE *)data; MINIDUMP_MODULE_CALLBACK module = callback_input->Module; char *buf = convert_wbcs_to_ansi(module.FullPath); fprintf(log_file, FORMAT_PTR, (UINT_PTR)module.BaseOfImage); fprintf(log_file, " %s", buf); free(buf); fprintf(log_file, " (%d.%d.%d.%d, %d bytes)\n", HIWORD(module.VersionInfo.dwFileVersionMS), LOWORD(module.VersionInfo.dwFileVersionMS), HIWORD(module.VersionInfo.dwFileVersionLS), LOWORD(module.VersionInfo.dwFileVersionLS), module.SizeOfImage); } return TRUE; } /* Write details about the current process, platform and the exception */ static void write_process_info(EXCEPTION_RECORD *exception, CONTEXT *context, FILE *log_file) { OSVERSIONINFOEXW oi; const char *cmd_line; char workingdir[8192]; /* write the command line */ cmd_line = GetCommandLine(); fprintf(log_file, "Cmd line: %s\n", cmd_line); _getcwd(workingdir, sizeof(workingdir)); fprintf(log_file, "Working Dir: %s\n", workingdir); /* write the svn version number info. */ fprintf(log_file, "Version: %s, compiled %s, %s\n", SVN_VERSION, __DATE__, __TIME__); /* write information about the OS */ if (svn_sysinfo___fill_windows_version(&oi)) fprintf(log_file, "Platform: Windows OS version %d.%d build %d %S\n\n", oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber, oi.szCSDVersion); /* write the exception code */ fprintf(log_file, "Exception: %s\n\n", exception_string(exception->ExceptionCode)); /* write the register info. */ fprintf(log_file, "Registers:\n"); #if defined(_M_IX86) fprintf(log_file, "eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n", context->Eax, context->Ebx, context->Ecx, context->Edx, context->Esi, context->Edi); fprintf(log_file, "eip=%08x esp=%08x ebp=%08x efl=%08x\n", context->Eip, context->Esp, context->Ebp, context->EFlags); fprintf(log_file, "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs); #elif defined(_M_X64) fprintf(log_file, "Rax=%016I64x Rcx=%016I64x Rdx=%016I64x Rbx=%016I64x\n", context->Rax, context->Rcx, context->Rdx, context->Rbx); fprintf(log_file, "Rsp=%016I64x Rbp=%016I64x Rsi=%016I64x Rdi=%016I64x\n", context->Rsp, context->Rbp, context->Rsi, context->Rdi); fprintf(log_file, "R8= %016I64x R9= %016I64x R10=%016I64x R11=%016I64x\n", context->R8, context->R9, context->R10, context->R11); fprintf(log_file, "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n", context->R12, context->R13, context->R14, context->R15); fprintf(log_file, "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x\n", context->SegCs, context->SegSs, context->SegDs, context->SegEs, context->SegFs, context->SegGs); #else #error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER #endif } /* Writes the value at address based on the specified basic type * (char, int, long ...) to LOG_FILE. */ static void write_basic_type(FILE *log_file, DWORD basic_type, DWORD64 length, void *address) { switch(length) { case 1: fprintf(log_file, "0x%02x", (int)*(unsigned char *)address); break; case 2: fprintf(log_file, "0x%04x", (int)*(unsigned short *)address); break; case 4: switch(basic_type) { case 2: /* btChar */ { if (!IsBadStringPtr(*(PSTR*)address, 32)) fprintf(log_file, "\"%.31s\"", *(const char **)address); else fprintf(log_file, FORMAT_PTR, *(DWORD_PTR *)address); } case 6: /* btInt */ fprintf(log_file, "%d", *(int *)address); break; case 8: /* btFloat */ fprintf(log_file, "%f", *(float *)address); break; default: fprintf(log_file, FORMAT_PTR, *(DWORD_PTR *)address); break; } break; case 8: if (basic_type == 8) /* btFloat */ fprintf(log_file, "%lf", *(double *)address); else fprintf(log_file, "0x%016I64X", *(unsigned __int64 *)address); break; default: fprintf(log_file, "[unhandled type 0x%08x of length " FORMAT_PTR "]", basic_type, (UINT_PTR)length); break; } } /* Writes the value at address based on the type (pointer, user defined, * basic type) to LOG_FILE. */ static void write_value(FILE *log_file, DWORD64 mod_base, DWORD type, void *value_addr) { DWORD tag = 0; int ptr = 0; HANDLE proc = GetCurrentProcess(); while (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMTAG, &tag)) { /* SymTagPointerType */ if (tag == 14) { ptr++; SymGetTypeInfo_(proc, mod_base, type, TI_GET_TYPE, &type); continue; } break; } switch(tag) { case 11: /* SymTagUDT */ { WCHAR *type_name_wbcs; if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMNAME, &type_name_wbcs)) { char *type_name = convert_wbcs_to_ansi(type_name_wbcs); LocalFree(type_name_wbcs); if (ptr == 0) fprintf(log_file, "(%s) " FORMAT_PTR, type_name, (UINT_PTR)(DWORD_PTR *)value_addr); else if (ptr == 1) fprintf(log_file, "(%s *) " FORMAT_PTR, type_name, *(DWORD_PTR *)value_addr); else fprintf(log_file, "(%s **) " FORMAT_PTR, type_name, *(DWORD_PTR *)value_addr); free(type_name); } else fprintf(log_file, "[no symbol tag]"); } break; case 16: /* SymTagBaseType */ { DWORD bt; ULONG64 length; SymGetTypeInfo_(proc, mod_base, type, TI_GET_LENGTH, &length); /* print a char * as a string */ if (ptr == 1 && length == 1) { fprintf(log_file, FORMAT_PTR " \"%s\"", *(DWORD_PTR *)value_addr, *(const char **)value_addr); } else if (ptr >= 1) { fprintf(log_file, FORMAT_PTR, *(DWORD_PTR *)value_addr); } else if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_BASETYPE, &bt)) { write_basic_type(log_file, bt, length, value_addr); } } break; case 12: /* SymTagEnum */ fprintf(log_file, "%Id", *(DWORD_PTR *)value_addr); break; case 13: /* SymTagFunctionType */ fprintf(log_file, FORMAT_PTR, *(DWORD_PTR *)value_addr); break; default: fprintf(log_file, "[unhandled tag: %d]", tag); break; } } /* Internal structure used to pass some data to the enumerate symbols * callback */ typedef struct symbols_baton_t { STACKFRAME64 *stack_frame; FILE *log_file; int nr_of_frame; BOOL log_params; } symbols_baton_t; /* Write the details of one parameter or local variable to the log file */ static BOOL WINAPI write_var_values(PSYMBOL_INFO sym_info, ULONG sym_size, void *baton) { static int last_nr_of_frame = 0; DWORD_PTR var_data = 0; /* Will point to the variable's data in memory */ STACKFRAME64 *stack_frame = ((symbols_baton_t*)baton)->stack_frame; FILE *log_file = ((symbols_baton_t*)baton)->log_file; int nr_of_frame = ((symbols_baton_t*)baton)->nr_of_frame; BOOL log_params = ((symbols_baton_t*)baton)->log_params; /* get the variable's data */ if (sym_info->Flags & SYMFLAG_REGREL) { var_data = (DWORD_PTR)stack_frame->AddrFrame.Offset; var_data += (DWORD_PTR)sym_info->Address; } else return FALSE; if (log_params && sym_info->Flags & SYMFLAG_PARAMETER) { if (last_nr_of_frame == nr_of_frame) fprintf(log_file, ", "); else last_nr_of_frame = nr_of_frame; fprintf(log_file, "%.*s=", (int)sym_info->NameLen, sym_info->Name); write_value(log_file, sym_info->ModBase, sym_info->TypeIndex, (void *)var_data); } if (!log_params && sym_info->Flags & SYMFLAG_LOCAL) { fprintf(log_file, " %.*s = ", (int)sym_info->NameLen, sym_info->Name); write_value(log_file, sym_info->ModBase, sym_info->TypeIndex, (void *)var_data); fprintf(log_file, "\n"); } return TRUE; } /* Write the details of one function to the log file */ static void write_function_detail(STACKFRAME64 stack_frame, int nr_of_frame, FILE *log_file) { ULONG64 symbolBuffer[(sizeof(SYMBOL_INFO) + MAX_SYM_NAME + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; PSYMBOL_INFO pIHS = (PSYMBOL_INFO)symbolBuffer; DWORD64 func_disp=0; IMAGEHLP_STACK_FRAME ih_stack_frame; IMAGEHLP_LINE64 ih_line; DWORD line_disp=0; HANDLE proc = GetCurrentProcess(); symbols_baton_t ensym; nr_of_frame++; /* We need a 1 based index here */ /* log the function name */ pIHS->SizeOfStruct = sizeof(SYMBOL_INFO); pIHS->MaxNameLen = MAX_SYM_NAME; if (SymFromAddr_(proc, stack_frame.AddrPC.Offset, &func_disp, pIHS)) { fprintf(log_file, "#%d 0x%08I64x in %.*s(", nr_of_frame, stack_frame.AddrPC.Offset, pIHS->NameLen > 200 ? 200 : (int)pIHS->NameLen, pIHS->Name); /* restrict symbol enumeration to this frame only */ ih_stack_frame.InstructionOffset = stack_frame.AddrPC.Offset; SymSetContext_(proc, &ih_stack_frame, 0); ensym.log_file = log_file; ensym.stack_frame = &stack_frame; ensym.nr_of_frame = nr_of_frame; /* log all function parameters */ ensym.log_params = TRUE; SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym); fprintf(log_file, ")"); } else { fprintf(log_file, "#%d 0x%08I64x in (unknown function)", nr_of_frame, stack_frame.AddrPC.Offset); } /* find the source line for this function. */ ih_line.SizeOfStruct = sizeof(IMAGEHLP_LINE); if (SymGetLineFromAddr64_(proc, stack_frame.AddrPC.Offset, &line_disp, &ih_line) != 0) { fprintf(log_file, " at %s:%d\n", ih_line.FileName, ih_line.LineNumber); } else { fprintf(log_file, "\n"); } /* log all function local variables */ ensym.log_params = FALSE; SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym); } /* Walk over the stack and log all relevant information to the log file */ static void write_stacktrace(CONTEXT *context, FILE *log_file) { #if defined (_M_IX86) || defined(_M_X64) || defined(_M_IA64) HANDLE proc = GetCurrentProcess(); STACKFRAME64 stack_frame; DWORD machine; CONTEXT ctx; int skip = 0, i = 0; /* The thread information - if not supplied. */ if (context == NULL) { /* If no context is supplied, skip 1 frame */ skip = 1; ctx.ContextFlags = CONTEXT_FULL; if (!GetThreadContext(GetCurrentThread(), &ctx)) return; } else { ctx = *context; } if (context == NULL) return; /* Write the stack trace */ ZeroMemory(&stack_frame, sizeof(STACKFRAME64)); stack_frame.AddrPC.Mode = AddrModeFlat; stack_frame.AddrStack.Mode = AddrModeFlat; stack_frame.AddrFrame.Mode = AddrModeFlat; #if defined(_M_IX86) machine = IMAGE_FILE_MACHINE_I386; stack_frame.AddrPC.Offset = context->Eip; stack_frame.AddrStack.Offset = context->Esp; stack_frame.AddrFrame.Offset = context->Ebp; #elif defined(_M_X64) machine = IMAGE_FILE_MACHINE_AMD64; stack_frame.AddrPC.Offset = context->Rip; stack_frame.AddrStack.Offset = context->Rsp; stack_frame.AddrFrame.Offset = context->Rbp; #elif defined(_M_IA64) machine = IMAGE_FILE_MACHINE_IA64; stack_frame.AddrPC.Offset = context->StIIP; stack_frame.AddrStack.Offset = context->SP; stack_frame.AddrBStore.Mode = AddrModeFlat; stack_frame.AddrBStore.Offset = context->RsBSP; #else #error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER #endif while (1) { if (! StackWalk64_(machine, proc, GetCurrentThread(), &stack_frame, &ctx, NULL, SymFunctionTableAccess64_, SymGetModuleBase64_, NULL)) { break; } if (i >= skip) { /* Try to include symbolic information. Also check that the address is not zero. Sometimes StackWalk returns TRUE with a frame of zero. */ if (stack_frame.AddrPC.Offset != 0) { write_function_detail(stack_frame, i, log_file); } } i++; } #else #error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER #endif } /* Check if a debugger is attached to this process */ static BOOL is_debugger_present() { return IsDebuggerPresent(); } /* Load the dbghelp.dll file, try to find a version that matches our requirements. */ static BOOL load_dbghelp_dll() { dbghelp_dll = LoadLibrary(DBGHELP_DLL); if (dbghelp_dll != NULL) { DWORD opts; /* load the functions */ MiniDumpWriteDump_ = (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump"); SymInitialize_ = (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize"); SymSetOptions_ = (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions"); SymGetOptions_ = (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions"); SymCleanup_ = (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup"); SymGetTypeInfo_ = (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo"); SymGetLineFromAddr64_ = (SYMGETLINEFROMADDR64)GetProcAddress(dbghelp_dll, "SymGetLineFromAddr64"); SymEnumSymbols_ = (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols"); SymSetContext_ = (SYMSETCONTEXT)GetProcAddress(dbghelp_dll, "SymSetContext"); SymFromAddr_ = (SYMFROMADDR)GetProcAddress(dbghelp_dll, "SymFromAddr"); StackWalk64_ = (STACKWALK64)GetProcAddress(dbghelp_dll, "StackWalk64"); SymFunctionTableAccess64_ = (SYMFUNCTIONTABLEACCESS64)GetProcAddress(dbghelp_dll, "SymFunctionTableAccess64"); SymGetModuleBase64_ = (SYMGETMODULEBASE64)GetProcAddress(dbghelp_dll, "SymGetModuleBase64"); if (! (MiniDumpWriteDump_ && SymInitialize_ && SymSetOptions_ && SymGetOptions_ && SymCleanup_ && SymGetTypeInfo_ && SymGetLineFromAddr64_ && SymEnumSymbols_ && SymSetContext_ && SymFromAddr_ && SymGetModuleBase64_ && StackWalk64_ && SymFunctionTableAccess64_)) goto cleanup; /* initialize the symbol loading code */ opts = SymGetOptions_(); /* Set the 'load lines' option to retrieve line number information; set the Deferred Loads option to map the debug info in memory only when needed. */ SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS); /* Initialize the debughlp DLL with the default path and automatic module enumeration (and loading of symbol tables) for this process. */ SymInitialize_(GetCurrentProcess(), NULL, TRUE); return TRUE; } cleanup: if (dbghelp_dll) FreeLibrary(dbghelp_dll); return FALSE; } /* Cleanup the dbghelp.dll library */ static void cleanup_debughlp() { SymCleanup_(GetCurrentProcess()); FreeLibrary(dbghelp_dll); } /* Create a filename based on a prefix, the timestamp and an extension. check if the filename was already taken, retry 3 times. */ BOOL get_temp_filename(char *filename, const char *prefix, const char *ext) { char temp_dir[MAX_PATH - 64]; int i; if (! GetTempPath(MAX_PATH - 64, temp_dir)) return FALSE; for (i = 0;i < 3;i++) { HANDLE file; time_t now; char time_str[64]; time(&now); strftime(time_str, 64, "%Y%m%d%H%M%S", localtime(&now)); sprintf(filename, "%s%s%s.%s", temp_dir, prefix, time_str, ext); file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); if (file != INVALID_HANDLE_VALUE) { CloseHandle(file); return TRUE; } } filename[0] = '\0'; return FALSE; } /* Unhandled exception callback set with SetUnhandledExceptionFilter() */ LONG WINAPI svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs) { char dmp_filename[MAX_PATH]; char log_filename[MAX_PATH]; FILE *log_file; /* Check if the crash handler was already loaded (crash while handling the crash) */ if (dbghelp_dll != INVALID_HANDLE_VALUE) return EXCEPTION_CONTINUE_SEARCH; /* don't log anything if we're running inside a debugger ... */ if (is_debugger_present()) return EXCEPTION_CONTINUE_SEARCH; /* ... or if we can't create the log files ... */ if (!get_temp_filename(dmp_filename, LOGFILE_PREFIX, "dmp") || !get_temp_filename(log_filename, LOGFILE_PREFIX, "log")) return EXCEPTION_CONTINUE_SEARCH; /* If we can't load a recent version of the dbghelp.dll, pass on this exception */ if (!load_dbghelp_dll()) return EXCEPTION_CONTINUE_SEARCH; /* open log file */ log_file = fopen(log_filename, "w+"); /* write information about the process */ fprintf(log_file, "\nProcess info:\n"); write_process_info(ptrs ? ptrs->ExceptionRecord : NULL, ptrs ? ptrs->ContextRecord : NULL, log_file); /* write the stacktrace, if available */ fprintf(log_file, "\nStacktrace:\n"); write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, log_file); /* write the minidump file and use the callback to write the list of modules to the log file */ fprintf(log_file, "\n\nLoaded modules:\n"); write_minidump_file(dmp_filename, ptrs, write_module_info_callback, (void *)log_file); fclose(log_file); /* inform the user */ fprintf(stderr, "This application has halted due to an unexpected error.\n" "A crash report and minidump file were saved to disk, you" " can find them here:\n" "%s\n%s\n" "Please send the log file to %s to help us analyze\nand " "solve this problem.\n\n" "NOTE: The crash report and minidump files can contain some" " sensitive information\n(filenames, partial file content, " "usernames and passwords etc.)\n", log_filename, dmp_filename, SVN_WIN32_CRASHREPORT_EMAIL); if (getenv("SVN_DBG_STACKTRACES_TO_STDERR") != NULL) { fprintf(stderr, "\nProcess info:\n"); write_process_info(ptrs ? ptrs->ExceptionRecord : NULL, ptrs ? ptrs->ContextRecord : NULL, stderr); fprintf(stderr, "\nStacktrace:\n"); write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, stderr); } fflush(stderr); fflush(stdout); cleanup_debughlp(); /* terminate the application */ return EXCEPTION_EXECUTE_HANDLER; } #endif /* SVN_USE_WIN32_CRASHHANDLER */ #else /* !WIN32 */ /* Silence OSX ranlib warnings about object files with no symbols. */ #include extern const apr_uint32_t svn__fake__win32_crashrpt; const apr_uint32_t svn__fake__win32_crashrpt = 0xdeadbeef; #endif /* WIN32 */