代码之家  ›  专栏  ›  技术社区  ›  Nathan Fellman

用C或C++打印调用堆栈

  •  91
  • Nathan Fellman  · 技术社区  · 15 年前

    在调用某个函数时,是否有任何方法在调用C或C++的进程中转储调用堆栈?我的想法是这样的:

    void foo()
    {
       print_stack_trace();
    
       // foo's body
    
       return
    }
    

    在哪里? print_stack_trace caller 在Perl中。

    int main (void)
    {
        // will print out debug info every time foo() is called
        register_stack_trace_function(foo); 
    
        // etc...
    }
    

    哪里 register_stack_trace_function 放置某种内部断点,该断点将导致在任何时候打印堆栈跟踪 foo 被称为。

    在一些标准的C库中有类似的东西吗?

    我使用GCC在Linux上工作。


    我有一个测试运行,它的行为不同,基于一些命令行开关,不应该影响这种行为。我的代码有一个伪随机数生成器,我假设它是基于这些开关被不同地调用的。我希望能够对每一组开关运行测试,看看随机数生成器对每个开关的调用是否不同。

    12 回复  |  直到 15 年前
        1
  •  77
  •   Idan K    15 年前

    对于只能使用linux的解决方案 backtrace(3) 返回一个数组 void * backtrace_symbols(3) .

    注意 notes section in backtrace(3) :

    选项。 对于使用GNU链接器的系统,有必要使用 -动态链接器 选择。注意,“static”函数的名称不公开, 不会的

        2
  •  28
  •   Ciro Santilli OurBigBook.com    6 年前

    https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack

    这是迄今为止我看到的最方便的选择,因为它:

    • 可以打印出行号。

      makes calls to addr2line however ,这很难看,如果你的痕迹太多的话可能会很慢。

    • 默认需求

    • Boost只是头文件,所以不需要修改构建系统

    助推_堆栈跟踪.cpp

    #include <iostream>
    
    #define BOOST_STACKTRACE_USE_ADDR2LINE
    #include <boost/stacktrace.hpp>
    
    void my_func_2(void) {
        std::cout << boost::stacktrace::stacktrace() << std::endl;
    }
    
    void my_func_1(double f) {
        (void)f;
        my_func_2();
    }
    
    void my_func_1(int i) {
        (void)i;
        my_func_2();
    }
    
    int main(int argc, char **argv) {
        long long unsigned int n;
        if (argc > 1) {
            n = strtoul(argv[1], NULL, 0);
        } else {
            n = 1;
        }
        for (long long unsigned int i = 0; i < n; ++i) {
            my_func_1(1);   // line 28
            my_func_1(2.0); // line 29
        }
    }
    

    不幸的是,这似乎是一个更新的添加,和包 libboost-stacktrace-dev 在Ubuntu 16.04中不存在,只有18.04:

    sudo apt-get install libboost-stacktrace-dev
    g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
      -Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
    ./boost_stacktrace.out
    

    -ldl 否则编译失败。

    输出:

     0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
     1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
     2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
     3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
     4# _start in ./boost_stacktrace.out
    
     0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
     1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
     2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
     3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
     4# _start in ./boost_stacktrace.out
    

    在下面的“glibc backtrace”部分中对输出和进行了进一步的解释,这是类似的。

    注意如何 my_func_1(int) my_func_1(float) , which are mangled due to function overload 对我们的要求很高。

    注意第一个 int 呼叫被一行关闭(28而不是27,第二行被两行关闭(27而不是29)。是的 suggested in the comments 这是因为正在考虑以下指令地址,这使得27变为28,29跳出循环变为27。

    -O3 ,输出完全被截断:

     0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
     1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
     2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
     3# _start in ./boost_stacktrace.out
    
     0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
     1# main at /home/ciro/test/boost_stacktrace.cpp:31
     2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
     3# _start in ./boost_stacktrace.out
    

    What is tail call optimization?

    基准运行 :

    time  ./boost_stacktrace.out 1000 >/dev/null
    

    输出:

    real    0m43.573s
    user    0m30.799s
    sys     0m13.665s
    

    所以正如所料,我们看到这个方法对于外部调用 ,只有在打了有限数量的电话时才可行。

    每次回溯打印似乎需要数百毫秒,因此请注意,如果回溯经常发生,程序性能将受到严重影响。

    在Ubuntu19.10,GCC9.2.1,Boost1.67.0上测试。

    backtrace

    https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

    主c

    #include <stdio.h>
    #include <stdlib.h>
    
    /* Paste this on the file you want to debug. */
    #include <stdio.h>
    #include <execinfo.h>
    void print_trace(void) {
        char **strings;
        size_t i, size;
        enum Constexpr { MAX_SIZE = 1024 };
        void *array[MAX_SIZE];
        size = backtrace(array, MAX_SIZE);
        strings = backtrace_symbols(array, size);
        for (i = 0; i < size; i++)
            printf("%s\n", strings[i]);
        puts("");
        free(strings);
    }
    
    void my_func_3(void) {
        print_trace();
    }
    
    void my_func_2(void) {
        my_func_3();
    }
    
    void my_func_1(void) {
        my_func_3();
    }
    
    int main(void) {
        my_func_1(); /* line 33 */
        my_func_2(); /* line 34 */
        return 0;
    }
    

    编译:

    gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
      -Wall -Wextra -pedantic-errors main.c
    

    -rdynamic 是必需的密钥选项。

    运行:

    ./main.out
    

    输出:

    ./main.out(print_trace+0x2d) [0x400a3d]
    ./main.out(main+0x9) [0x4008f9]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
    ./main.out(_start+0x29) [0x400939]
    
    ./main.out(print_trace+0x2d) [0x400a3d]
    ./main.out(main+0xe) [0x4008fe]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
    ./main.out(_start+0x29) [0x400939]
    

    addr2line -e main.out 0x4008f9 0x4008fe
    

    我们得到:

    /home/ciro/main.c:21
    /home/ciro/main.c:36
    

    完全关闭了。

    如果我们也这么做 -O0 相反, ./main.out 提供正确的完整跟踪:

    ./main.out(print_trace+0x2e) [0x4009a4]
    ./main.out(my_func_3+0x9) [0x400a50]
    ./main.out(my_func_1+0x9) [0x400a68]
    ./main.out(main+0x9) [0x400a74]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
    ./main.out(_start+0x29) [0x4008a9]
    
    ./main.out(print_trace+0x2e) [0x4009a4]
    ./main.out(my_func_3+0x9) [0x400a50]
    ./main.out(my_func_2+0x9) [0x400a5c]
    ./main.out(main+0xe) [0x400a79]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
    ./main.out(_start+0x29) [0x4008a9]
    

    然后:

    addr2line -e main.out 0x400a74 0x400a79
    

    给予:

    /home/cirsan01/test/main.c:34
    /home/cirsan01/test/main.c:35
    

    -O0号 . 通过优化,原始的回溯在编译后的代码中得到了根本性的修改。

    我找不到一个简单的方法来自动地把C++符号用这个方法去破解,这里有一些黑客:

    在Ubuntu 16.04,GCC 6.4.0,libc 2.23上测试。

    浮华 backtrace_symbols_fd

    这个助手比 backtrace_symbols ,并产生基本相同的输出:

    /* Paste this on the file you want to debug. */
    #include <execinfo.h>
    #include <stdio.h>
    #include <unistd.h>
    void print_trace(void) {
        size_t i, size;
        enum Constexpr { MAX_SIZE = 1024 };
        void *array[MAX_SIZE];
        size = backtrace(array, MAX_SIZE);
        backtrace_symbols_fd(array, size, STDOUT_FILENO);
        puts("");
    }
    

    在Ubuntu 16.04,GCC 6.4.0,libc 2.23上测试。

    浮华 带有C++的1分叉破解: -export-dynamic + dladdr

    https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3

    这是一个“黑客”,因为它需要用 -导出动态 .

    #include <dlfcn.h>     // for dladdr
    #include <cxxabi.h>    // for __cxa_demangle
    
    #include <cstdio>
    #include <string>
    #include <sstream>
    #include <iostream>
    
    // This function produces a stack backtrace with demangled function & method names.
    std::string backtrace(int skip = 1)
    {
        void *callstack[128];
        const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
        char buf[1024];
        int nFrames = backtrace(callstack, nMaxFrames);
        char **symbols = backtrace_symbols(callstack, nFrames);
    
        std::ostringstream trace_buf;
        for (int i = skip; i < nFrames; i++) {
            Dl_info info;
            if (dladdr(callstack[i], &info)) {
                char *demangled = NULL;
                int status;
                demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
                std::snprintf(
                    buf,
                    sizeof(buf),
                    "%-3d %*p %s + %zd\n",
                    i,
                    (int)(2 + sizeof(void*) * 2),
                    callstack[i],
                    status == 0 ? demangled : info.dli_sname,
                    (char *)callstack[i] - (char *)info.dli_saddr
                );
                free(demangled);
            } else {
                std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
                    i, (int)(2 + sizeof(void*) * 2), callstack[i]);
            }
            trace_buf << buf;
            std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
            trace_buf << buf;
        }
        free(symbols);
        if (nFrames == nMaxFrames)
            trace_buf << "[truncated]\n";
        return trace_buf.str();
    }
    
    void my_func_2(void) {
        std::cout << backtrace() << std::endl;
    }
    
    void my_func_1(double f) {
        (void)f;
        my_func_2();
    }
    
    void my_func_1(int i) {
        (void)i;
        my_func_2();
    }
    
    int main() {
        my_func_1(1);
        my_func_1(2.0);
    }
    

    编译并运行:

    g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
      -pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
    ./glibc_ldl.out 
    

    输出:

    1             0x40130a my_func_2() + 41
    ./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
    2             0x40139e my_func_1(int) + 16
    ./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
    3             0x4013b3 main + 18
    ./glibc_ldl.out(main+0x12) [0x4013b3]
    4       0x7f7594552b97 __libc_start_main + 231
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
    5             0x400f3a _start + 42
    ./glibc_ldl.out(_start+0x2a) [0x400f3a]
    
    1             0x40130a my_func_2() + 41
    ./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
    2             0x40138b my_func_1(double) + 18
    ./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
    3             0x4013c8 main + 39
    ./glibc_ldl.out(main+0x27) [0x4013c8]
    4       0x7f7594552b97 __libc_start_main + 231
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
    5             0x400f3a _start + 42
    ./glibc_ldl.out(_start+0x2a) [0x400f3a]
    

    浮华 用C++解散黑客2:解析回溯输出

    显示于: https://panthema.net/2008/0901-stacktrace-demangled网站/

    自由放纵

    代码改编自: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

    主c

    /* This must be on top. */
    #define _XOPEN_SOURCE 700
    
    #include <stdio.h>
    #include <stdlib.h>
    
    /* Paste this on the file you want to debug. */
    #define UNW_LOCAL_ONLY
    #include <libunwind.h>
    #include <stdio.h>
    void print_trace() {
        char sym[256];
        unw_context_t context;
        unw_cursor_t cursor;
        unw_getcontext(&context);
        unw_init_local(&cursor, &context);
        while (unw_step(&cursor) > 0) {
            unw_word_t offset, pc;
            unw_get_reg(&cursor, UNW_REG_IP, &pc);
            if (pc == 0) {
                break;
            }
            printf("0x%lx:", pc);
            if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
                printf(" (%s+0x%lx)\n", sym, offset);
            } else {
                printf(" -- error: unable to obtain symbol name for this frame\n");
            }
        }
        puts("");
    }
    
    void my_func_3(void) {
        print_trace();
    }
    
    void my_func_2(void) {
        my_func_3();
    }
    
    void my_func_1(void) {
        my_func_3();
    }
    
    int main(void) {
        my_func_1(); /* line 46 */
        my_func_2(); /* line 47 */
        return 0;
    }
    

    编译并运行:

    sudo apt-get install libunwind-dev
    gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
      -Wall -Wextra -pedantic-errors main.c -lunwind
    

    #define _XOPEN_SOURCE 700 必须在上面,否则我们必须使用 -std=gnu99 :

    运行:

    ./主输出
    

    输出:

    0x4007db: (main+0xb)
    0x7f4ff50aa830: (__libc_start_main+0xf0)
    0x400819: (_start+0x29)
    
    0x4007e2: (main+0x12)
    0x7f4ff50aa830: (__libc_start_main+0xf0)
    0x400819: (_start+0x29)
    

    以及:

    addr2line -e main.out 0x4007db 0x4007e2
    

    给予:

    /home/ciro/main.c:34
    /home/ciro/main.c:49
    

    :

    0x4009cf: (my_func_3+0xe)
    0x4009e7: (my_func_1+0x9)
    0x4009f3: (main+0x9)
    0x7f7b84ad7830: (__libc_start_main+0xf0)
    0x4007d9: (_start+0x29)
    
    0x4009cf: (my_func_3+0xe)
    0x4009db: (my_func_2+0x9)
    0x4009f8: (main+0xe)
    0x7f7b84ad7830: (__libc_start_main+0xf0)
    0x4007d9: (_start+0x29)
    

    addr2line -e main.out 0x4009f3 0x4009f8
    

    /home/ciro/main.c:47
    /home/ciro/main.c:48
    

    用C++名字命名的LyBunWrand

    https://eli.thegreenplace.net/2015/programmic-access-to-the-call-stack-in-c/

    展开.cpp

    #define UNW_LOCAL_ONLY
    #include <cxxabi.h>
    #include <libunwind.h>
    #include <cstdio>
    #include <cstdlib>
    #include <iostream>
    
    void backtrace() {
      unw_cursor_t cursor;
      unw_context_t context;
    
      // Initialize cursor to current frame for local unwinding.
      unw_getcontext(&context);
      unw_init_local(&cursor, &context);
    
      // Unwind frames one by one, going up the frame stack.
      while (unw_step(&cursor) > 0) {
        unw_word_t offset, pc;
        unw_get_reg(&cursor, UNW_REG_IP, &pc);
        if (pc == 0) {
          break;
        }
        std::printf("0x%lx:", pc);
    
        char sym[256];
        if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
          char* nameptr = sym;
          int status;
          char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
          if (status == 0) {
            nameptr = demangled;
          }
          std::printf(" (%s+0x%lx)\n", nameptr, offset);
          std::free(demangled);
        } else {
          std::printf(" -- error: unable to obtain symbol name for this frame\n");
        }
      }
    }
    
    void my_func_2(void) {
        backtrace();
        std::cout << std::endl; // line 43
    }
    
    void my_func_1(double f) {
        (void)f;
        my_func_2();
    }
    
    void my_func_1(int i) {
        (void)i;
        my_func_2();
    }  // line 54
    
    int main() {
        my_func_1(1);
        my_func_1(2.0);
    }
    

    sudo apt-get install libunwind-dev
    g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
      -Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
    ./unwind.out
    

    输出:

    0x400c80: (my_func_2()+0x9)
    0x400cb7: (my_func_1(int)+0x10)
    0x400ccc: (main+0x12)
    0x7f4c68926b97: (__libc_start_main+0xe7)
    0x400a3a: (_start+0x2a)
    
    0x400c80: (my_func_2()+0x9)
    0x400ca4: (my_func_1(double)+0x12)
    0x400ce1: (main+0x27)
    0x7f4c68926b97: (__libc_start_main+0xe7)
    0x400a3a: (_start+0x2a)
    

    my_func_2 我的函数1(int) 使用:

    addr2line -e unwind.out 0x400c80 0x400cb7
    

    /home/ciro/test/unwind.cpp:43
    /home/ciro/test/unwind.cpp:54
    

    托多:为什么一条线断了?

    GDB自动化

    我们也可以在不重新编译的情况下使用GDB,方法是: How to do an specific action when a certain breakpoint is hit in GDB?

    compile code How to call assembly in gdb?

    主.cpp

    void my_func_2(void) {}
    
    void my_func_1(double f) {
        my_func_2();
    }
    
    void my_func_1(int i) {
        my_func_2();
    }
    
    int main() {
        my_func_1(1);
        my_func_1(2.0);
    }
    

    主gdb

    start
    break my_func_2
    commands
      silent
      backtrace
      printf "\n"
      continue
    end
    continue
    

    编译并运行:

    g++ -ggdb3 -o main.out main.cpp
    gdb -nh -batch -x main.gdb main.out
    

    输出:

    Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.
    
    Temporary breakpoint 1, main () at main.cpp:12
    12          my_func_1(1);
    Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
    #0  my_func_2 () at main.cpp:1
    #1  0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
    #2  0x0000555555555162 in main () at main.cpp:12
    
    #0  my_func_2 () at main.cpp:1
    #1  0x000055555555513e in my_func_1 (f=2) at main.cpp:4
    #2  0x000055555555516f in main () at main.cpp:13
    
    [Inferior 1 (process 14193) exited normally]
    

    -ex 从命令行创建 main.gdb 但我没能得到 commands 在那里工作。

    Linux内核

    How to print the current thread stack trace inside the Linux kernel?

    libdwfl语言

    https://stackoverflow.com/a/60713161/895245 这也许是最好的方法,但我需要更多的基准测试,但请去投票的答案。

    托多:我试着把答案中的代码最小化,这个答案是有效的,变成一个函数,但它是分段的,如果有人能找到原因,请告诉我。

    dwfl.cpp公司

    #include <cassert>
    #include <iostream>
    #include <memory>
    #include <sstream>
    #include <string>
    
    #include <cxxabi.h> // __cxa_demangle
    #include <elfutils/libdwfl.h> // Dwfl*
    #include <execinfo.h> // backtrace
    #include <unistd.h> // getpid
    
    // https://stackoverflow.com/questions/281818/unmangling-the-result-of-stdtype-infoname
    std::string demangle(const char* name) {
        int status = -4;
        std::unique_ptr<char, void(*)(void*)> res {
            abi::__cxa_demangle(name, NULL, NULL, &status),
            std::free
        };
        return (status==0) ? res.get() : name ;
    }
    
    std::string debug_info(Dwfl* dwfl, void* ip) {
        std::string function;
        int line = -1;
        char const* file;
        uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
        Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
        char const* name = dwfl_module_addrname(module, ip2);
        function = name ? demangle(name) : "<unknown>";
        if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
            Dwarf_Addr addr;
            file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
        }
        std::stringstream ss;
        ss << ip << ' ' << function;
        if (file)
            ss << " at " << file << ':' << line;
        ss << std::endl;
        return ss.str();
    }
    
    std::string stacktrace() {
        // Initialize Dwfl.
        Dwfl* dwfl = nullptr;
        {
            Dwfl_Callbacks callbacks = {};
            char* debuginfo_path = nullptr;
            callbacks.find_elf = dwfl_linux_proc_find_elf;
            callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
            callbacks.debuginfo_path = &debuginfo_path;
            dwfl = dwfl_begin(&callbacks);
            assert(dwfl);
            int r;
            r = dwfl_linux_proc_report(dwfl, getpid());
            assert(!r);
            r = dwfl_report_end(dwfl, nullptr, nullptr);
            assert(!r);
            static_cast<void>(r);
        }
    
        // Loop over stack frames.
        std::stringstream ss;
        {
            void* stack[512];
            int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
            for (int i = 0; i < stack_size; ++i) {
                ss << i << ": ";
    
                // Works.
                ss << debug_info(dwfl, stack[i]);
    
    #if 0
                // TODO intended to do the same as above, but segfaults,
                // so possibly UB In above function that does not blow up by chance?
                void *ip = stack[i];
                std::string function;
                int line = -1;
                char const* file;
                uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
                Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
                char const* name = dwfl_module_addrname(module, ip2);
                function = name ? demangle(name) : "<unknown>";
                // TODO if I comment out this line it does not blow up anymore.
                if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
                  Dwarf_Addr addr;
                  file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
                }
                ss << ip << ' ' << function;
                if (file)
                    ss << " at " << file << ':' << line;
                ss << std::endl;
    #endif
            }
        }
        dwfl_end(dwfl);
        return ss.str();
    }
    
    void my_func_2() {
        std::cout << stacktrace() << std::endl;
        std::cout.flush();
    }
    
    void my_func_1(double f) {
        (void)f;
        my_func_2();
    }
    
    void my_func_1(int i) {
        (void)i;
        my_func_2();
    }
    
    int main(int argc, char **argv) {
        long long unsigned int n;
        if (argc > 1) {
            n = strtoul(argv[1], NULL, 0);
        } else {
            n = 1;
        }
        for (long long unsigned int i = 0; i < n; ++i) {
            my_func_1(1);
            my_func_1(2.0);
        }
    }
    

    sudo apt install libdw-dev
    g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
    ./dwfl.out
    

    输出:

    0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
    1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
    2: 0x402d7d my_func_1(int) at /home/ciro/test/dwfl.cpp:112
    3: 0x402de0 main at /home/ciro/test/dwfl.cpp:123
    4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
    5: 0x40253e _start at ../csu/libc-start.c:-1
    
    0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
    1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
    2: 0x402d66 my_func_1(double) at /home/ciro/test/dwfl.cpp:107
    3: 0x402df1 main at /home/ciro/test/dwfl.cpp:121
    4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
    5: 0x40253e _start at ../csu/libc-start.c:-1
    
    

    基准运行:

    g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
    time ./dwfl.out 1000 >/dev/null
    

    real    0m3.751s
    user    0m2.822s
    sys     0m0.928s
    

    因此,我们看到这个方法比Boost的stacktrace快10倍,因此可能适用于更多的用例。

    在Ubuntu19.10amd64中测试,libdw dev 0.176-1.1。

    另见

        3
  •  6
  •   Paul Michalik    15 年前

    没有标准化的方法可以做到这一点。对于windows,功能在 DbgHelp 图书馆

        4
  •  6
  •   NullPointerException    13 年前

    可以在特定函数中使用宏函数而不是返回语句。

    int foo(...)
    {
        if (error happened)
            return -1;
    
        ... do something ...
    
        return 0
    }
    

    可以使用宏函数。

    #include "c-callstack.h"
    
    int foo(...)
    {
        if (error happened)
            NL_RETURN(-1);
    
        ... do something ...
    
        NL_RETURN(0);
    }
    

    每当函数中发生错误时,您将看到Java风格的调用堆栈,如下所示。

    Error(code:-1) at : so_topless_ranking_server (sample.c:23)
    Error(code:-1) at : nanolat_database (sample.c:31)
    Error(code:-1) at : nanolat_message_queue (sample.c:39)
    Error(code:-1) at : main (sample.c:47)
    

    这里有完整的源代码。

    c-callstack at https://github.com/Nanolat

        5
  •  6
  •   Paul Floyd    6 年前

    当我需要这样做的时候,我通常只使用 system() pstack

    #include <sys/types.h>
    #include <unistd.h>
    #include <string>
    #include <sstream>
    #include <cstdlib>
    
    void f()
    {
        pid_t myPid = getpid();
        std::string pstackCommand = "pstack ";
        std::stringstream ss;
        ss << myPid;
        pstackCommand += ss.str();
        system(pstackCommand.c_str());
    }
    
    void g()
    {
       f();
    }
    
    
    void h()
    {
       g();
    }
    
    int main()
    {
       h();
    }
    

    这个输出

    #0  0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
    #1  0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
    #2  0x0000000000400c3c in f() ()
    #3  0x0000000000400cc5 in g() ()
    #4  0x0000000000400cd1 in h() ()
    #5  0x0000000000400cdd in main ()
    

    thread seems to have an alternative .

    如果你正在使用 C ,则需要使用 字符串函数。

    #include <sys/types.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    void f()
    {
        pid_t myPid = getpid();
        /*
          length of command 7 for 'pstack ', 7 for the PID, 1 for nul
        */
        char pstackCommand[7+7+1];
        sprintf(pstackCommand, "pstack %d", (int)myPid);
        system(pstackCommand);
    }
    

    我用7表示PID中的最大位数,基于 this post .

        6
  •  5
  •   Maxim Egorushkin    6 年前

    1. backtrace 在里面 glibc 仅当 -lunwind 已链接(未记录的平台特定功能)。
    2. 输出 , 源文件 行号 使用 #include <elfutils/libdwfl.h> (此库仅记录在其头文件中)。 backtrace_symbols backtrace_symbolsd_fd

    在现代Linux上,您可以使用函数获取stacktrace地址 回溯 . 无证制造 在流行的平台上生成更精确的地址 -lunwind libunwind-dev 在Ubuntu18.04上(见下面的输出示例)。 回溯 _Unwind_Backtrace 默认情况下,后者来自 libgcc_s.so.1 而且这种实现是最可移植的。什么时候? -轮风 它提供了一个更精确的版本 _展开回溯 但是这个库的可移植性较差(参见 libunwind/src ).

    不幸的是,同伴 backtrace_symbolsd backtrace_symbols_fd 十年来,函数无法将stacktrace地址解析为具有源文件名和行号的函数名(请参阅下面的示例输出)。

    , 源文件 行号 . 方法是 #包括<elfutils/libdwfl.h> 并与 -ldw libdw-dev

    工作C++实例 test.cc

    #include <stdexcept>
    #include <iostream>
    #include <cassert>
    #include <cstdlib>
    #include <string>
    
    #include <boost/core/demangle.hpp>
    
    #include <execinfo.h>
    #include <elfutils/libdwfl.h>
    
    struct DebugInfoSession {
        Dwfl_Callbacks callbacks = {};
        char* debuginfo_path = nullptr;
        Dwfl* dwfl = nullptr;
    
        DebugInfoSession() {
            callbacks.find_elf = dwfl_linux_proc_find_elf;
            callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
            callbacks.debuginfo_path = &debuginfo_path;
    
            dwfl = dwfl_begin(&callbacks);
            assert(dwfl);
    
            int r;
            r = dwfl_linux_proc_report(dwfl, getpid());
            assert(!r);
            r = dwfl_report_end(dwfl, nullptr, nullptr);
            assert(!r);
            static_cast<void>(r);
        }
    
        ~DebugInfoSession() {
            dwfl_end(dwfl);
        }
    
        DebugInfoSession(DebugInfoSession const&) = delete;
        DebugInfoSession& operator=(DebugInfoSession const&) = delete;
    };
    
    struct DebugInfo {
        void* ip;
        std::string function;
        char const* file;
        int line;
    
        DebugInfo(DebugInfoSession const& dis, void* ip)
            : ip(ip)
            , file()
            , line(-1)
        {
            // Get function name.
            uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
            Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
            char const* name = dwfl_module_addrname(module, ip2);
            function = name ? boost::core::demangle(name) : "<unknown>";
    
            // Get source filename and line number.
            if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
                Dwarf_Addr addr;
                file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
            }
        }
    };
    
    std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
        s << di.ip << ' ' << di.function;
        if(di.file)
            s << " at " << di.file << ':' << di.line;
        return s;
    }
    
    void terminate_with_stacktrace() {
        void* stack[512];
        int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
    
        // Print the exception info, if any.
        if(auto ex = std::current_exception()) {
            try {
                std::rethrow_exception(ex);
            }
            catch(std::exception& e) {
                std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
            }
            catch(...) {
                std::cerr << "Fatal unknown exception.\n";
            }
        }
    
        DebugInfoSession dis;
        std::cerr << "Stacktrace of " << stack_size << " frames:\n";
        for(int i = 0; i < stack_size; ++i) {
            std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
        }
        std::cerr.flush();
    
        std::_Exit(EXIT_FAILURE);
    }
    
    int main() {
        std::set_terminate(terminate_with_stacktrace);
        throw std::runtime_error("test exception");
    }
    

    用gcc-8.3在Ubuntu 18.04.4 LTS上编译:

    g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
    g++ -o test -g test.o -ldw -lunwind
    

    Fatal exception std::runtime_error: test exception.
    Stacktrace of 7 frames:
    0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
    1: 0x7fbc1c845ae5 <unknown>
    2: 0x7fbc1c845b20 std::terminate()
    3: 0x7fbc1c845d53 __cxa_throw
    4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
    5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
    6: 0x55f3837c17e9 _start
    

    当没有 如果链接,则生成的堆栈跟踪不太准确:

    0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
    1: 0x7f3c18ad6ae6 <unknown>
    2: 0x7f3c18ad6b21 <unknown>
    3: 0x7f3c18ad6d54 <unknown>
    4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
    5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
    6: 0x5591dd9d17aa _start
    

    回溯符号

    /home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
    /home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
    /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
    /home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]
    

    boost::core::demangle , std::string std::cout 和他们潜在的需求。

    也可以覆盖 __cxa_throw catch 块堆栈已被释放,因此调用已太迟 ,这就是为什么必须在 throw 通过函数实现 __cxa_throw __投球 可以由多个线程同时调用,以便如果它将stacktrace捕获到必须 thread_local .

        7
  •  3
  •   slashmais    15 年前

    使用一个全局(字符串)堆栈,在每个函数开始时,将函数名和其他值(如参数)推到这个堆栈上;在函数退出时,再次弹出它。

        8
  •  2
  •   Matthieu M.    15 年前

    当然,下一个问题是:这足够吗?

    堆栈跟踪的主要缺点是,为什么要调用精确的函数,而没有其他东西,比如它的参数值,这对调试非常有用。

    assert 检查特定条件,如果不满足则生成内存转储。当然,这意味着进程将停止,但您将拥有一个完整的报告,而不仅仅是堆栈跟踪。

    Pantheios 例如。这又一次给了你一个更精确的图像。

        9
  •  2
  •   Orlin Georgiev    11 年前

    你可以用 Poppy 为了这个。它通常用于在崩溃期间收集堆栈跟踪,但也可以为正在运行的程序输出它。

    好的方面是:它可以输出堆栈上每个函数的实际参数值,甚至本地变量、循环计数器等。

        10
  •  2
  •   François    8 年前

    我知道这条线很旧,但我认为它对其他人有用。如果您使用的是gcc,那么可以使用它的instrument特性(-finstrument functions选项)来记录任何函数调用(entry和exit)。查看此以获取更多信息: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

    例如,您可以将每个调用推送并弹出到一个堆栈中,当您想打印它时,只需查看堆栈中的内容。

    更新:您还可以在GCC文档中找到有关-finstrument functions compile选项的信息,该选项与Instrumentation选项有关: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

        11
  •  2
  •   Barkles    7 年前

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

    这里的人: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html

        12
  •  0
  •   Taryn Frank Pearson    13 年前

    您可以使用GNU分析器。它也显示了调用图!命令是 gprof

        13
  •  -6
  •   sbi    15 年前

    在调用某个函数时,是否有任何方法在调用C或C++的进程中转储调用堆栈?

    不存在,尽管可能存在依赖于平台的解决方案。