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

Posted

技术标签:

【中文标题】Windows下的异常处理和堆栈跟踪(MinGW/gcc)【英文标题】:Exception handling and stacktrace under Windows (MinGW/gcc) 【发布时间】:2013-05-27 07:31:32 【问题描述】:

我需要做一个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 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);

以上所有在 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_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;

这是打印堆栈跟踪的方法:

#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 没有backtracebacktrace_symbols 功能。以上需要知道global_program_name,因为管理异常的代码位于主程序加载的dll中,所以我没有。

所以问题:

有没有办法从 dll 中动态获取 global_program_name ?如果不是,那将是使用 MinGW 打印堆栈跟踪的好方法?

Nota Bene :此时另一个子问题是在取笑我。要获得好的堆栈跟踪,我需要启用 -g 编译器选项。使用它是否会影响性能(即使我将优化保持在其最大值-O3)?还是我只是影响共享库的大小?

感谢您对此的任何帮助。

【问题讨论】:

您可以包含所有 code for the stack walk,而不是为其中的重要部分调用外部进程。 嗯,我的目标是让堆栈跟踪代码远离最终用户。用户链接 dll 并使用内置功能制作自己的程序。在这种情况下,堆栈跟踪主要用于捕获(由我)从 dll 抛出的异常。我应该在我的问题中首先说这一点。或者也许我没有很好地理解你指向的代码? 换句话说,dll的用户不应该在其主程序中处理任何额外的代码...... 抱歉,我有点累了 :),您只是想通过系统调用 addr2line 并使用您的示例代码来显示堆栈。我试一试。非常感谢。 【参考方案1】:

在 Windows 平台上您可以通过全局变量__argv 获取程序的名称。

#include <stdlib.h>

addr2line(__argv[0], addr);

【讨论】:

好的,谢谢。但是你为什么要删除 On Windows platforms 呢?在 Linux (gcc (Debian 4.7.2-4) 4.7.2) 下我无法识别它。 AFAIK 包含在 MinGW 中的 stdlib.h 也可以在其他一些平台上使用,但不是专门的 Linux。我正在验证,但忘记添加其他平台注释;) 根据我的配置,对于 Linux 部分,这更像是 ***.com/a/9098478/1715716 。感谢 MinGW 解决方案 ;-)【参考方案2】:

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

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

【讨论】:

以上是关于Windows下的异常处理和堆栈跟踪(MinGW/gcc)的主要内容,如果未能解决你的问题,请参考以下文章

Xcode 4.2/iOS 5 下控制台中没有异常堆栈跟踪?

python中的堆栈跟踪和异常处理

asp.NET 的update错误,请高人进来看下

Ruby 异常处理:反向堆栈跟踪顺序

JVM中常用堆栈跟踪内建指令

如何隐藏 Kafka connect api 未处理异常的堆栈跟踪