win32_crashrpt.c revision 262253
11590Srgrimes/*
21590Srgrimes * win32_crashrpt.c : provides information after a crash
31590Srgrimes *
41590Srgrimes * ====================================================================
51590Srgrimes *    Licensed to the Apache Software Foundation (ASF) under one
61590Srgrimes *    or more contributor license agreements.  See the NOTICE file
71590Srgrimes *    distributed with this work for additional information
81590Srgrimes *    regarding copyright ownership.  The ASF licenses this file
91590Srgrimes *    to you under the Apache License, Version 2.0 (the
101590Srgrimes *    "License"); you may not use this file except in compliance
111590Srgrimes *    with the License.  You may obtain a copy of the License at
121590Srgrimes *
131590Srgrimes *      http://www.apache.org/licenses/LICENSE-2.0
141590Srgrimes *
151590Srgrimes *    Unless required by applicable law or agreed to in writing,
161590Srgrimes *    software distributed under the License is distributed on an
171590Srgrimes *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
181590Srgrimes *    KIND, either express or implied.  See the License for the
191590Srgrimes *    specific language governing permissions and limitations
201590Srgrimes *    under the License.
211590Srgrimes * ====================================================================
221590Srgrimes */
231590Srgrimes
241590Srgrimes/* prevent "empty compilation unit" warning on e.g. UNIX */
251590Srgrimestypedef int win32_crashrpt__dummy;
261590Srgrimes
271590Srgrimes#ifdef WIN32
281590Srgrimes#ifdef SVN_USE_WIN32_CRASHHANDLER
2950477Speter
301590Srgrimes/*** Includes. ***/
31240506Seadler#include <apr.h>
321590Srgrimes#include <dbghelp.h>
3379535Sru#include <direct.h>
341590Srgrimes#include <stdio.h>
351590Srgrimes#include <stdlib.h>
361590Srgrimes#include <time.h>
371590Srgrimes
3868963Sru#include "svn_version.h"
39119024Stjr
4077291Sdd#include "win32_crashrpt.h"
4177291Sdd#include "win32_crashrpt_dll.h"
4277291Sdd
4377291Sdd/*** Global variables ***/
4477291SddHANDLE dbghelp_dll = INVALID_HANDLE_VALUE;
4577291Sdd
4677291Sdd/* Email address where the crash reports should be sent too. */
4777291Sdd#define CRASHREPORT_EMAIL "users@subversion.apache.org"
4877291Sdd
491590Srgrimes#define DBGHELP_DLL "dbghelp.dll"
501590Srgrimes
51118077Stjr#define LOGFILE_PREFIX "svn-crash-log"
521590Srgrimes
5379275Sru#if defined(_M_IX86)
541590Srgrimes#define FORMAT_PTR "0x%08x"
5595124Scharnier#elif defined(_M_X64)
5695124Scharnier#define FORMAT_PTR "0x%016I64x"
5795124Scharnier#endif
581590Srgrimes
591590Srgrimes/*** Code. ***/
601590Srgrimes
611590Srgrimes/* Convert a wide-character string to utf-8. This function will create a buffer
6277291Sdd * large enough to hold the result string, the caller should free this buffer.
6377291Sdd * If the string can't be converted, NULL is returned.
6476014Sdd */
651590Srgrimesstatic char *
661590Srgrimesconvert_wbcs_to_utf8(const wchar_t *str)
6776014Sdd{
6876014Sdd  size_t len = wcslen(str);
691590Srgrimes  char *utf8_str = malloc(sizeof(wchar_t) * len + 1);
7068963Sru  len = wcstombs(utf8_str, str, len);
711590Srgrimes
721590Srgrimes  if (len == -1)
7395124Scharnier    return NULL;
741590Srgrimes
7577291Sdd  utf8_str[len] = '\0';
7677291Sdd
7777291Sdd  return utf8_str;
7877291Sdd}
7977291Sdd
8077291Sdd/* Convert the exception code to a string */
81100440Scharnierstatic const char *
82202756Sedexception_string(int exception)
83100440Scharnier{
8477291Sdd#define EXCEPTION(x) case EXCEPTION_##x: return (#x);
8577291Sdd
8677291Sdd  switch (exception)
87118077Stjr    {
8877291Sdd      EXCEPTION(ACCESS_VIOLATION)
8977291Sdd      EXCEPTION(DATATYPE_MISALIGNMENT)
9077291Sdd      EXCEPTION(BREAKPOINT)
9177291Sdd      EXCEPTION(SINGLE_STEP)
9277291Sdd      EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
9377291Sdd      EXCEPTION(FLT_DENORMAL_OPERAND)
9477291Sdd      EXCEPTION(FLT_DIVIDE_BY_ZERO)
9577291Sdd      EXCEPTION(FLT_INEXACT_RESULT)
9677291Sdd      EXCEPTION(FLT_INVALID_OPERATION)
9777291Sdd      EXCEPTION(FLT_OVERFLOW)
9877291Sdd      EXCEPTION(FLT_STACK_CHECK)
9977291Sdd      EXCEPTION(FLT_UNDERFLOW)
10077291Sdd      EXCEPTION(INT_DIVIDE_BY_ZERO)
10177291Sdd      EXCEPTION(INT_OVERFLOW)
10277291Sdd      EXCEPTION(PRIV_INSTRUCTION)
10377291Sdd      EXCEPTION(IN_PAGE_ERROR)
10477291Sdd      EXCEPTION(ILLEGAL_INSTRUCTION)
10577291Sdd      EXCEPTION(NONCONTINUABLE_EXCEPTION)
10677291Sdd      EXCEPTION(STACK_OVERFLOW)
10777291Sdd      EXCEPTION(INVALID_DISPOSITION)
10877291Sdd      EXCEPTION(GUARD_PAGE)
10977291Sdd      EXCEPTION(INVALID_HANDLE)
11077291Sdd
11177291Sdd      default:
11277291Sdd        return "UNKNOWN_ERROR";
11377291Sdd    }
11477291Sdd#undef EXCEPTION
11577291Sdd}
11677291Sdd
11777291Sdd/* Write the minidump to file. The callback function will at the same time
11877291Sdd   write the list of modules to the log file. */
11977291Sddstatic BOOL
12077291Sddwrite_minidump_file(const char *file, PEXCEPTION_POINTERS ptrs,
12177291Sdd                    MINIDUMP_CALLBACK_ROUTINE module_callback,
12277291Sdd                    void *data)
12377291Sdd{
12477291Sdd  /* open minidump file */
12577291Sdd  HANDLE minidump_file = CreateFile(file, GENERIC_WRITE, 0, NULL,
12677291Sdd                                    CREATE_ALWAYS,
12777291Sdd                                    FILE_ATTRIBUTE_NORMAL,
12877291Sdd                                    NULL);
12977291Sdd
13077291Sdd  if (minidump_file != INVALID_HANDLE_VALUE)
13177291Sdd    {
13277291Sdd      MINIDUMP_EXCEPTION_INFORMATION expt_info;
13377291Sdd      MINIDUMP_CALLBACK_INFORMATION dump_cb_info;
13477291Sdd
13577291Sdd      expt_info.ThreadId = GetCurrentThreadId();
1361590Srgrimes      expt_info.ExceptionPointers = ptrs;
13795124Scharnier      expt_info.ClientPointers = FALSE;
1381590Srgrimes
1391590Srgrimes      dump_cb_info.CallbackRoutine = module_callback;
140202756Sed      dump_cb_info.CallbackParam = data;
14136434Sdanny
14236434Sdanny      MiniDumpWriteDump_(GetCurrentProcess(),
14336434Sdanny                         GetCurrentProcessId(),
144118077Stjr                         minidump_file,
145118077Stjr                         MiniDumpNormal,
146118077Stjr                         ptrs ? &expt_info : NULL,
147118077Stjr                         NULL,
14836434Sdanny                         &dump_cb_info);
14936434Sdanny
15036434Sdanny      CloseHandle(minidump_file);
1511590Srgrimes      return TRUE;
1521590Srgrimes    }
1531590Srgrimes
1541590Srgrimes  return FALSE;
1551590Srgrimes}
1561590Srgrimes
1571590Srgrimes/* Write module information to the log file */
1581590Srgrimesstatic BOOL CALLBACK
15936434Sdannywrite_module_info_callback(void *data,
16036434Sdanny                 CONST PMINIDUMP_CALLBACK_INPUT callback_input,
16136434Sdanny                 PMINIDUMP_CALLBACK_OUTPUT callback_output)
16291541Siedowse{
16391541Siedowse  if (data != NULL &&
1641590Srgrimes      callback_input != NULL &&
1651590Srgrimes      callback_input->CallbackType == ModuleCallback)
16677291Sdd    {
16777291Sdd      FILE *log_file = (FILE *)data;
16877291Sdd      MINIDUMP_MODULE_CALLBACK module = callback_input->Module;
1691590Srgrimes
1701590Srgrimes      char *buf = convert_wbcs_to_utf8(module.FullPath);
1711590Srgrimes      fprintf(log_file, FORMAT_PTR, module.BaseOfImage);
1721590Srgrimes      fprintf(log_file, "  %s", buf);
17357670Ssheldonh      free(buf);
17457670Ssheldonh
1751590Srgrimes      fprintf(log_file, " (%d.%d.%d.%d, %d bytes)\n",
17668963Sru                              HIWORD(module.VersionInfo.dwFileVersionMS),
1771590Srgrimes                              LOWORD(module.VersionInfo.dwFileVersionMS),
1781590Srgrimes                              HIWORD(module.VersionInfo.dwFileVersionLS),
1791590Srgrimes                              LOWORD(module.VersionInfo.dwFileVersionLS),
1801590Srgrimes                              module.SizeOfImage);
1811590Srgrimes    }
1821590Srgrimes
1831590Srgrimes  return TRUE;
1841590Srgrimes}
1851590Srgrimes
1861590Srgrimes/* Write details about the current process, platform and the exception */
18768963Srustatic void
1881590Srgrimeswrite_process_info(EXCEPTION_RECORD *exception, CONTEXT *context,
18976014Sdd                   FILE *log_file)
19076014Sdd{
19168963Sru  OSVERSIONINFO oi;
1921590Srgrimes  const char *cmd_line;
1931590Srgrimes  char workingdir[8192];
1941590Srgrimes
195202756Sed  /* write the command line */
196202756Sed  cmd_line = GetCommandLine();
1971590Srgrimes  fprintf(log_file,
1981590Srgrimes                "Cmd line: %s\n", cmd_line);
1991590Srgrimes
2001590Srgrimes  _getcwd(workingdir, sizeof(workingdir));
201230458Shrs  fprintf(log_file,
202202756Sed                "Working Dir: %s\n", workingdir);
2031590Srgrimes
204140420Sru  /* write the svn version number info. */
205140420Sru  fprintf(log_file,
206140420Sru                "Version:  %s, compiled %s, %s\n",
207140420Sru                SVN_VERSION, __DATE__, __TIME__);
208140420Sru
20911547Sdg  /* write information about the OS */
21011547Sdg  oi.dwOSVersionInfoSize = sizeof(oi);
211141846Sru  GetVersionEx(&oi);
212202756Sed
213100440Scharnier  fprintf(log_file,
21457670Ssheldonh                "Platform: Windows OS version %d.%d build %d %s\n\n",
21568963Sru                oi.dwMajorVersion, oi.dwMinorVersion, oi.dwBuildNumber,
21611547Sdg                oi.szCSDVersion);
217
218  /* write the exception code */
219  fprintf(log_file,
220               "Exception: %s\n\n",
221               exception_string(exception->ExceptionCode));
222
223  /* write the register info. */
224  fprintf(log_file,
225                "Registers:\n");
226#if defined(_M_IX86)
227  fprintf(log_file,
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);
231  fprintf(log_file,
232                "eip=%08x esp=%08x ebp=%08x efl=%08x\n",
233                context->Eip, context->Esp,
234                context->Ebp, context->EFlags);
235  fprintf(log_file,
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)
240  fprintf(log_file,
241                "Rax=%016I64x Rcx=%016I64x Rdx=%016I64x Rbx=%016I64x\n",
242                context->Rax, context->Rcx, context->Rdx, context->Rbx);
243  fprintf(log_file,
244                "Rsp=%016I64x Rbp=%016I64x Rsi=%016I64x Rdi=%016I64x\n",
245                context->Rsp, context->Rbp, context->Rsi, context->Rdi);
246  fprintf(log_file,
247                "R8= %016I64x R9= %016I64x R10= %016I64x R11=%016I64x\n",
248                context->R8, context->R9, context->R10, context->R11);
249  fprintf(log_file,
250                "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n",
251                context->R12, context->R13, context->R14, context->R15);
252
253  fprintf(log_file,
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);
257#else
258#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
259#endif
260}
261
262/* Formats the value at address based on the specified basic type
263 * (char, int, long ...). */
264static void
265format_basic_type(char *buf, DWORD basic_type, DWORD64 length, void *address)
266{
267  switch(length)
268    {
269      case 1:
270        sprintf(buf, "0x%02x", (int)*(unsigned char *)address);
271        break;
272      case 2:
273        sprintf(buf, "0x%04x", (int)*(unsigned short *)address);
274        break;
275      case 4:
276        switch(basic_type)
277          {
278            case 2:  /* btChar */
279              {
280                if (!IsBadStringPtr(*(PSTR*)address, 32))
281                  sprintf(buf, "\"%.31s\"", *(const char **)address);
282                else
283                  sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address);
284              }
285            case 6:  /* btInt */
286              sprintf(buf, "%d", *(int *)address);
287              break;
288            case 8:  /* btFloat */
289              sprintf(buf, "%f", *(float *)address);
290              break;
291            default:
292              sprintf(buf, FORMAT_PTR, *(DWORD_PTR *)address);
293              break;
294          }
295        break;
296      case 8:
297        if (basic_type == 8) /* btFloat */
298          sprintf(buf, "%lf", *(double *)address);
299        else
300          sprintf(buf, "0x%016I64X", *(unsigned __int64 *)address);
301        break;
302      default:
303        sprintf(buf, "[unhandled type 0x%08x of length " FORMAT_PTR "]",
304                     basic_type, length);
305        break;
306    }
307}
308
309/* Formats the value at address based on the type (pointer, user defined,
310 * basic type). */
311static void
312format_value(char *value_str, DWORD64 mod_base, DWORD type, void *value_addr)
313{
314  DWORD tag = 0;
315  int ptr = 0;
316  HANDLE proc = GetCurrentProcess();
317
318  while (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMTAG, &tag))
319    {
320      /* SymTagPointerType */
321      if (tag == 14)
322        {
323          ptr++;
324          SymGetTypeInfo_(proc, mod_base, type, TI_GET_TYPE, &type);
325          continue;
326        }
327      break;
328    }
329
330  switch(tag)
331    {
332      case 11: /* SymTagUDT */
333        {
334          WCHAR *type_name_wbcs;
335          if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_SYMNAME,
336                              &type_name_wbcs))
337            {
338              char *type_name = convert_wbcs_to_utf8(type_name_wbcs);
339              LocalFree(type_name_wbcs);
340
341              if (ptr == 0)
342                sprintf(value_str, "(%s) " FORMAT_PTR,
343                        type_name, (DWORD_PTR *)value_addr);
344              else if (ptr == 1)
345                sprintf(value_str, "(%s *) " FORMAT_PTR,
346                        type_name, *(DWORD_PTR *)value_addr);
347              else
348                sprintf(value_str, "(%s **) " FORMAT_PTR,
349                        type_name, *(DWORD_PTR *)value_addr);
350
351              free(type_name);
352            }
353          else
354            sprintf(value_str, "[no symbol tag]");
355        }
356        break;
357      case 16: /* SymTagBaseType */
358        {
359          DWORD bt;
360          ULONG64 length;
361          SymGetTypeInfo_(proc, mod_base, type, TI_GET_LENGTH, &length);
362
363          /* print a char * as a string */
364          if (ptr == 1 && length == 1)
365            {
366              sprintf(value_str, FORMAT_PTR " \"%s\"",
367                      *(DWORD_PTR *)value_addr, *(const char **)value_addr);
368            }
369          else if (ptr >= 1)
370            {
371              sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr);
372            }
373          else if (SymGetTypeInfo_(proc, mod_base, type, TI_GET_BASETYPE, &bt))
374            {
375              format_basic_type(value_str, bt, length, value_addr);
376            }
377        }
378        break;
379      case 12: /* SymTagEnum */
380          sprintf(value_str, "%d", *(DWORD_PTR *)value_addr);
381          break;
382      case 13: /* SymTagFunctionType */
383          sprintf(value_str, FORMAT_PTR, *(DWORD_PTR *)value_addr);
384          break;
385      default:
386          sprintf(value_str, "[unhandled tag: %d]", tag);
387          break;
388    }
389}
390
391/* Internal structure used to pass some data to the enumerate symbols
392 * callback */
393typedef struct symbols_baton_t {
394  STACKFRAME64 *stack_frame;
395  FILE *log_file;
396  int nr_of_frame;
397  BOOL log_params;
398} symbols_baton_t;
399
400/* Write the details of one parameter or local variable to the log file */
401static BOOL WINAPI
402write_var_values(PSYMBOL_INFO sym_info, ULONG sym_size, void *baton)
403{
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] = "";
411
412  /* get the variable's data */
413  if (sym_info->Flags & SYMFLAG_REGREL)
414    {
415      var_data = (DWORD_PTR)stack_frame->AddrFrame.Offset;
416      var_data += (DWORD_PTR)sym_info->Address;
417    }
418  else
419    return FALSE;
420
421  if (log_params && sym_info->Flags & SYMFLAG_PARAMETER)
422    {
423      if (last_nr_of_frame == nr_of_frame)
424        fprintf(log_file, ", ", 2);
425      else
426        last_nr_of_frame = nr_of_frame;
427
428      format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
429                   (void *)var_data);
430      fprintf(log_file, "%.*s=%s", (int)sym_info->NameLen, sym_info->Name,
431              value_str);
432    }
433  if (!log_params && sym_info->Flags & SYMFLAG_LOCAL)
434    {
435      format_value(value_str, sym_info->ModBase, sym_info->TypeIndex,
436                   (void *)var_data);
437      fprintf(log_file, "        %.*s = %s\n", (int)sym_info->NameLen,
438              sym_info->Name, value_str);
439    }
440
441  return TRUE;
442}
443
444/* Write the details of one function to the log file */
445static void
446write_function_detail(STACKFRAME64 stack_frame, int nr_of_frame, FILE *log_file)
447{
448  ULONG64 symbolBuffer[(sizeof(SYMBOL_INFO) +
449    MAX_SYM_NAME +
450    sizeof(ULONG64) - 1) /
451    sizeof(ULONG64)];
452  PSYMBOL_INFO pIHS = (PSYMBOL_INFO)symbolBuffer;
453  DWORD64 func_disp=0;
454
455  IMAGEHLP_STACK_FRAME ih_stack_frame;
456  IMAGEHLP_LINE64 ih_line;
457  DWORD line_disp=0;
458
459  HANDLE proc = GetCurrentProcess();
460
461  symbols_baton_t ensym;
462
463  nr_of_frame++; /* We need a 1 based index here */
464
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))
469    {
470      fprintf(log_file,
471                    "#%d  0x%08I64x in %.*s(",
472                    nr_of_frame, stack_frame.AddrPC.Offset,
473                    pIHS->NameLen > 200 ? 200 : (int)pIHS->NameLen,
474                    pIHS->Name);
475
476      /* restrict symbol enumeration to this frame only */
477      ih_stack_frame.InstructionOffset = stack_frame.AddrPC.Offset;
478      SymSetContext_(proc, &ih_stack_frame, 0);
479
480      ensym.log_file = log_file;
481      ensym.stack_frame = &stack_frame;
482      ensym.nr_of_frame = nr_of_frame;
483
484      /* log all function parameters */
485      ensym.log_params = TRUE;
486      SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
487
488      fprintf(log_file, ")");
489    }
490  else
491    {
492      fprintf(log_file,
493                    "#%d  0x%08I64x in (unknown function)",
494                    nr_of_frame, stack_frame.AddrPC.Offset);
495    }
496
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)
501    {
502      fprintf(log_file,
503                    " at %s:%d\n", ih_line.FileName, ih_line.LineNumber);
504    }
505  else
506    {
507      fprintf(log_file, "\n");
508    }
509
510  /* log all function local variables */
511  ensym.log_params = FALSE;
512  SymEnumSymbols_(proc, 0, 0, write_var_values, &ensym);
513}
514
515/* Walk over the stack and log all relevant information to the log file */
516static void
517write_stacktrace(CONTEXT *context, FILE *log_file)
518{
519#if defined (_M_IX86) || defined(_M_X64) || defined(_M_IA64)
520  HANDLE proc = GetCurrentProcess();
521  STACKFRAME64 stack_frame;
522  DWORD machine;
523  CONTEXT ctx;
524  int skip = 0, i = 0;
525
526  /* The thread information - if not supplied. */
527  if (context == NULL)
528    {
529      /* If no context is supplied, skip 1 frame */
530      skip = 1;
531
532      ctx.ContextFlags = CONTEXT_FULL;
533      if (!GetThreadContext(GetCurrentThread(), &ctx))
534        return;
535    }
536  else
537    {
538      ctx = *context;
539    }
540
541  if (context == NULL)
542    return;
543
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;
549
550#if defined(_M_IX86)
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;
566#else
567#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
568#endif
569
570  while (1)
571    {
572      if (! StackWalk64_(machine, proc, GetCurrentThread(),
573                         &stack_frame, &ctx, NULL,
574                         SymFunctionTableAccess64_, SymGetModuleBase64_, NULL))
575        {
576          break;
577        }
578
579      if (i >= skip)
580        {
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)
585            {
586              write_function_detail(stack_frame, i, log_file);
587            }
588        }
589      i++;
590    }
591#else
592#error Unknown processortype, please disable SVN_USE_WIN32_CRASHHANDLER
593#endif
594}
595
596/* Check if a debugger is attached to this process */
597static BOOL
598is_debugger_present()
599{
600  HANDLE kernel32_dll = LoadLibrary("kernel32.dll");
601  BOOL result;
602
603  ISDEBUGGERPRESENT IsDebuggerPresent_ =
604          (ISDEBUGGERPRESENT)GetProcAddress(kernel32_dll, "IsDebuggerPresent");
605
606  if (IsDebuggerPresent_ && IsDebuggerPresent_())
607    result = TRUE;
608  else
609    result = FALSE;
610
611  FreeLibrary(kernel32_dll);
612
613  return result;
614}
615
616/* Load the dbghelp.dll file, try to find a version that matches our
617   requirements. */
618static BOOL
619load_dbghelp_dll()
620{
621  dbghelp_dll = LoadLibrary(DBGHELP_DLL);
622  if (dbghelp_dll != INVALID_HANDLE_VALUE)
623    {
624      DWORD opts;
625
626      /* load the functions */
627      MiniDumpWriteDump_ =
628           (MINIDUMPWRITEDUMP)GetProcAddress(dbghelp_dll, "MiniDumpWriteDump");
629      SymInitialize_ =
630           (SYMINITIALIZE)GetProcAddress(dbghelp_dll, "SymInitialize");
631      SymSetOptions_ =
632           (SYMSETOPTIONS)GetProcAddress(dbghelp_dll, "SymSetOptions");
633      SymGetOptions_ =
634           (SYMGETOPTIONS)GetProcAddress(dbghelp_dll, "SymGetOptions");
635      SymCleanup_ =
636           (SYMCLEANUP)GetProcAddress(dbghelp_dll, "SymCleanup");
637      SymGetTypeInfo_ =
638           (SYMGETTYPEINFO)GetProcAddress(dbghelp_dll, "SymGetTypeInfo");
639      SymGetLineFromAddr64_ =
640           (SYMGETLINEFROMADDR64)GetProcAddress(dbghelp_dll,
641                                              "SymGetLineFromAddr64");
642      SymEnumSymbols_ =
643           (SYMENUMSYMBOLS)GetProcAddress(dbghelp_dll, "SymEnumSymbols");
644      SymSetContext_ =
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");
653
654      if (! (MiniDumpWriteDump_ &&
655             SymInitialize_ && SymSetOptions_  && SymGetOptions_ &&
656             SymCleanup_    && SymGetTypeInfo_ && SymGetLineFromAddr64_ &&
657             SymEnumSymbols_ && SymSetContext_ && SymFromAddr_ &&
658             SymGetModuleBase64_ && StackWalk64_ &&
659             SymFunctionTableAccess64_))
660        goto cleanup;
661
662      /* initialize the symbol loading code */
663      opts = SymGetOptions_();
664
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
667         when needed. */
668      SymSetOptions_(opts | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS);
669
670      /* Initialize the debughlp DLL with the default path and automatic
671         module enumeration (and loading of symbol tables) for this process.
672       */
673      SymInitialize_(GetCurrentProcess(), NULL, TRUE);
674
675      return TRUE;
676    }
677
678cleanup:
679  if (dbghelp_dll)
680    FreeLibrary(dbghelp_dll);
681
682  return FALSE;
683}
684
685/* Cleanup the dbghelp.dll library */
686static void
687cleanup_debughlp()
688{
689  SymCleanup_(GetCurrentProcess());
690
691  FreeLibrary(dbghelp_dll);
692}
693
694/* Create a filename based on a prefix, the timestamp and an extension.
695   check if the filename was already taken, retry 3 times. */
696BOOL
697get_temp_filename(char *filename, const char *prefix, const char *ext)
698{
699  char temp_dir[MAX_PATH - 64];
700  int i;
701
702  if (! GetTempPath(MAX_PATH - 64, temp_dir))
703    return FALSE;
704
705  for (i = 0;i < 3;i++)
706    {
707      HANDLE file;
708      time_t now;
709      char time_str[64];
710
711      time(&now);
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);
714
715      file = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_NEW,
716                        FILE_ATTRIBUTE_NORMAL, NULL);
717      if (file != INVALID_HANDLE_VALUE)
718        {
719          CloseHandle(file);
720          return TRUE;
721        }
722    }
723
724   filename[0] = '\0';
725   return FALSE;
726}
727
728/* Unhandled exception callback set with SetUnhandledExceptionFilter() */
729LONG WINAPI
730svn__unhandled_exception_filter(PEXCEPTION_POINTERS ptrs)
731{
732  char dmp_filename[MAX_PATH];
733  char log_filename[MAX_PATH];
734  FILE *log_file;
735
736  /* Check if the crash handler was already loaded (crash while handling the
737     crash) */
738  if (dbghelp_dll != INVALID_HANDLE_VALUE)
739    return EXCEPTION_CONTINUE_SEARCH;
740
741  /* don't log anything if we're running inside a debugger ... */
742  if (is_debugger_present())
743    return EXCEPTION_CONTINUE_SEARCH;
744
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;
749
750  /* If we can't load a recent version of the dbghelp.dll, pass on this
751     exception */
752  if (!load_dbghelp_dll())
753    return EXCEPTION_CONTINUE_SEARCH;
754
755  /* open log file */
756  log_file = fopen(log_filename, "w+");
757
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,
762                     log_file);
763
764  /* write the stacktrace, if available */
765  fprintf(log_file, "\nStacktrace:\n");
766  write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, log_file);
767
768  /* write the minidump file and use the callback to write the list of modules
769     to the log file */
770  fprintf(log_file, "\n\nLoaded modules:\n");
771  write_minidump_file(dmp_filename, ptrs,
772                      write_module_info_callback, (void *)log_file);
773
774  fclose(log_file);
775
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"
780                  "%s\n%s\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",
786                  log_filename,
787                  dmp_filename,
788                  CRASHREPORT_EMAIL);
789
790  if (getenv("SVN_DBG_STACKTRACES_TO_STDERR") != NULL)
791    {
792      fprintf(stderr, "\nProcess info:\n");
793      write_process_info(ptrs ? ptrs->ExceptionRecord : NULL,
794                         ptrs ? ptrs->ContextRecord : NULL,
795                         stderr);
796      fprintf(stderr, "\nStacktrace:\n");
797      write_stacktrace(ptrs ? ptrs->ContextRecord : NULL, stderr);
798    }
799
800  fflush(stderr);
801  fflush(stdout);
802
803  cleanup_debughlp();
804
805  /* terminate the application */
806  return EXCEPTION_EXECUTE_HANDLER;
807}
808#endif /* SVN_USE_WIN32_CRASHHANDLER */
809#endif /* WIN32 */
810