【问题标题】:C++ display stack trace on exceptionC++ 在异常时显示堆栈跟踪
【发布时间】:2010-10-16 01:22:55
【问题描述】:

如果抛出异常,我希望有一种方法可以向用户报告堆栈跟踪。做这个的最好方式是什么?是否需要大量的额外代码?

回答问题:

如果可能的话,我希望它是便携的。我希望弹出信息,以便用户可以复制堆栈跟踪并在出现错误时通过电子邮件发送给我。

【问题讨论】:

    标签: c++ exception exception-handling stack-trace


    【解决方案1】:

    Unix:backtrace

    Mac:backtrace

    Windows:CaptureBackTrace

    【讨论】:

      【解决方案2】:

      Andrew Grant's answer not 是否有助于获取 throwing 函数的堆栈跟踪,至少对于 GCC 没有帮助,因为 throw 语句可以不会自行保存当前的堆栈跟踪,此时 catch 处理程序将无法再访问堆栈跟踪。

      使用 GCC 解决这个问题的唯一方法是确保在 throw 指令处生成堆栈跟踪,并将其与异常对象一起保存。

      当然,此方法要求每个引发异常的代码都使用该特定的异常类。

      2017 年 7 月 11 日更新:对于一些有用的代码,请查看 cahit beyaz's answer,它指向 http://stacktrace.sourceforge.net - 我还没有使用它,但它看起来很有希望。

      【讨论】:

      • 不幸的是,链接已失效。你能提供一些其他的吗?
      • archive.org 也不知道。该死。好吧,过程应该很清楚:抛出一个自定义类的对象,该对象记录了抛出时的堆栈跟踪。
      • 在 StackTrace 的主页上,我看到了throw stack_runtime_error。我是否正确推断出此库仅适用于从该类派生的异常,而不适用于 std::exception 或来自第三方库的异常?
      • 遗憾的是,答案是“不,您无法从 C++ 异常中获取堆栈跟踪”,唯一的选择是抛出您自己的类,该类在构建时会生成堆栈跟踪。如果你一直在使用诸如 C++ std:: 库的任何部分之类的东西,那么你就不走运了。对不起,做你真糟糕。
      【解决方案3】:

      如果您使用的是 Boost 1.65 或更高版本,则可以使用boost::stacktrace

      #include <boost/stacktrace.hpp>
      
      // ... somewhere inside the bar(int) function that is called recursively:
      std::cout << boost::stacktrace::stacktrace();
      

      【讨论】:

      • boost docs 不仅解释了捕获堆栈跟踪,还解释了如何处理异常和断言。很棒的东西。
      • 这个 stacktrace() 是否打印入门指南中给出的源文件和行号?
      【解决方案4】:

      一个适用于 OSX 的工作示例(现已在 Catalina 10.15 上测试)。显然不能移植到 linux/windows。可能对某人有用。

      在“Mew-exception”字符串中,您可以使用 backtrace 和/或 backtrace_symbols 函数

      #include <stdexcept>
      #include <typeinfo>
      #include <dlfcn.h>
      
      extern "C" void __cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *));
      static void (*__cxa_throw_orig)(void *thrown_object, std::type_info *tinfo, void (*dest)(void *));
      extern "C" void luna_cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *))
      {
          printf("Mew-exception you can catch your backtrace here!");
          __cxa_throw_orig(thrown_object, tinfo, dest);
      }
      
      
      //__attribute__ ((used))
      //__attribute__ ((section ("__DATA,__interpose")))
      static struct replace_pair_t {
          void *replacement, *replacee;
      } replace_pair = { (void*)luna_cxa_throw, (void*)__cxa_throw };
      
      extern "C" const struct mach_header __dso_handle;
      extern "C" void dyld_dynamic_interpose(const struct mach_header*,
                                     const replace_pair_t replacements[],
                                     size_t count);
      
      int fn()
      {
          int a = 10; ++a;
          throw std::runtime_error("Mew!");
      }
      
      int main(int argc, const char * argv[]) {
          __cxa_throw_orig = (void (*)(void *thrown_object, std::type_info *tinfo, void (*dest)(void *)))dlsym(RTLD_DEFAULT, "__cxa_throw");
      
          dyld_dynamic_interpose(&__dso_handle, &replace_pair, 1);
      
          fn();
          return 0;
      }
      

      【讨论】:

        【解决方案5】:

        如果您使用的是 C++ 并且不想/不能使用 Boost,则可以使用以下代码 [link to the original site] 打印带有解构名称的回溯。

        请注意,此解决方案特定于 Linux。它使用 GNU 的 libc 函数 backtrace()/backtrace_symbols()(来自 execinfo.h)来获取回溯,然后使用 __cxa_demangle()(来自 cxxabi.h)对回溯符号名称进行分解。

        // stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
        // published under the WTFPL v2.0
        
        #ifndef _STACKTRACE_H_
        #define _STACKTRACE_H_
        
        #include <stdio.h>
        #include <stdlib.h>
        #include <execinfo.h>
        #include <cxxabi.h>
        
        /** Print a demangled stack backtrace of the caller function to FILE* out. */
        static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
        {
            fprintf(out, "stack trace:\n");
        
            // storage array for stack trace address data
            void* addrlist[max_frames+1];
        
            // retrieve current stack addresses
            int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
        
            if (addrlen == 0) {
            fprintf(out, "  <empty, possibly corrupt>\n");
            return;
            }
        
            // resolve addresses into strings containing "filename(function+address)",
            // this array must be free()-ed
            char** symbollist = backtrace_symbols(addrlist, addrlen);
        
            // allocate string which will be filled with the demangled function name
            size_t funcnamesize = 256;
            char* funcname = (char*)malloc(funcnamesize);
        
            // iterate over the returned symbol lines. skip the first, it is the
            // address of this function.
            for (int i = 1; i < addrlen; i++)
            {
            char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
        
            // find parentheses and +address offset surrounding the mangled name:
            // ./module(function+0x15c) [0x8048a6d]
            for (char *p = symbollist[i]; *p; ++p)
            {
                if (*p == '(')
                begin_name = p;
                else if (*p == '+')
                begin_offset = p;
                else if (*p == ')' && begin_offset) {
                end_offset = p;
                break;
                }
            }
        
            if (begin_name && begin_offset && end_offset
                && begin_name < begin_offset)
            {
                *begin_name++ = '\0';
                *begin_offset++ = '\0';
                *end_offset = '\0';
        
                // mangled name is now in [begin_name, begin_offset) and caller
                // offset in [begin_offset, end_offset). now apply
                // __cxa_demangle():
        
                int status;
                char* ret = abi::__cxa_demangle(begin_name,
                                funcname, &funcnamesize, &status);
                if (status == 0) {
                funcname = ret; // use possibly realloc()-ed string
                fprintf(out, "  %s : %s+%s\n",
                    symbollist[i], funcname, begin_offset);
                }
                else {
                // demangling failed. Output function name as a C function with
                // no arguments.
                fprintf(out, "  %s : %s()+%s\n",
                    symbollist[i], begin_name, begin_offset);
                }
            }
            else
            {
                // couldn't parse the line? print the whole line.
                fprintf(out, "  %s\n", symbollist[i]);
            }
            }
        
            free(funcname);
            free(symbollist);
        }
        
        #endif // _STACKTRACE_H_
        

        HTH!

        【讨论】:

          【解决方案6】:

          由于在进入 catch 块时堆栈已经展开,所以我的解决方案是不捕获某些异常,然后导致 SIGABRT。在 SIGABRT 的信号处理程序中,我然后 fork() 和 execl() gdb(在调试版本中)或 Google breakpads stackwalk(在发布版本中)。我也尝试只使用信号处理程序安全函数。

          GDB:

          static const char BACKTRACE_START[] = "<2>--- backtrace of entire stack ---\n";
          static const char BACKTRACE_STOP[] = "<2>--- backtrace finished ---\n";
          
          static char *ltrim(char *s)
          {
              while (' ' == *s) {
                  s++;
              }
              return s;
          }
          
          void Backtracer::print()
          {
              int child_pid = ::fork();
              if (child_pid == 0) {
                  // redirect stdout to stderr
                  ::dup2(2, 1);
          
                  // create buffer for parent pid (2+16+1 spaces to allow up to a 64 bit hex parent pid)
                  char pid_buf[32];
                  const char* stem = "                   ";
                  const char* s = stem;
                  char* d = &pid_buf[0];
                  while (static_cast<bool>(*s))
                  {
                      *d++ = *s++;
                  }
                  *d-- = '\0';
                  char* hexppid = d;
          
                  // write parent pid to buffer and prefix with 0x
                  int ppid = getppid();
                  while (ppid != 0) {
                      *hexppid = ((ppid & 0xF) + '0');
                      if(*hexppid > '9') {
                          *hexppid += 'a' - '0' - 10;
                      }
                      --hexppid;
                      ppid >>= 4;
                  }
                  *hexppid-- = 'x';
                  *hexppid = '0';
          
                  // invoke GDB
                  char name_buf[512];
                  name_buf[::readlink("/proc/self/exe", &name_buf[0], 511)] = 0;
                  ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_START[0], sizeof(BACKTRACE_START));
                  (void)r;
                  ::execl("/usr/bin/gdb",
                          "/usr/bin/gdb", "--batch", "-n", "-ex", "thread apply all bt full", "-ex", "quit",
                          &name_buf[0], ltrim(&pid_buf[0]), nullptr);
                  ::exit(1); // if GDB failed to start
              } else if (child_pid == -1) {
                  ::exit(1); // if forking failed
              } else {
                  // make it work for non root users
                  if (0 != getuid()) {
                      ::prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
                  }
                  ::waitpid(child_pid, nullptr, 0);
                  ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_STOP[0], sizeof(BACKTRACE_STOP));
                  (void)r;
              }
          }
          

          minidump_stackwalk:

          static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
          {
              int child_pid = ::fork();
              if (child_pid == 0) {
                  ::dup2(open("/dev/null", O_WRONLY), 2); // ignore verbose output on stderr
                  ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_START[0], sizeof(MINIDUMP_STACKWALK_START));
                  (void)r;
                  ::execl("/usr/bin/minidump_stackwalk", "/usr/bin/minidump_stackwalk", descriptor.path(), "/usr/share/breakpad-syms", nullptr);
                  ::exit(1); // if minidump_stackwalk failed to start
              } else if (child_pid == -1) {
                  ::exit(1); // if forking failed
              } else {
                  ::waitpid(child_pid, nullptr, 0);
                  ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_STOP[0], sizeof(MINIDUMP_STACKWALK_STOP));
                  (void)r;
              }
              ::remove(descriptor.path()); // this is not signal safe anymore but should still work
              return succeeded;
          }
          

          编辑:为了使其适用于breakpad,我还必须添加以下内容:

          std::set_terminate([]()
          {
              ssize_t r = ::write(STDERR_FILENO, EXCEPTION, sizeof(EXCEPTION));
              (void)r;
              google_breakpad::ExceptionHandler::WriteMinidump(std::string("/tmp"), dumpCallback, NULL);
              exit(1); // avoid creating a second dump by not calling std::abort
          });
          

          来源:How to get a stack trace for C++ using gcc with line number information?Is it possible to attach gdb to a crashed process (a.k.a "just-in-time" debugging)

          【讨论】:

            【解决方案7】:

            我想添加一个标准库选项(即跨平台)如何生成异常回溯,C++11已经提供: p>

            使用std::nested_exceptionstd::throw_with_nested

            这不会让您放松堆栈,但在我看来,这是下一个最好的事情。 它在 StackOverflow herehere 上进行了描述,您可以在代码中获取异常的回溯,而无需调试器或繁琐的日志记录,只需编写一个适当的异常处理程序即可重新抛出嵌套异常。

            由于您可以对任何派生的异常类执行此操作,因此您可以向此类回溯添加大量信息! 你也可以看看我的MWE on GitHub,回溯看起来像这样:

            Library API: Exception caught in function 'api_function'
            Backtrace:
            ~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
            ~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
            

            【讨论】:

            • 如果您愿意做额外的工作,这可能比通常的愚蠢堆栈跟踪要好得多。
            • 我已经有一段时间没有对 C++ 为何会扼杀如此简单的概念感到恐惧了。谢谢提醒。
            【解决方案8】:

            我也有类似的问题,虽然我喜欢可移植性,但我只需要 gcc 支持。在 gcc 中,可以使用 execinfo.h 和 backtrace 调用。为了解开函数名称,Bingmann 先生有a nice piece of code. 为了在异常上转储回溯,我创建了一个异常,在构造函数中打印回溯。如果我希望这适用于库中抛出的异常,则可能需要重建/链接以便使用回溯异常。

            /******************************************
            #Makefile with flags for printing backtrace with function names
            # compile with symbols for backtrace
            CXXFLAGS=-g
            # add symbols to dynamic symbol table for backtrace
            LDFLAGS=-rdynamic
            turducken: turducken.cc
            ******************************************/
            
            #include <cstdio>
            #include <stdexcept>
            #include <execinfo.h>
            #include "stacktrace.h" /* https://panthema.net/2008/0901-stacktrace-demangled/ */
            
            // simple exception that prints backtrace when constructed
            class btoverflow_error: public std::overflow_error
            {
                public:
                btoverflow_error( const std::string& arg ) :
                    std::overflow_error( arg )
                {
                    print_stacktrace();
                };
            };
            
            
            void chicken(void)
            {
                throw btoverflow_error( "too big" );
            }
            
            void duck(void)
            {
                chicken();
            }
            
            void turkey(void)
            {
                duck();
            }
            
            int main( int argc, char *argv[])
            {
                try
                {
                    turkey();
                }
                catch( btoverflow_error e)
                {
                    printf( "caught exception: %s\n", e.what() );
                }
            }
            

            使用 gcc 4.8.4 编译和运行它会产生一个带有很好地未损坏的 C++ 函数名称的回溯:

            stack trace:
             ./turducken : btoverflow_error::btoverflow_error(std::string const&)+0x43
             ./turducken : chicken()+0x48
             ./turducken : duck()+0x9
             ./turducken : turkey()+0x9
             ./turducken : main()+0x15
             /lib/x86_64-linux-gnu/libc.so.6 : __libc_start_main()+0xf5
             ./turducken() [0x401629]
            

            【讨论】:

              【解决方案9】:

              我推荐http://stacktrace.sourceforge.net/ 项目。它支持 Windows、Mac OS 和 Linux

              【讨论】:

              • 在它的主页上,我看到了throw stack_runtime_error。我是否正确推断出此库仅适用于从该类派生的异常,而不适用于 std::exception 或来自第三方库的异常?
              【解决方案10】:

              以下代码在引发异常后立即停止执行。您需要设置 windows_exception_handler 和终止处理程序。我在 MinGW 32bits 中对此进行了测试。

              void beforeCrash(void);
              
              static const bool SET_TERMINATE = std::set_terminate(beforeCrash);
              
              void beforeCrash() {
                  __asm("int3");
              }
              
              int main(int argc, char *argv[])
              {
              SetUnhandledExceptionFilter(windows_exception_handler);
              ...
              }
              

              检查 windows_exception_handler 函数的以下代码: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html

              【讨论】:

                【解决方案11】:

                Poppy 不仅可以收集堆栈跟踪,还可以收集参数值、局部变量等 - 导致崩溃的所有内容。

                【讨论】:

                • "然后将 STACK~ 宏撒在您的代码上...只需将它放在您想要堆栈跟踪的每个函数的开头即可。"嗯,没有。
                【解决方案12】:

                在 Windows 上,查看BugTrap。它不再位于原始链接中,但仍可在 CodeProject 上找到。

                【讨论】:

                  【解决方案13】:

                  在带有 g++ 的 linux 上查看这个库

                  https://sourceforge.net/projects/libcsdbg

                  它为您完成所有工作

                  【讨论】:

                    【解决方案14】:

                    Cpp-tool ex_diag - 轻量级、多平台、最少的资源使用、简单而灵活的跟踪。

                    【讨论】:

                    【解决方案15】:

                    AFAIK libunwind 非常便携,到目前为止我还没有发现更容易使用的东西。

                    【讨论】:

                    • libunwind 1.1 不是在 os x 上构建的。
                    【解决方案16】:

                    这取决于哪个平台。

                    在 GCC 上这很简单,请参阅 this post 了解更多详细信息。

                    在 MSVC 上,您可以使用 StackWalker 库来处理 Windows 所需的所有底层 API 调用。

                    您必须找出将此功能集成到您的应用中的最佳方式,但您需要编写的代码量应该是最少的。

                    【讨论】:

                    • 您链接到的帖子主要指向从段错误生成跟踪,但提问者特别提到了异常,这是完全不同的野兽。
                    • 我同意@Shep - 这个答案并不能真正帮助获取 GCC 上的抛出代码的堆栈跟踪。请参阅我的答案以获得可能的解决方案。
                    • 这个答案具有误导性。该链接指向特定于 Linux 而不是 gcc 的答案。
                    • 您可以按照this answer 中的说明覆盖libstdc++ 的抛出机制(由GCC 和可能的Clang 使用)。
                    猜你喜欢
                    • 2017-08-06
                    • 2014-01-24
                    • 2011-01-05
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-08-09
                    相关资源
                    最近更新 更多