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
Vscode C++ Task.Json Include Path and Libraries
Why Does Std::Getline() Skip Input After a Formatted Extraction
Why Do I Have to Access Template Base Class Members Through the This Pointer
What Does the Explicit Keyword Mean
How to Parse a String to an Int in C++
What Is "Rvalue Reference For *This"
C++ Convert Hex String to Signed Integer
Is Local Static Variable Initialization Thread-Safe in C++11
Why Should I Not Include Cpp Files and Instead Use a Header
Take the Address of a One-Past-The-End Array Element Via Subscript: Legal by the C++ Standard or Not
How to Properly Add Include Directories With Cmake
How to Iterate Over the Words of a String
What Are Aggregates and Pods and How/Why Are They Special
Regular Cast Vs. Static_Cast Vs. Dynamic_Cast
Why Does Reading a Record Struct Fields from Std::Istream Fail, and How to Fix It
How to Remove Code Duplication Between Similar Const and Non-Const Member Functions
How to Print a List of Elements Separated by Commas
Function With Same Name But Different Signature in Derived Class