print call stack in C or C++

邮差的信 提交于 2019-11-26 01:02:39

For a linux-only solution you can use backtrace(3) that simply returns an array of void * (in fact each of these point to the return address from the corresponding stack frame). To translate these to something of use, there's backtrace_symbols(3).

Pay attention to the notes section in backtrace(3):

The symbol names may be unavailable without the use of special linker options. For systems using the GNU linker, it is necessary to use the -rdynamic linker option. Note that names of "static" functions are not exposed, and won't be available in the backtrace.

Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called?

You can use a macro function instead of return statement in the specific function.

For example, instead of using return,

int foo(...)
{
    if (error happened)
        return -1;

    ... do something ...

    return 0
}

You can use a macro function.

#include "c-callstack.h"

int foo(...)
{
    if (error happened)
        NL_RETURN(-1);

    ... do something ...

    NL_RETURN(0);
}

Whenever an error happens in a function, you will see Java-style call stack as shown below.

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)

Full source code is available here.

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

Boost stacktrace

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

This is the most convenient option I've seen so far, because it:

  • can actually print out the line numbers.

    It just makes calls to addr2line however, which is ugly and might be slow if your are taking too many traces.

  • demangles by default

  • Boost is header only, so no need to modify your build system most likely

main.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) {
    my_func_2();
}

void my_func_1(int i) {
    my_func_2();
}

int main() {
    my_func_1(1);   /* line 19 */
    my_func_1(2.0); /* line 20 */
}

Unfortunately, it seems to be a more recent addition, and the package libboost-stacktrace-dev is not present in Ubuntu 16.04, only 18.04:

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

We have to add -ldl at the end or else compilation fails.

Then:

./main.out

gives:

 0# my_func_2() at /root/lkmc/main.cpp:7
 1# my_func_1(int) at /root/lkmc/main.cpp:16
 2# main at /root/lkmc/main.cpp:20
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./main.out

 0# my_func_2() at /root/lkmc/main.cpp:7
 1# my_func_1(double) at /root/lkmc/main.cpp:12
 2# main at /root/lkmc/main.cpp:21
 3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 4# _start in ./main.out

And with -O3:

 0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
 1# my_func_1(double) at /root/lkmc/main.cpp:11
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./main.out

 0# my_func_2() at /usr/include/boost/stacktrace/stacktrace.hpp:217
 1# main at /root/lkmc/main.cpp:21
 2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 3# _start in ./main.out

Keep in mind that backtraces are in general irreparably mutilated by optimizations. Tail call optimization is a notable example of that: What Is Tail Call Optimization?

The output and is further explained on the "glibc backtrace" section below, which is analogous.

Tested on Ubuntu 18.04, GCC 7.3.0, boost 1.65.1.

glibc backtrace

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

main.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;
}

Compile:

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

-rdynamic is the key required option.

Run:

./main.out

Outputs:

./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]

So we immediately see that an inlining optimization happened, and some functions were lost from the trace.

If we try to get the addresses:

addr2line -e main.out 0x4008f9 0x4008fe

we obtain:

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

which is completely off.

If we do the same with -O0 instead, ./main.out gives the correct full trace:

./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]

and then:

addr2line -e main.out 0x400a74 0x400a79

gives:

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

so the lines are off by just one, TODO why? But this might still be usable.

Conclusion: backtraces can only possibly show perfectly with -O0. With optimizations, the original backtrace is fundamentally modified in the compiled code.

Tested on Ubuntu 16.04, GCC 6.4.0, libc 2.23.

glibc backtrace_symbols_fd

This helper is a bit more convenient than backtrace_symbols, and produces basically identical output:

/* 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("");
}

Tested on Ubuntu 16.04, GCC 6.4.0, libc 2.23.

libunwind

TODO does this have any advantage over glibc backtrace? Very similar output, also requires modifying the build command, but less widely available.

Code adapted from: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

main.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;
}

Compile and run:

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

Either #define _XOPEN_SOURCE 700 must be on top, or we must use -std=gnu99:

Run:

./main.out

Output:

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

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

and:

addr2line -e main.out 0x4007db 0x4007e2

gives:

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

With -O0:

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)

and:

addr2line -e main.out 0x4009f3 0x4009f8

gives:

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

Tested on Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.

C++ demangling

Can be done with abi::__cxa_demangle for both glibc backtrace and libunwind, see an example at: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/

Linux kernel

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

See also

There is no standardized way to do that. For windows the functionality is provided in the DbgHelp library

Another answer to an old thread.

When I need to do this, I usually just use system() and pstack

So something like this:

#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();
}

This outputs

#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 ()

This should work on Linux, FreeBSD and Solaris. I don't think that macOS has pstack or a simple equivalent, but this thread seems to have an alternative.

You can implement the functionality yourself:

Use a global (string)stack and at start of each function push the function name and such other values (eg parameters) onto this stack; at exit of function pop it again.

Write a function that will printout the stack content when it is called, and use this in the function where you want to see the callstack.

This may sound like a lot of work but is quite useful.

Of course the next question is: will this be enough ?

The main disadvantage of stack-traces is that why you have the precise function being called you do not have anything else, like the value of its arguments, which is very useful for debugging.

If you have access to gcc and gdb, I would suggest using assert to check for a specific condition, and produce a memory dump if it is not met. Of course this means the process will stop, but you'll have a full fledged report instead of a mere stack-trace.

If you wish for a less obtrusive way, you can always use logging. There are very efficient logging facilities out there, like Pantheios for example. Which once again could give you a much more accurate image of what is going on.

You can use Poppy for this. It is normally used to gather the stack trace during a crash but it can also output it for a running program as well.

Now here's the good part: it can output the actual parameter values for each function on the stack, and even local variables, loop counters, etc.

You can use the Boost libraries to print the current callstack.

#include <boost/stacktrace.hpp>

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

Man here: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html

I know this thread is old, but I think it can be useful for other people. If you are using gcc, you can use its instrument features (-finstrument-functions option) to log any function call (entry and exit). Have a look at this for more information: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html

You can thus for instance push and pop every calls into a stack, and when you want to print it, you just look at what you have in your stack.

I've tested it, it works perfectly and is very handy

UPDATE: you can also find information about the -finstrument-functions compile option in the GCC doc concerning the Instrumentation options: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html

Saurabh Shah

You can use the GNU profiler. It shows the call-graph as well! the command is gprof and you need to compile your code with some option.

Is there any way to dump the call stack in a running process in C or C++ every time a certain function is called?

No there is not, although platform-dependent solutions might exist.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!