2

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.

Gauthier Boaglio
  • 10,054
  • 5
  • 48
  • 85
  • 1
    You could include all the [code for the stack walk](http://stackoverflow.com/a/6207030/179910) instead of invoking an external process for a significant part of it. – Jerry Coffin May 27 '13 at 07:43
  • Well, my goal here, was to keep the stack tracing code out of reach of the final user. The user links the dll and makes his own program using the builtin features. The stacktrace, in this context, is mainly used to catch exceptions thrown (by me) from the dll. I should have said that first in my question. Or maybe I didn't get well the code you pointed to ? – Gauthier Boaglio May 27 '13 at 08:01
  • In other words, the user of the dll should not have to deal with any additional code in its main program... – Gauthier Boaglio May 27 '13 at 08:06
  • Sorry, I am a little bit tired :), you simply meant to get ride of the system call to `addr2line` and use your sample code to display the stack. I give it a try. Thanks a lot. – Gauthier Boaglio May 27 '13 at 08:27

2 Answers2

3

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

#include <stdlib.h>

addr2line(__argv[0], addr);
Captain Obvlious
  • 19,754
  • 5
  • 44
  • 74
  • Ok thanks. But why did you remove the `On Windows platforms` ? I can't get it recognized under Linux (gcc (Debian 4.7.2-4) 4.7.2). – Gauthier Boaglio May 27 '13 at 09:49
  • 1
    AFAIK the `stdlib.h` included with MinGW is also available on some other platform but not specifically Linux. I was verifying that but forgot to add the other platforms note ;) – Captain Obvlious May 27 '13 at 10:03
  • With my configuration, for the Linux part, this would have been, more like http://stackoverflow.com/a/9098478/1715716 . Thanks for the MinGW solution ;-) – Gauthier Boaglio May 27 '13 at 10:43
2

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);
chacham15
  • 13,719
  • 26
  • 104
  • 207