How to Get a Stack Trace for C++ Using Gcc With Line Number Information

How to get a stack trace for C++ using gcc with line number information?

Not too long ago I answered a similar question. You should take a look at the source code available on method #4, which also prints line numbers and filenames.

  • Method #4:

A small improvement I've done on method #3 to print line numbers. This could be copied to work on method #2 also.

Basically, it uses addr2line to convert addresses into file names and line numbers.

The source code below prints line numbers for all local functions. If a function from another library is called, you might see a couple of ??:0 instead of file names.

#include <stdio.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>
#include <execinfo.h>

void bt_sighandler(int sig, struct sigcontext ctx) {

void *trace[16];
char **messages = (char **)NULL;
int i, trace_size = 0;

if (sig == SIGSEGV)
printf("Got signal %d, faulty address is %p, "
"from %p\n", sig, ctx.cr2, ctx.eip);
else
printf("Got signal %d\n", sig);

trace_size = backtrace(trace, 16);
/* overwrite sigaction with caller's address */
trace[1] = (void *)ctx.eip;
messages = backtrace_symbols(trace, trace_size);
/* skip first stack frame (points here) */
printf("[bt] Execution path:\n");
for (i=1; i<trace_size; ++i)
{
printf("[bt] #%d %s\n", i, messages[i]);

/* find first occurence of '(' or ' ' in message[i] and assume
* everything before that is the file name. (Don't go beyond 0 though
* (string terminator)*/
size_t p = 0;
while(messages[i][p] != '(' && messages[i][p] != ' '
&& messages[i][p] != 0)
++p;

char syscom[256];
sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
//last parameter is the file name of the symbol
system(syscom);
}

exit(0);
}


int func_a(int a, char b) {

char *p = (char *)0xdeadbeef;

a = a + b;
*p = 10; /* CRASH here!! */

return 2*a;
}


int func_b() {

int res, a = 5;

res = 5 + func_a(a, 't');

return res;
}


int main() {

/* Install our signal handler */
struct sigaction sa;

sa.sa_handler = (void *)bt_sighandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;

sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
/* ... add any other signal here */

/* Do something */
printf("%d\n", func_b());
}

This code should be compiled as: gcc sighandler.c -o sighandler -rdynamic

The program outputs:

Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0

Is there a portable/standard-compliant way to get filenames and linenumbers in a stack trace?

Adding to @EmployedRussian's valid answer - there is now a multi-platform library which does this:

Boost StackTrace

And just to illustrate what a trace looks like, if you were to write:

// This following definition may be necessary to ensure you can get
// line numbers included in the stack trace; see:
// https://stackoverflow.com/questions/3899870/
// for details
//
#define BOOST_STACKTRACE_USE_ADDR2LINE

#include <boost/stacktrace.hpp>

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

you might get something like (on Linux for example):

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start

How to find line number in ThreadSanitizer stack trace

Look for "/home" for finding your code.

The stacks of the threads look nice with well shown line numbers. Your MediaServer::initialize() created the thread T1.

Thread T1 (tid=2667937, running) created by main thread at:
#2 MediaServer::initialize /home/MediaServer/MediaServerMethods.cpp:1808
#3 main /home/MediaServer/MediaServer.cpp:33

T1 continued MediaServer initialization and created the thread 7.

Thread T7 (tid=2667963, running) created by thread T1 at:
#31 SimpleWeb::SocketServerBase<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::executor> >::start() /usr/local/include/simple-websocket-server/server_ws.hpp:437
#32 MediaServer::initialize()::$_7::operator()() const /home/MediaServer/MediaServerMethods.cpp:1812

T7 received some message and called your code, that was creating std::shared_ptr that was creating some std::string from a C string perhaps.

Previous write of size 8 at 0x7b1800006060 by thread T7:
#1 void std::__cxx11::basic_string<char>::_M_construct<char*>(char*, char*, std::forward_iterator_tag) /usr/include/c++/11/bits/basic_string.tcc:219
#2 rtc::impl::Certificate::Certificate()
...
#8 std::make_shared<rtc::PeerConnection>() /usr/include/c++/11/bits/shared_ptr.h:876
#9 RTCTransport::createPeerConnection() /home/MediaServer/MediaServerMethods.cpp:458
#10 MediaServer::initialize()::$_5::operator()(...) const /home/MediaServer/MediaServerMethods.cpp:1761

Finally thread T1 was attempting to construct the same std::string without using a lock guard.

WARNING: ThreadSanitizer: data race (pid=2667935)
Read of size 8 at 0x7b1800006060 by thread T1:
#1 void std::__cxx11::basic_string<char>::_M_construct<char*>(char*, char*, std::forward_iterator_tag) /usr/include/c++/11/bits/basic_string.tcc:225
#2 rtc::impl::Certificate::fingerprint[abi:cxx11]() const
...
#24 MediaServer::initialize()::$_7::operator()() const /home/MediaServer/MediaServerMethods.cpp:1812

print call stack in C or C++

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.

Failing to get line numbers with Boost stacktrace + backtrace

To get more information in the stack track uses a debug build.
For GCC compile a project with the flag -g or -fno-omit-frame-pointer.



Related Topics



Leave a reply



Submit