Windows下的异常处理和堆栈跟踪(MinGW/gcc)

Exception handling and stacktrace under Windows (MinGW/gcc)

本文关键字:MinGW gcc 跟踪 堆栈 异常处理 Windows      更新时间:2023-10-16

我需要做一个Linux/GCC异常处理系统Windows/MinGW兼容。

注意:我需要从共享库中捕获和回溯异常。

这是我如何在Linux/GCC下实现它的…

头:

#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 zeron",
            stderr);
      break;
    case SIGILL:
      fputs("Caught SIGILL: illegal instructionn", stderr);
      break;
    case SIGINT:
      fputs("Caught SIGINT: interactive attention signal, probably a ctrl+cn",
            stderr);
      break;
    case SIGSEGV:
      fputs("Caught SIGSEGV: segfaultn", stderr);
      break;
    case SIGTERM:
    default:
      fputs("Caught SIGTERM: a termination request was sent to the programn",
            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);

以上所有内容在Linux下都可以正常工作。现在我想在我的库的Windows版本下有相同的行为…

下面是我捕获异常的方法:

#include <windows.h>
static LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS * ExceptionInfo)
{
  switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
  {
    case EXCEPTION_ACCESS_VIOLATION:
      fputs("Error: EXCEPTION_ACCESS_VIOLATIONn", stderr);
      break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
      fputs("Error: EXCEPTION_ARRAY_BOUNDS_EXCEEDEDn", stderr);
      break;
    case EXCEPTION_BREAKPOINT:
      fputs("Error: EXCEPTION_BREAKPOINTn", stderr);
      break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
      fputs("Error: EXCEPTION_DATATYPE_MISALIGNMENTn", stderr);
      break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
      fputs("Error: EXCEPTION_FLT_DENORMAL_OPERANDn", stderr);
      break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
      fputs("Error: EXCEPTION_FLT_DIVIDE_BY_ZEROn", stderr);
      break;
    case EXCEPTION_FLT_INEXACT_RESULT:
      fputs("Error: EXCEPTION_FLT_INEXACT_RESULTn", stderr);
      break;
    case EXCEPTION_FLT_INVALID_OPERATION:
      fputs("Error: EXCEPTION_FLT_INVALID_OPERATIONn", stderr);
      break;
    case EXCEPTION_FLT_OVERFLOW:
      fputs("Error: EXCEPTION_FLT_OVERFLOWn", stderr);
      break;
    case EXCEPTION_FLT_STACK_CHECK:
      fputs("Error: EXCEPTION_FLT_STACK_CHECKn", stderr);
      break;
    case EXCEPTION_FLT_UNDERFLOW:
      fputs("Error: EXCEPTION_FLT_UNDERFLOWn", stderr);
      break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
      fputs("Error: EXCEPTION_ILLEGAL_INSTRUCTIONn", stderr);
      break;
    case EXCEPTION_IN_PAGE_ERROR:
      fputs("Error: EXCEPTION_IN_PAGE_ERRORn", stderr);
      break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
      fputs("Error: EXCEPTION_INT_DIVIDE_BY_ZEROn", stderr);
      break;
    case EXCEPTION_INT_OVERFLOW:
      fputs("Error: EXCEPTION_INT_OVERFLOWn", stderr);
      break;
    case EXCEPTION_INVALID_DISPOSITION:
      fputs("Error: EXCEPTION_INVALID_DISPOSITIONn", stderr);
      break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
      fputs("Error: EXCEPTION_NONCONTINUABLE_EXCEPTIONn", stderr);
      break;
    case EXCEPTION_PRIV_INSTRUCTION:
      fputs("Error: EXCEPTION_PRIV_INSTRUCTIONn", stderr);
      break;
    case EXCEPTION_SINGLE_STEP:
      fputs("Error: EXCEPTION_SINGLE_STEPn", stderr);
      break;
    case EXCEPTION_STACK_OVERFLOW:
      fputs("Error: EXCEPTION_STACK_OVERFLOWn", stderr);
      break;
    default:
      fputs("Error: Unrecognized Exceptionn", stderr);
      break;
  }
  fflush(stderr);
  if (EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode)
  {
      // TODO : ...
      //windows_print_stacktrace(ExceptionInfo->ContextRecord);
  }
  return EXCEPTION_EXECUTE_HANDLER;
}

下面是打印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() );
}

其中addr2line为:

#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);
}

但是:

MinGW没有backtrace,也没有backtrace_symbols特征。上面需要知道global_program_name,我没有,因为管理异常的代码位于主程序加载的dll中。

So 问题:

是否有办法从dll动态地获得global_program_name ?如果没有,那么用MinGW打印堆栈跟踪的好方法是什么呢?

Nota Bene:在这一点上,另一个子问题在戏弄我。为了获得漂亮的堆栈跟踪,我需要启用-g编译器选项。使用它是否会影响性能(即使我将优化保持在最大-O3) ?还是只影响共享库的大小?

谢谢你的帮助。

Windows平台可以通过全局__argv变量获取程序名

#include <stdlib.h>
addr2line(__argv[0], addr);

虽然前面的答案有时可能有效,但在许多情况下它可能会失败。Argv[0]是一个命令行参数,可以在调用执行类型函数(包括windows变体)时传递。要可靠地获取可执行文件,请使用以下代码:

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