Linux+Windows: 程序崩溃时,在 C++ 代码中,如何获取函数调用栈信息

开发 后端
这篇文章主要讲述在 Linux 和 Windows 这 2 个平台上,如何用C++ 来捕获函数调用栈里的信息。

[[390869]]

一、前言

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

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

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

二、Linux 平台

1. 注册异常信号的处理函数

需要处理哪些异常信号

  1. #include <execinfo.h> 
  2. #include <cxxabi.h> 
  3. #include <signal.h> 
  4.  
  5. const std::map<int, std::string> Signals = { 
  6.     {SIGINT, "SIGINT"},     
  7.     {SIGABRT, "SIGABRT"},  
  8.     {SIGFPE, "SIGFPE"},    
  9.     {SIGILL, "SIGILL"},   
  10.     {SIGSEGV, "SIGSEGV"
  11.     // 可以添加其他信号 
  12. }; 

注册信号处理函数

  1. struct sigaction action
  2. sigemptyset(&action.sa_mask); 
  3. action.sa_sigaction = &sigHandler; 
  4. action.sa_flags = SA_SIGINFO;  
  5.  
  6.  for (const auto &sigPair : Signals) 
  7.  { 
  8.     if (sigaction(sigPair.first, &actionNULL) < 0) 
  9.         fprintf(stderr, "Error: sigaction failed! \n"); 
  10.  } 

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

  1. void sigHandler(int signum, siginfo_t *info, void *ctx) 
  2.     const size_t dump_size = 50; 
  3.     void *array[dump_size]; 
  4.     int size = backtrace(array, dump_size); 
  5.     char **symbols = backtrace_symbols(array, size); 
  6.     std::ostringstream oss; 
  7.  
  8.     for (int i = 0; i < size; ++i) 
  9.     { 
  10.         char *mangleName = 0; 
  11.         char *offsetBegin = 0; 
  12.         char *offsetEnd = 0; 
  13.  
  14.         for (char *p = symbols[i]; *p; ++p) 
  15.         { 
  16.             if ('(' == *p) 
  17.             {    
  18.                  mangleName = p; 
  19.             }    
  20.             else if ('+' == *p) 
  21.             { 
  22.                 offsetBegin = p; 
  23.             } 
  24.             else if (')' == *p) 
  25.             { 
  26.                 offsetEnd = p; 
  27.                 break; 
  28.             } 
  29.         } 
  30.  
  31.         if (mangleName && offsetBegin && offsetEnd && mangleName < offsetBegin) 
  32.         { 
  33.             *mangleName++ = '\0'
  34.             *offsetBegin++ = '\0'
  35.             *offsetEnd++ = '\0'
  36.              
  37.             int status; 
  38.             char *realName = abi::__cxa_demangle(mangleName, 0, 0, &status); 
  39.             if (0 == status) 
  40.                 oss << "\tstack dump [" << i << "]  " << symbols[i] << " : " << realName << "+"
  41.             else 
  42.                 oss << "\tstack dump [" << i << "]  " << symbols[i] << mangleName << "+"
  43.             oss << offsetBegin << offsetEnd << std::endl; 
  44.             free(realName); 
  45.         } 
  46.         else 
  47.         { 
  48.             oss << "\tstack dump [" << i << "]  " << symbols[i] << std::endl; 
  49.         } 
  50.     } 
  51.     free(symbols); 
  52.     oss << std::endl; 
  53.     std::cout << oss.str(); // 打印函数调用栈信息 

三、Windwos 平台

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

1. 设置异常处理函数

  1. #include <windows.h> 
  2. #include <dbghelp.h> 
  3.  
  4. SetUnhandledExceptionFilter(exceptionHandler); 

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

  1. void exceptionHandler(LPEXCEPTION_POINTERS info) 
  2.     CONTEXT *context = info->ContextRecord; 
  3.     std::shared_ptr<void> RaiiSysCleaner(nullptr, [&](void *) { 
  4.       SymCleanup(GetCurrentProcess()); 
  5.     }); 
  6.  
  7.   const size_t dumpSize = 64; 
  8.   std::vector<uint64_t> frameVector(dumpSize); 
  9.  
  10.   DWORD machine_type = 0; 
  11.   STACKFRAME64 frame = {}; 
  12.   frame.AddrPC.Mode = AddrModeFlat; 
  13.   frame.AddrFrame.Mode = AddrModeFlat; 
  14.   frame.AddrStack.Mode = AddrModeFlat; 
  15.  
  16. #ifdef _M_IX86 
  17.   frame.AddrPC.Offset = context->Eip; 
  18.   frame.AddrFrame.Offset = context->Ebp; 
  19.   frame.AddrStack.Offset = context->Esp; 
  20.   machine_type = IMAGE_FILE_MACHINE_I386; 
  21. #elif _M_X64 
  22.   frame.AddrPC.Offset = context->Rip; 
  23.   frame.AddrFrame.Offset = context->Rbp; 
  24.   frame.AddrStack.Offset = context->Rsp; 
  25.   machine_type = IMAGE_FILE_MACHINE_AMD64; 
  26. #elif _M_IA64 
  27.   frame.AddrPC.Offset = context->StIIP; 
  28.   frame.AddrFrame.Offset = context->IntSp; 
  29.   frame.AddrStack.Offset = context->IntSp; 
  30.   machine_type = IMAGE_FILE_MACHINE_IA64; 
  31.   frame.AddrBStore.Offset = context.RsBSP; 
  32.   frame.AddrBStore.Mode = AddrModeFlat; 
  33. #else 
  34.   frame.AddrPC.Offset = context->Eip; 
  35.   frame.AddrFrame.Offset = context->Ebp; 
  36.   frame.AddrStack.Offset = context->Esp; 
  37.   machine_type = IMAGE_FILE_MACHINE_I386; 
  38. #endif 
  39.  
  40.   for (size_t index = 0; index < frameVector.size(); ++index
  41.   { 
  42.     if (StackWalk64(machine_type, 
  43.            GetCurrentProcess(), 
  44.            GetCurrentThread(), 
  45.            &frame, 
  46.            context, 
  47.            NULL
  48.            SymFunctionTableAccess64, 
  49.            SymGetModuleBase64, 
  50.            NULL)) { 
  51.       frameVector[index] = frame.AddrPC.Offset; 
  52.     } else { 
  53.       break; 
  54.     } 
  55.   } 
  56.  
  57.   std::string dump; 
  58.   const size_t kSize = frameVector.size(); 
  59.   for (size_t index = 0; index < kSize && frameVector[index]; ++index) { 
  60.     dump += getSymbolInfo(index, frameVector); 
  61.     dump += "\n"
  62.   } 
  63.  
  64. std::cout << dump;  

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

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

 

责任编辑:姜华 来源: IOT物联网小镇
相关推荐

2010-01-28 13:35:41

调用C++函数

2011-08-22 17:13:00

LuaC++函数

2023-10-30 11:45:44

FridaC++函数

2014-09-25 11:08:17

ECLLispC语言

2010-02-02 13:15:00

C++ lambda函

2023-11-09 23:31:02

C++函数调用

2012-04-16 13:47:37

JavaMatlab

2012-04-28 15:28:21

JNI混合编程Java

2020-07-31 18:33:56

C++编程语言

2019-08-28 14:21:39

C++C接口代码

2017-02-06 18:42:37

Linuxgdb程序

2010-01-21 11:23:58

C++函数调用

2009-04-14 14:53:06

C++Lambda函数多线程

2010-02-05 17:58:32

C++链栈模板

2011-08-22 17:25:31

LuaC++函数

2010-01-14 10:42:08

C++源代码

2023-09-27 23:24:50

C++链表

2010-01-20 14:25:56

函数调用

2010-02-05 10:23:09

C++基本函数

2010-02-02 15:59:32

C++赋值函数
点赞
收藏

51CTO技术栈公众号