用户登录
用户注册

分享至

如何在系统崩溃时从C++中获取函数调用栈信息?

  • 作者: 姐就是姐_别侮辱姐
  • 来源: 51数据库
  • 2022-09-20
导读 这篇文章主要讲述在 Linux 和 Windows 这 2 个平台上,如何用C++ 来捕获函数调用栈里的信息。

一、前言

程序在执行过程中 crash 是非常严重的问题,一般都应该在测试阶段排除掉这些问题,但是总会有漏网之鱼被带到 release 阶段。

因此,程序的日志系统需要侦测这种情况,在代码崩溃的时候获取函数调用栈信息,为 debug 提供有效的信息。

这篇文章的理论知识很少,直接分享 2 段代码:在 Linux 和 Windows 这 2 个平台上,如何用C++ 来捕获函数调用栈里的信息。

二、Linux 平台

1. 注册异常信号的处理函数
需要处理哪些异常信号

#include  
#include  
#include  
 
const std::map Signals = { 
    {SIGINT, "SIGINT"},     
    {SIGABRT, "SIGABRT"},  
    {SIGFPE, "SIGFPE"},    
    {SIGILL, "SIGILL"},   
    {SIGSEGV, "SIGSEGV"} 
    // 可以添加其他信号 
}; 

注册信号处理函数

struct sigaction action; 
sigemptyset(&action.sa_mask); 
action.sa_sigaction = &sigHandler; 
action.sa_flags = SA_SIGINFO;  
 
 for (const auto &sigPair : Signals) 
 { 
    if (sigaction(sigPair.first, &action, NULL) < 0)="" fprintf(stderr,="" "error:="" sigaction="" failed!="" \n");="" }="">

2. 捕获异常,获取函数调用栈信息

void sigHandler(int signum, siginfo_t *info, void *ctx) 
{ 
    const size_t dump_size = 50; 
    void *array[dump_size]; 
    int size = backtrace(array, dump_size); 
    char **symbols = backtrace_symbols(array, size); 
    std::ostringstream oss; 
 
    for (int i = 0; i < size;="" ++i)="" {="" char="" *manglename="0;" char="" *offsetbegin="0;" char="" *offsetend="0;" for="" (char="" *p="symbols[i];" *p;="" ++p)="" {="" if="" ('('="=" *p)="" {="" manglename="p;" }="" else="" if="" ('+'="=" *p)="" {="" offsetbegin="p;" }="" else="" if="" (')'="=" *p)="" {="" offsetend="p;" break;="" }="" }="" if="" (manglename="" &&="" offsetbegin="" &&="" offsetend="" &&="" manglename="">< offsetbegin)="" {="" *manglename++='\0' ;="" *offsetbegin++='\0' ;="" *offsetend++='\0' ;="" int="" status;="" char="" *realname="abi::__cxa_demangle(mangleName," 0,="" 0,="" &status);="" if="" (0="=" status)="" oss="">< "\tstack="" dump="" ["="">< i="">< "]="" "="">< symbols[i]="">< "="" :="" "="">< realname="">< "+";="" else="" oss="">< "\tstack="" dump="" ["="">< i="">< "]="" "="">< symbols[i]="">< manglename="">< "+";="" oss="">< offsetbegin="">< offsetend="">< std::endl;="" free(realname);="" }="" else="" {="" oss="">< "\tstack="" dump="" ["="">< i="">< "]="" "="">< symbols[i]="">< std::endl;="" }="" }="" free(symbols);="" oss="">< std::endl;="" std::cout="">< oss.str();="" 打印函数调用栈信息="" }="">
三、Windwos 平台

在 Windows 平台下的代码实现,参考了国外某个老兄的代码,如下:

1. 设置异常处理函数
#include  
#include  
 
SetUnhandledExceptionFilter(exceptionHandler); 
2. 捕获异常,获取函数调用栈信息
void exceptionHandler(LPEXCEPTION_POINTERS info) 
{ 
    CONTEXT *context = info->ContextRecord; 
    std::shared_ptr RaiiSysCleaner(nullptr, [&](void *) { 
      SymCleanup(GetCurrentProcess()); 
    }); 
 
  const size_t dumpSize = 64; 
  std::vector frameVector(dumpSize); 
 
  DWORD machine_type = 0; 
  STACKFRAME64 frame = {}; 
  frame.AddrPC.Mode = AddrModeFlat; 
  frame.AddrFrame.Mode = AddrModeFlat; 
  frame.AddrStack.Mode = AddrModeFlat; 
 
#ifdef _M_IX86 
  frame.AddrPC.Offset = context->Eip; 
  frame.AddrFrame.Offset = context->Ebp; 
  frame.AddrStack.Offset = context->Esp; 
  machine_type = IMAGE_FILE_MACHINE_I386; 
#elif _M_X64 
  frame.AddrPC.Offset = context->Rip; 
  frame.AddrFrame.Offset = context->Rbp; 
  frame.AddrStack.Offset = context->Rsp; 
  machine_type = IMAGE_FILE_MACHINE_AMD64; 
#elif _M_IA64 
  frame.AddrPC.Offset = context->StIIP; 
  frame.AddrFrame.Offset = context->IntSp; 
  frame.AddrStack.Offset = context->IntSp; 
  machine_type = IMAGE_FILE_MACHINE_IA64; 
  frame.AddrBStore.Offset = context.RsBSP; 
  frame.AddrBStore.Mode = AddrModeFlat; 
#else 
  frame.AddrPC.Offset = context->Eip; 
  frame.AddrFrame.Offset = context->Ebp; 
  frame.AddrStack.Offset = context->Esp; 
  machine_type = IMAGE_FILE_MACHINE_I386; 
#endif 
 
  for (size_t index = 0; index < framevector.size();="" ++index)="" {="" if="" (stackwalk64(machine_type,="" getcurrentprocess(),="" getcurrentthread(),="" &frame,="" context,="" null,="" symfunctiontableaccess64,="" symgetmodulebase64,="" null))="" {="" framevector[index]="frame.AddrPC.Offset;" }="" else="" {="" break;="" }="" }="" std::string="" dump;="" const="" size_t="" ksize="frameVector.size();" for="" (size_t="" index="0;" index="">< ksize="" &&="" framevector[index];="" ++index)="" {="" dump="" +="getSymbolInfo(index," framevector);="" dump="" +="\n" ;="" }="" std::cout="">< dump;="" }="">

主要是利用了 StackWalk64 这个函数,从地址转换为函数名称。

利用以上几个神器,基本上可以获取到程序崩溃时的函数调用栈信息,定位问题,有如神助!

软件
前端设计
程序设计
Java相关