简体   繁体   中英

Exception handling and stacktrace under Windows (MinGW/gcc)

I need to make a Linux/GCC exception handling system Windows / MinGW compatible.

Note : I need to catch and backtrace exceptions from inside a shared library.

Here is how I implemented it under Linux / GCC...

Header :

#include <execinfo.h>
#include <signal.h>

static void handler(int sig)
{
  // Catch exceptions
  switch(sig)
  {
    case SIGABRT:
      fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr);
      break;
    case SIGFPE:
      fputs("Caught SIGFPE: arithmetic exception, such as divide by zero\n",
            stderr);
      break;
    case SIGILL:
      fputs("Caught SIGILL: illegal instruction\n", stderr);
      break;
    case SIGINT:
      fputs("Caught SIGINT: interactive attention signal, probably a ctrl+c\n",
            stderr);
      break;
    case SIGSEGV:
      fputs("Caught SIGSEGV: segfault\n", stderr);
      break;
    case SIGTERM:
    default:
      fputs("Caught SIGTERM: a termination request was sent to the program\n",
            stderr);
      break;
  }

    // Print stacktrace
    void *array[10];
    size_t size;

    // get void*'s for all entries on the stack
    size = backtrace(array, 10);

    // Ctrl+C interrupt => No backtrace
    if (sig != (int)SIGINT) {
        // print out all the frames to stderr
        fprintf(stderr, "Error: signal %d:\n", sig);
        backtrace_symbols_fd(array, size, 2);
    }
    exit(sig);

}

Cpp :

signal(SIGABRT, handler);
signal(SIGFPE,  handler);
signal(SIGILL,  handler);
signal(SIGINT,  handler);
signal(SIGSEGV, handler);
signal(SIGTERM, handler);

All of the above works fine under Linux. Now I would like to have the same behavior under the Windows version of my library...

Here is how I catch the exceptions :

#include <windows.h>

static LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS * ExceptionInfo)
{
  switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
  {
    case EXCEPTION_ACCESS_VIOLATION:
      fputs("Error: EXCEPTION_ACCESS_VIOLATION\n", stderr);
      break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
      fputs("Error: EXCEPTION_ARRAY_BOUNDS_EXCEEDED\n", stderr);
      break;
    case EXCEPTION_BREAKPOINT:
      fputs("Error: EXCEPTION_BREAKPOINT\n", stderr);
      break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
      fputs("Error: EXCEPTION_DATATYPE_MISALIGNMENT\n", stderr);
      break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
      fputs("Error: EXCEPTION_FLT_DENORMAL_OPERAND\n", stderr);
      break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
      fputs("Error: EXCEPTION_FLT_DIVIDE_BY_ZERO\n", stderr);
      break;
    case EXCEPTION_FLT_INEXACT_RESULT:
      fputs("Error: EXCEPTION_FLT_INEXACT_RESULT\n", stderr);
      break;
    case EXCEPTION_FLT_INVALID_OPERATION:
      fputs("Error: EXCEPTION_FLT_INVALID_OPERATION\n", stderr);
      break;
    case EXCEPTION_FLT_OVERFLOW:
      fputs("Error: EXCEPTION_FLT_OVERFLOW\n", stderr);
      break;
    case EXCEPTION_FLT_STACK_CHECK:
      fputs("Error: EXCEPTION_FLT_STACK_CHECK\n", stderr);
      break;
    case EXCEPTION_FLT_UNDERFLOW:
      fputs("Error: EXCEPTION_FLT_UNDERFLOW\n", stderr);
      break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
      fputs("Error: EXCEPTION_ILLEGAL_INSTRUCTION\n", stderr);
      break;
    case EXCEPTION_IN_PAGE_ERROR:
      fputs("Error: EXCEPTION_IN_PAGE_ERROR\n", stderr);
      break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
      fputs("Error: EXCEPTION_INT_DIVIDE_BY_ZERO\n", stderr);
      break;
    case EXCEPTION_INT_OVERFLOW:
      fputs("Error: EXCEPTION_INT_OVERFLOW\n", stderr);
      break;
    case EXCEPTION_INVALID_DISPOSITION:
      fputs("Error: EXCEPTION_INVALID_DISPOSITION\n", stderr);
      break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
      fputs("Error: EXCEPTION_NONCONTINUABLE_EXCEPTION\n", stderr);
      break;
    case EXCEPTION_PRIV_INSTRUCTION:
      fputs("Error: EXCEPTION_PRIV_INSTRUCTION\n", stderr);
      break;
    case EXCEPTION_SINGLE_STEP:
      fputs("Error: EXCEPTION_SINGLE_STEP\n", stderr);
      break;
    case EXCEPTION_STACK_OVERFLOW:
      fputs("Error: EXCEPTION_STACK_OVERFLOW\n", stderr);
      break;
    default:
      fputs("Error: Unrecognized Exception\n", stderr);
      break;
  }
  fflush(stderr);

  if (EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode)
  {
      // TODO : ...
      //windows_print_stacktrace(ExceptionInfo->ContextRecord);
  }

  return EXCEPTION_EXECUTE_HANDLER;
}

Here is how one could print the stacktrace :

#include <windows.h>
#include <imagehlp.h>
void windows_print_stacktrace(CONTEXT* context)
{
  SymInitialize(GetCurrentProcess(), 0, true);

  STACKFRAME frame = { 0 };

  /* setup initial stack frame */
  frame.AddrPC.Offset         = context->Eip;
  frame.AddrPC.Mode           = AddrModeFlat;
  frame.AddrStack.Offset      = context->Esp;
  frame.AddrStack.Mode        = AddrModeFlat;
  frame.AddrFrame.Offset      = context->Ebp;
  frame.AddrFrame.Mode        = AddrModeFlat;

  while (StackWalk(IMAGE_FILE_MACHINE_I386 ,
                   GetCurrentProcess(),
                   GetCurrentThread(),
                   &frame,
                   context,
                   0,
                   SymFunctionTableAccess,
                   SymGetModuleBase,
                   0 ) )
  {
    addr2line(global_program_name, (void*)frame.AddrPC.Offset);
  }

  SymCleanup( GetCurrentProcess() );
}

Where addr2line would be :

#include <stdlib.h>
#include <stdio.h>

/* Resolve symbol name and source location given the path to the executable
   and an address */
int addr2line(char const * const program_name, void const * const addr)
{
  char addr2line_cmd[512] = {0};

  /* have addr2line map the address to the relent line in the code */
   sprintf(addr2line_cmd,"addr2line -f -p -e %.256s %p", program_name, addr);

  /* This will print a nicely formatted string specifying the
     function and source line of the address */
  return system(addr2line_cmd);
}

But :

MinGW doesn't have backtrace , nether backtrace_symbols features. And the above requires to know the global_program_name , which I don't have since the code for managing exceptions is located in a dll the main program loads.

So Question :

Is there a way to obtain the global_program_name dynamically from the dll ? And if not, which would be a good approach to get the stacktrace printed with MinGW ?

Nota Bene : Another sub-question is teasing me at this point. To get nice stacktraces I need to enable the -g compiler option. Do I get a performance hit using it (even if I keep the optimization at its maximum -O3 ) ? Or do I just affect the size of my shared library ?

Thanks for any help on this.

On Windows platforms You can obtain the name of the program with the global __argv variable.

#include <stdlib.h>

addr2line(__argv[0], addr);

While the previous answer may work sometimes, it can fail in many situations. Argv[0] is a command line parameter that can be passed when calling execve type functions (including the windows variants). To reliably get the executable use the following code:

TCHAR szExeFileName[MAX_PATH]; 
GetModuleFileName(NULL, szExeFileName, MAX_PATH);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM