#ifdef WIN32
#ifdef SVN_USE_WIN32_CRASHHANDLER
#include <apr.h>
#include <dbghelp.h>
#include <stdio.h>
#include <stdlib.h>
#include "svn_version.h"
#include "win32_crashrpt.h"
#include "win32_crashrpt_dll.h"
HANDLE dbghelp_dll = INVALID_HANDLE_VALUE;
#define CRASHREPORT_EMAIL "svn-breakage@subversion.tigris.org"
#define DBGHELP_DLL "dbghelp.dll"
#define VERSION_DLL "version.dll"
#define LOGFILE_PREFIX "svn-crash-log"
static char *
convert_wbcs_to_utf8(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;
}
static const char *
exception_string(int exception)
{
#define EXCEPTION(x) case EXCEPTION_##x: return (#x);
switch (exception)
{
EXCEPTION(ACCESS_VIOLATION)
EXCEPTION(DATATYPE_MISALIGNMENT)
EXCEPTION(BREAKPOINT)
EXCEPTION(SINGLE_STEP)
EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
EXCEPTION(FLT_DENORMAL_OPERAND)
EXCEPTION(FLT_DIVIDE_BY_ZERO)
EXCEPTION(FLT_INEXACT_RESULT)
EXCEPTION(FLT_INVALID_OPERATION)
EXCEPTION(FLT_OVERFLOW)
EXCEPTION(FLT_STACK_CHECK)
EXCEPTION(FLT_UNDERFLOW)
EXCEPTION(INT_DIVIDE_BY_ZERO)
EXCEPTION(INT_OVERFLOW)
EXCEPTION(PRIV_INSTRUCTION)
EXCEPTION(IN_PAGE_ERROR)
EXCEPTION(ILLEGAL_INSTRUCTION)
EXCEPTION(NONCONTINUABLE_EXCEPTION)
EXCEPTION(STACK_OVERFLOW)
EXCEPTION(INVALID_DISPOSITION)
EXCEPTION(GUARD_PAGE)
EXCEPTION(INVALID_HANDLE)
default:
return "UNKNOWN_ERROR";
}
#undef EXCEPTION
}
static BOOL
write_minidump_file(const char *file, PEXCEPTION_POINTERS ptrs,
MINIDUMP_CALLBACK_ROUTINE module_callback,
void *data)
{
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;
}
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_utf8(module.FullPath);
fprintf(log_file, "0x%08x", 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;
}
static void
write_process_info(EXCEPTION_RECORD *exception, CONTEXT *context,
FILE *log_file)
{
OSVERSIONINFO oi;
const char *cmd_line;
cmd_line = GetCommandLine();
fprintf(log_file,
"Cmd line: %.65s\n", cmd_line);
fprintf(log_file,
"Version: %s, compiled %s, %s\n",
SVN_VERSION, __DATE__, __TIME__);
oi.dwOSVersionInfoSize = sizeof(oi);
GetVersionEx(&oi);
fprintf(log_file,
"Platform: Windows OS version %d.%d build %d %s\n\n",
oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber,
oi.szCSDVersion);
fprintf(log_file,
"Exception: %s\n\n",
exception_string(exception->ExceptionCode));
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 ss=%04x\n",
context->SegCs, context->SegDs, context->SegEs,
context->SegFs, context->SegGs, context->SegSs);
#else
#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
#endif
}
static void
format_basic_type(char *buf, DWORD basic_type, DWORD64 length, void *address)
{
switch(length)
{
case 1:
sprintf(buf, "%x", *(unsigned char *)address);
break;
case 2:
sprintf(buf, "%x", *(unsigned short *)address);
break;
case 4:
switch(basic_type)
{
case 2:
{
if (!IsBadStringPtr(*(PSTR*)address, 32))
sprintf(buf, "\"%.31s\"", *(unsigned long *)address);
else
sprintf(buf, "%x", *(unsigned long *)address);
}
case 6:
sprintf(buf, "%d", *(int *)address);
break;
case 8:
sprintf(buf, "%f", *(float *)address);
break;
default:
sprintf(buf, "%x", *(unsigned long *)address);
break;
}
break;
case 8:
if (basic_type == 8)
sprintf(buf, "%lf", *(double *)address);
else
sprintf(buf, "%I64X", *(unsigned __int64 *)address);
break;
}
}
static void
format_value(char *value_str, DWORD64 mod_base, DWORD type, void *value_addr)
{
DWORD tag;
int ptr = 0;
HANDLE proc = GetCurrentProcess();
while (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMTAG, &tag))
{
if (tag == 14)
{
ptr++;
SymGetTypeInfo_(proc, mod_base, type, TI_GET_TYPE, &type);
continue;
}
break;
}
switch(tag)
{
case 11:
{
WCHAR *type_name_wbcs;
if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMNAME,
&type_name_wbcs))
{
char *type_name = convert_wbcs_to_utf8(type_name_wbcs);
LocalFree(type_name_wbcs);
if (ptr == 0)
sprintf(value_str, "(%s) 0x%08x",
type_name, (DWORD *)value_addr);
else if (ptr == 1)
sprintf(value_str, "(%s *) 0x%08x",
type_name, *(DWORD *)value_addr);
else
sprintf(value_str, "(%s **) 0x%08x",
type_name, (DWORD *)value_addr);
free(type_name);
}
}
break;
case 16:
{
DWORD bt;
ULONG64 length;
SymGetTypeInfo_(proc, mod_base, type, TI_GET_LENGTH, &length);
if (ptr == 1 && length == 1)
{
sprintf(value_str, "0x%08x \"%s\"",
*(DWORD *)value_addr, (char *)*(DWORD*)value_addr);
break;
}
if (ptr >= 1)
{
sprintf(value_str, "0x%08x", *(DWORD *)value_addr);
break;
}
if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_BASETYPE, &bt))
{
format_basic_type(value_str, bt, length, value_addr);
break;
}
}
break;
case 12:
sprintf(value_str, "%d", *(DWORD *)value_addr);
break;
case 13:
sprintf(value_str, "0x%08x", *(DWORD *)value_addr);
break;
default: break;
}
}
typedef struct {
STACKFRAME64 *stack_frame;
FILE *log_file;
int nr_of_frame;
BOOL log_params;
} symbols_baton_t;
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;
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;
char value_str[256] = "";
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 == TRUE && sym_info->Flags & SYMFLAG_PARAMETER)
{
if (last_nr_of_frame == nr_of_frame)
fprintf(log_file, ", ", 2);
else
last_nr_of_frame = nr_of_frame;
format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
(void *)var_data);
fprintf(log_file, "%s=%s", sym_info->Name, value_str);
}
if (log_params == FALSE && sym_info->Flags & SYMFLAG_LOCAL)
{
format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
(void *)var_data);
fprintf(log_file, " %s = %s\n", sym_info->Name, value_str);
}
return TRUE;
}
static void
write_function_detail(STACKFRAME64 stack_frame, void *data)
{
ULONG64 symbolBuffer[(sizeof(SYMBOL_INFO) +
MAX_PATH +
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();
FILE *log_file = (FILE *)data;
symbols_baton_t ensym;
static int nr_of_frame = 0;
nr_of_frame++;
pIHS->SizeOfStruct = sizeof(SYMBOL_INFO);
pIHS->MaxNameLen = MAX_PATH;
if (SymFromAddr_(proc, stack_frame.AddrPC.Offset, &func_disp, pIHS) == TRUE)
{
fprintf(log_file,
"#%d 0x%08x in %.200s (",
nr_of_frame, stack_frame.AddrPC.Offset, pIHS->Name);
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;
ensym.log_params = TRUE;
SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
fprintf(log_file, ")");
}
else
{
fprintf(log_file,
"#%d 0x%08x in (unknown function)",
nr_of_frame, stack_frame.AddrPC.Offset);
}
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");
}
ensym.log_params = FALSE;
SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
}
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;
if (context == NULL)
{
skip = 1;
ctx.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(GetCurrentThread(), &ctx))
context = &ctx;
}
if (context == NULL)
return;
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, context, NULL,
SymFunctionTableAccess64_, SymGetModuleBase64_, NULL))
{
break;
}
if (i >= skip)
{
if (stack_frame.AddrPC.Offset != 0)
{
write_function_detail(stack_frame, (void *)log_file);
}
}
i++;
}
#else
#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
#endif
}
static BOOL
is_debugger_present()
{
HANDLE kernel32_dll = LoadLibrary("kernel32.dll");
BOOL result;
ISDEBUGGERPRESENT IsDebuggerPresent_ =
(ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent");
if (IsDebuggerPresent_ && IsDebuggerPresent_())
result = TRUE;
else
result = FALSE;
FreeLibrary(kernel32_dll);
return result;
}
static BOOL
check_dbghelp_version(WORD exp_major, WORD exp_minor, WORD exp_build,
WORD exp_qfe)
{
HANDLE version_dll = LoadLibrary(VERSION_DLL);
GETFILEVERSIONINFOSIZE GetFileVersionInfoSize_ =
(GETFILEVERSIONINFOSIZE)GetProcAddress(version_dll,
"GetFileVersionInfoSizeA");
GETFILEVERSIONINFO GetFileVersionInfo_ =
(GETFILEVERSIONINFO)GetProcAddress(version_dll,
"GetFileVersionInfoA");
VERQUERYVALUE VerQueryValue_ =
(VERQUERYVALUE)GetProcAddress(version_dll, "VerQueryValueA");
DWORD version = 0,
exp_version = MAKELONG(MAKEWORD(exp_qfe, exp_build),
MAKEWORD(exp_minor, exp_major));
DWORD h = 0;
DWORD resource_size = GetFileVersionInfoSize_(DBGHELP_DLL, &h);
if (resource_size)
{
void *resource_data = malloc(resource_size);
if (GetFileVersionInfo_(DBGHELP_DLL, h, resource_size,
resource_data) != FALSE)
{
void *buf = NULL;
UINT len;
if (VerQueryValue_(resource_data, "\\", &buf, &len))
{
VS_FIXEDFILEINFO *info = (VS_FIXEDFILEINFO*)buf;
version = MAKELONG(MAKEWORD(LOWORD(info->dwFileVersionLS),
HIWORD(info->dwFileVersionLS)),
MAKEWORD(LOWORD(info->dwFileVersionMS),
HIWORD(info->dwFileVersionMS)));
}
}
free(resource_data);
}
FreeLibrary(version_dll);
if (version >= exp_version)
return TRUE;
return FALSE;
}
static BOOL
load_dbghelp_dll()
{
if (check_dbghelp_version(6, 6, 7, 5) == FALSE)
return FALSE;
dbghelp_dll = LoadLibrary(DBGHELP_DLL);
if (dbghelp_dll != INVALID_HANDLE_VALUE)
{
DWORD opts;
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;
opts = SymGetOptions_();
SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS);
SymInitialize_(GetCurrentProcess(), NULL, TRUE);
return TRUE;
}
cleanup:
if (dbghelp_dll)
FreeLibrary(dbghelp_dll);
return FALSE;
}
static void
cleanup_debughlp()
{
SymCleanup_(GetCurrentProcess());
FreeLibrary(dbghelp_dll);
}
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;
}
LONG WINAPI
svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs)
{
char dmp_filename[MAX_PATH];
char log_filename[MAX_PATH];
FILE *log_file;
if (dbghelp_dll != INVALID_HANDLE_VALUE)
return EXCEPTION_CONTINUE_SEARCH;
if (is_debugger_present() == TRUE)
return EXCEPTION_CONTINUE_SEARCH;
if (get_temp_filename(dmp_filename, LOGFILE_PREFIX, "dmp") == FALSE ||
get_temp_filename(log_filename, LOGFILE_PREFIX, "log") == FALSE)
return EXCEPTION_CONTINUE_SEARCH;
if (load_dbghelp_dll() == FALSE)
return EXCEPTION_CONTINUE_SEARCH;
log_file = fopen(log_filename, "w+");
fprintf(log_file, "\nProcess info:\n");
write_process_info(ptrs ? ptrs->ExceptionRecord : NULL,
ptrs ? ptrs->ContextRecord : NULL,
log_file);
fprintf(log_file, "\nStacktrace:\n");
write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, 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);
cleanup_debughlp();
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 analyse\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,
CRASHREPORT_EMAIL);
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
#endif