如何使用带有行号信息的 gcc 获取 C++ 的堆栈跟踪?[重复]
- 2024-10-22 08:28:00
- admin 原创
- 63
问题描述:
我们在专有的宏中使用堆栈跟踪assert
来捕捉开发人员的错误 - 当捕获到错误时,就会打印堆栈跟踪。
我发现 gcc 的 pair backtrace()
/ backtrace_symbols()
methods 不够充分:
名字混乱
无线路信息
第一个问题可以通过abi::__cxa_demangle解决。
但是第二个问题更棘手。我找到了backtrace_symbols() 的替代品。这比 gcc 的 backtrace_symbols() 更好,因为它可以检索行号(如果使用 -g 编译),而您不需要使用 -rdynamic 进行编译。
Hoverer 代码是 GNU 许可的,所以恕我直言,我不能将它用于商业代码。
有什么建议吗?
附言
gdb 能够打印出传递给函数的参数。可能这要求太高了 :)
PS2 游戏
类似问题(感谢nobar)
解决方案 1:
因此,您需要一个独立的函数,它可以打印堆栈跟踪,并具有 gdb 堆栈跟踪的所有功能,并且不会终止您的应用程序。答案是在非交互模式下自动启动 gdb,以执行您想要的任务。
这是通过在子进程中执行 gdb、使用 fork() 并编写脚本使其在应用程序等待其完成时显示堆栈跟踪来实现的。这可以在不使用核心转储和中止应用程序的情况下执行。我通过查看这个问题学会了如何做到这一点:如何更好地从程序中调用 gdb 来打印其堆栈跟踪?
该问题中发布的示例对我来说并不完全按照书面形式工作,所以这是我的“修复”版本(我在 Ubuntu 9.04 上运行了这个版本)。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/prctl.h>
void print_trace() {
char pid_buf[30];
sprintf(pid_buf, "%d", getpid());
char name_buf[512];
name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
int child_pid = fork();
if (!child_pid) {
dup2(2,1); // redirect output to stderr - edit: unnecessary?
execl("/usr/bin/gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
abort(); /* If gdb failed to start */
} else {
waitpid(child_pid,NULL,0);
}
}
如引用的问题所示,gdb 提供了您可以使用的附加选项。例如,使用“bt full”而不是“bt”会产生更详细的报告(输出中包含局部变量)。gdb 的手册页比较简略,但完整文档可在此处找到。
由于这是基于 gdb 的,因此输出包括解压缩的名称、行号、函数参数,甚至可选的局部变量。此外,gdb 具有线程感知能力,因此您应该能够提取一些特定于线程的元数据。
下面是我使用此方法看到的堆栈跟踪类型的示例。
0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0 0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1 0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2 0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3 0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4 0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5 0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70
注意:我发现这与valgrind的使用不兼容(可能是因为 Valgrind 使用虚拟机)。当您在 gdb 会话中运行程序时,它也不起作用(无法将“ptrace”的第二个实例应用于进程)。
解决方案 2:
不久前我回答了一个类似的问题。您应该查看方法 4 中提供的源代码,它还打印行号和文件名。
方法 4:
我对方法 3 做了一个小改进,可以打印行号。这也可以复制到方法 2 上。
基本上,它使用addr2line将地址转换为文件名和行号。
下面的源代码打印所有本地函数的行号。如果调用了另一个库中的函数,您可能会看到几个??:0
而不是文件名。
#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
", sig, ctx.cr2, ctx.eip);
else
printf("Got signal %d
", 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:
");
for (i=1; i<trace_size; ++i)
{
printf("[bt] #%d %s
", 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
", func_b());
}
该代码应被编译为:gcc sighandler.c -o sighandler -rdynamic
程序输出:
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
解决方案 3:
在“当我的 gcc C++ 应用程序崩溃时如何生成堆栈跟踪”中,对本质上相同的问题进行了深入的讨论。提供了许多建议,包括大量关于如何在运行时生成堆栈跟踪的讨论。
我个人最喜欢的答案是启用核心转储,这样您就可以查看崩溃时的完整应用程序状态(包括函数参数、行号和未损坏的名称)。这种方法的另一个好处是它不仅适用于断言,还适用于分段错误和未处理的异常。
不同的 Linux shell 使用不同的命令来启用核心转储,但您可以在应用程序代码中使用类似这样的命令来执行此操作...
#include <sys/resource.h>
...
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds
崩溃后,运行您最喜欢的调试器来检查程序状态。
$ kdbg executable core
以下是一些示例输出...
还可以从命令行的核心转储中提取堆栈跟踪。
$ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core )
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 22857]
#0 0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#0 0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#1 0x00007f4189be7bc3 in abort () from /lib/libc.so.6
#2 0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6
#3 0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18
#4 0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19
#5 0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19
#6 0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19
#7 0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19
#8 0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19
#9 0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26
解决方案 4:
由于 GPL 许可代码旨在帮助您进行开发,因此您可以简单地不将其包含在最终产品中。GPL 限制您分发与非 GPL 兼容代码链接的 GPL 许可代码。只要您只在内部使用 GPL 代码,就没问题。
解决方案 5:
这是另一种方法。debug_assert ()宏以编程方式设置条件断点。如果您在调试器中运行,则当断言表达式为假时,您将到达断点 - 并且您可以分析实时堆栈(程序不会终止)。如果您没有在调试器中运行,则失败的 debug_assert() 会导致程序中止,并且您会得到一个核心转储,您可以从中分析堆栈(请参阅我之前的回答)。
与普通断言相比,这种方法的优点是,在触发 debug_assert 后(在调试器中运行时),您可以继续运行程序。换句话说,debug_assert() 比 assert() 稍微灵活一些。
#include <iostream>
#include <cassert>
#include <sys/resource.h>
// note: The assert expression should show up in
// stack trace as parameter to this function
void debug_breakpoint( char const * expression )
{
asm("int3"); // x86 specific
}
#ifdef NDEBUG
#define debug_assert( expression )
#else
// creates a conditional breakpoint
#define debug_assert( expression ) \n do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0)
#endif
void recursive( int i=0 )
{
debug_assert( i < 5 );
if ( i < 10 ) recursive(i+1);
}
int main( int argc, char * argv[] )
{
rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
recursive();
}
注意:有时在调试器中设置的“条件断点”可能会很慢。通过以编程方式建立断点,此方法的性能应与普通 assert() 的性能相当。
注意:正如所写,这特定于 Intel x86 架构——其他处理器可能有不同的生成断点的指令。
解决方案 6:
使用 google glog 库。它有新的 BSD 许可证。
它在 stacktrace.h 文件中包含一个 GetStackTrace 函数。
编辑
我在这里http://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/发现有一个名为 addr2line 的实用程序,可以将程序地址转换为文件名和行号。
http://linuxcommand.org/man_pages/addr2line1.html
解决方案 7:
有点晚了,但你可以使用libbfb来获取文件名和行号,就像 refdbg 在symsnarf.caddr2line
中所做的那样。libbfb 在内部由和使用gdb
解决方案 8:
这是我的解决方案:
#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <zconf.h>
#include "regex"
std::string getexepath() {
char result[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
return std::string(result, (count > 0) ? count : 0);
}
std::string sh(std::string cmd) {
std::array<char, 128> buffer;
std::string result;
std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe) throw std::runtime_error("popen() failed!");
while (!feof(pipe.get())) {
if (fgets(buffer.data(), 128, pipe.get()) != nullptr) {
result += buffer.data();
}
}
return result;
}
void print_backtrace(void) {
void *bt[1024];
int bt_size;
char **bt_syms;
int i;
bt_size = backtrace(bt, 1024);
bt_syms = backtrace_symbols(bt, bt_size);
std::regex re("\\[(.+)\\]");
auto exec_path = getexepath();
for (i = 1; i < bt_size; i++) {
std::string sym = bt_syms[i];
std::smatch ms;
if (std::regex_search(sym, ms, re)) {
std::string addr = ms[1];
std::string cmd = "addr2line -e " + exec_path + " -f -C " + addr;
auto r = sh(cmd);
std::regex re2("\\n$");
auto r2 = std::regex_replace(r, re2, "");
std::cout << r2 << std::endl;
}
}
free(bt_syms);
}
void test_m() {
print_backtrace();
}
int main() {
test_m();
return 0;
}
输出:
/home/roroco/Dropbox/c/ro-c/cmake-build-debug/ex/test_backtrace_with_line_number
test_m()
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:57
main
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:61
??
??:0
“??” 和 “??:0” 因为这个跟踪是在 libc 中,而不是在我的源代码中
解决方案 9:
解决方案之一是在失败的断言处理程序中使用“bt”脚本启动 gdb。集成这种 gdb 启动不太容易,但它会为您提供回溯和参数以及解调名称(或者您可以通过 c++filt 程序传递 gdb 输出)。
这两个程序(gdb 和 c++filt)都不会链接到您的应用程序中,因此 GPL 不会要求您开源完整的应用程序。
您可以使用与 backtrace-symbols 相同的方法(执行 GPL 程序)。只需生成 %eip 的 ascii 列表和执行文件 (/proc/self/maps) 的映射并将其传递给单独的二进制文件即可。
解决方案 10:
您可以使用DeathHandler - 小型 C++ 类,它可以为您完成所有工作,非常可靠。
解决方案 11:
我必须在具有许多限制的生产环境中执行此操作,所以我想解释一下已经发布的方法的优点和缺点。
附加GDB
非常简单和强大
对于大型程序来说速度很慢,因为 GDB 坚持预先将整个地址加载到行 # 数据库中,而不是延迟加载
干扰信号处理。当连接 GDB 时,它会拦截 SIGINT(ctrl-c)之类的信号,这将导致程序卡在 GDB 交互提示符处?如果其他进程经常发送此类信号。也许有办法解决这个问题,但这导致 GDB 在我的情况下无法使用。如果您只关心在程序崩溃时打印一次调用堆栈,而不是多次,您仍然可以使用它。
addr2line。这是一个不使用 backtrace_symbols 的替代解决方案。
不从堆中分配,这在信号处理程序中是不安全的
不需要解析 backtrace_symbols 的输出
不适用于没有 dladdr1 的 MacOS。您可以改用 _dyld_get_image_vmaddr_slide,它返回的偏移量与 link_map::l_addr 相同。
需要添加负偏移量,否则翻译的行号将大 1。backtrace_symbols 会为您完成此操作
#include <execinfo.h>
#include <link.h>
#include <stdlib.h>
#include <stdio.h>
// converts a function's address in memory to its VMA address in the executable file. VMA is what addr2line expects
size_t ConvertToVMA(size_t addr)
{
Dl_info info;
link_map* link_map;
dladdr1((void*)addr,&info,(void**)&link_map,RTLD_DL_LINKMAP);
return addr-link_map->l_addr;
}
void PrintCallStack()
{
void *callstack[128];
int frame_count = backtrace(callstack, sizeof(callstack)/sizeof(callstack[0]));
for (int i = 0; i < frame_count; i++)
{
char location[1024];
Dl_info info;
if(dladdr(callstack[i],&info))
{
char command[256];
size_t VMA_addr=ConvertToVMA((size_t)callstack[i]);
//if(i!=crash_depth)
VMA_addr-=1; // https://stackoverflow.com/questions/11579509/wrong-line-numbers-from-addr2line/63841497#63841497
snprintf(command,sizeof(command),"addr2line -e %s -Ci %zx",info.dli_fname,VMA_addr);
system(command);
}
}
}
void Foo()
{
PrintCallStack();
}
int main()
{
Foo();
return 0;
}
我还想澄清一下 backtrace 和 backtrace_symbols 生成的地址以及 addr2line 期望的地址。addr2line
期望 Foo VMA,或者如果您使用 --section=.text,则期望 Foo file - text file。backtrace返回 Foo mem。backtrace_symbols在某处生成 Foo VMA。我犯的一个大错误并且在其他几篇文章中看到的是假设 VMA base = 0 或 Foo VMA = Foo file = Foo mem - ELF mem,这很容易计算。这通常有效,但对于某些编译器(即链接器脚本),请使用 VMA base > 0。例如 Ubuntu 16 上的 GCC 5.4(0x400000)和 MacOS 上的 clang 11(0x100000000)。对于共享库,它始终为 0。似乎 VMAbase 仅对非位置独立代码有意义。否则它对 EXE 在内存中的加载位置没有影响。
此外,karlphillip 和本文都不需要使用 -rdynamic 进行编译。这会增加二进制文件的大小,尤其是对于大型 C++ 程序或共享库而言,因为动态符号表中存在永远不会导入的无用条目
解决方案 12:
我认为行号与当前 eip 值有关,对吗?
解决方案 1:
然后您可以使用类似GetThreadContext() 的东西,只不过您是在 Linux 上工作。我在 Google 上搜索了一下,找到了类似的东西,ptrace():
ptrace() 系统调用提供了一种方法,父进程可以通过该方法观察和控制另一个进程的执行,并检查和更改其核心映像和寄存器。[...] 父进程可以通过调用 fork(2) 并让生成的子进程执行 PTRACE_TRACEME,然后(通常)执行 exec(3) 来启动跟踪。或者,父进程可以使用 PTRACE_ATTACH 开始跟踪现有进程。
现在我在想,你可以做一个“主”程序,检查发送给它的子程序(你正在处理的真正程序)的信号。之后fork()
调用waitid():
所有这些系统调用都用于等待调用进程的子进程的状态改变,并获取有关状态已改变的子进程的信息。
如果捕获到 SIGSEGV (或类似的东西),则调用ptrace()
以获取eip
的值。
PS:我从未使用过这些系统调用(好吧,实际上,我以前从未见过它们;)所以我不知道是否有可能帮助你。至少我希望这些链接有用。;)
解决方案 2:
第一个解决方案相当复杂,对吧?我想出了一个更简单的方法:使用signal()捕获您感兴趣的信号并调用一个读取eip
堆栈中存储的值的简单函数:
...
signal(SIGSEGV, sig_handler);
...
void sig_handler(int signum)
{
int eip_value;
asm {
push eax;
mov eax, [ebp - 4]
mov eip_value, eax
pop eax
}
// now you have the address of the
// **next** instruction after the
// SIGSEGV was received
}
该 asm 语法是 Borland 的语法,只需使其适应即可GAS
。;)
解决方案 13:
这是我的第三个答案——仍然尝试利用核心转储。
问题并不完全清楚,“类似断言”的宏是否应该终止应用程序(就像断言那样)或者它们是否应该在生成堆栈跟踪后继续执行。
在这个答案中,我解决了您想要显示堆栈跟踪并继续执行的情况。我编写了下面的coredump () 函数来生成核心转储,自动从中提取堆栈跟踪,然后继续执行程序。
用法与 assert() 相同。当然,区别在于 assert() 会终止程序,而coredump_assert () 则不会。
#include <iostream>
#include <sys/resource.h>
#include <cstdio>
#include <cstdlib>
#include <boost/lexical_cast.hpp>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
std::string exename;
// expression argument is for diagnostic purposes (shows up in call-stack)
void coredump( char const * expression )
{
pid_t childpid = fork();
if ( childpid == 0 ) // child process generates core dump
{
rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
abort(); // terminate child process and generate core dump
}
// give each core-file a unique name
if ( childpid > 0 ) waitpid( childpid, 0, 0 );
static int count=0;
using std::string;
string pid = boost::lexical_cast<string>(getpid());
string newcorename = "core-"+boost::lexical_cast<string>(count++)+"."+pid;
string rawcorename = "core."+boost::lexical_cast<string>(childpid);
int rename_rval = rename(rawcorename.c_str(),newcorename.c_str()); // try with core.PID
if ( rename_rval == -1 ) rename_rval = rename("core",newcorename.c_str()); // try with just core
if ( rename_rval == -1 ) std::cerr<<"failed to capture core file
";
#if 1 // optional: dump stack trace and delete core file
string cmd = "( CMDFILE=$(mktemp); echo 'bt' >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} "+exename+" "+newcorename+" ; unlink ${CMDFILE} )";
int system_rval = system( ("bash -c '"+cmd+"'").c_str() );
if ( system_rval == -1 ) std::cerr.flush(), perror("system() failed during stack trace"), fflush(stderr);
unlink( newcorename.c_str() );
#endif
}
#ifdef NDEBUG
#define coredump_assert( expression ) ((void)(expression))
#else
#define coredump_assert( expression ) do { if ( !(expression) ) { coredump( #expression ); } } while (0)
#endif
void recursive( int i=0 )
{
coredump_assert( i < 2 );
if ( i < 4 ) recursive(i+1);
}
int main( int argc, char * argv[] )
{
exename = argv[0]; // this is used to generate the stack trace
recursive();
}
当我运行该程序时,它会显示三个堆栈跟踪......
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24251]
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3 0x0000000000401f5f in recursive (i=2) at ./demo3.cpp:60
#4 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#5 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#6 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24259]
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3 0x0000000000401f5f in recursive (i=3) at ./demo3.cpp:60
#4 0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#5 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#6 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#7 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24267]
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3 0x0000000000401f5f in recursive (i=4) at ./demo3.cpp:60
#4 0x0000000000401f70 in recursive (i=3) at ./demo3.cpp:61
#5 0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#6 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#7 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#8 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
解决方案 14:
据我所知,目前提供的所有解决方案都不会打印共享库中的函数名称和行号。这正是我所需要的,所以我修改了 karlphillip 的解决方案(以及类似问题中的其他一些答案),以使用 /proc/id/maps 解析共享库地址。
#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <execinfo.h>
#include <stdbool.h>
struct Region { // one mapped file, for example a shared library
uintptr_t start;
uintptr_t end;
char* path;
};
static struct Region* getRegions(int* size) {
// parse /proc/self/maps and get list of mapped files
FILE* file;
int allocated = 10;
*size = 0;
struct Region* res;
uintptr_t regionStart = 0x00000000;
uintptr_t regionEnd = 0x00000000;
char* regionPath = "";
uintmax_t matchedStart;
uintmax_t matchedEnd;
char* matchedPath;
res = (struct Region*)malloc(sizeof(struct Region) * allocated);
file = fopen("/proc/self/maps", "r");
while (!feof(file)) {
fscanf(file, "%jx-%jx %*s %*s %*s %*s%*[ ]%m[^
]
", &matchedStart, &matchedEnd, &matchedPath);
bool bothNull = matchedPath == 0x0 && regionPath == 0x0;
bool similar = matchedPath && regionPath && !strcmp(matchedPath, regionPath);
if(bothNull || similar) {
free(matchedPath);
regionEnd = matchedEnd;
} else {
if(*size == allocated) {
allocated *= 2;
res = (struct Region*)realloc(res, sizeof(struct Region) * allocated);
}
res[*size].start = regionStart;
res[*size].end = regionEnd;
res[*size].path = regionPath;
(*size)++;
regionStart = matchedStart;
regionEnd = matchedEnd;
regionPath = matchedPath;
}
}
return res;
}
struct SemiResolvedAddress {
char* path;
uintptr_t offset;
};
static struct SemiResolvedAddress semiResolve(struct Region* regions, int regionsNum, uintptr_t address) {
// convert address from our address space to
// address suitable fo addr2line
struct Region* region;
struct SemiResolvedAddress res = {"", address};
for(region = regions; region < regions+regionsNum; region++) {
if(address >= region->start && address < region->end) {
res.path = region->path;
res.offset = address - region->start;
}
}
return res;
}
void printStacktraceWithLines(unsigned int max_frames)
{
int regionsNum;
fprintf(stderr, "stack trace:
");
// 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(stderr, " <empty, possibly corrupt>
");
return;
}
struct Region* regions = getRegions(&regionsNum);
for (int i = 1; i < addrlen; i++)
{
struct SemiResolvedAddress hres =
semiResolve(regions, regionsNum, (uintptr_t)(addrlist[i]));
char syscom[256];
sprintf(syscom, "addr2line -C -f -p -a -e %s 0x%jx", hres.path, (intmax_t)(hres.offset));
system(syscom);
}
free(regions);
}
解决方案 15:
C++23<stacktrace>
终于,它来了!更多详细信息/与其他系统的比较请访问:使用 C 或 C++ 打印调用堆栈
堆栈跟踪.cpp
#include <iostream>
#include <stacktrace>
void my_func_2(void) {
std::cout << std::stacktrace::current(); // Line 5
}
void my_func_1(double f) {
(void)f;
my_func_2(); // Line 10
}
void my_func_1(int i) {
(void)i;
my_func_2(); // Line 15
}
int main(int argc, char **argv) {
my_func_1(1); // Line 19
my_func_1(2.0); // Line 20
}
Ubuntu 22.04 中的 GCC 12.1.0 没有编译支持,所以现在我按照以下步骤从源代码构建它:如何编辑和重新构建 GCC libstdc++ C++ 标准库源代码?并设置--enable-libstdcxx-backtrace=yes
,它就成功了!
编译并运行:
g++ -ggdb3 -O2 -std=c++23 -Wall -Wextra -pedantic -o stacktrace.out stacktrace.cpp -lstdc++_libbacktrace
./stacktrace.out
输出:
0# my_func_2() at /home/ciro/stacktrace.cpp:5
1# my_func_1(int) at /home/ciro/stacktrace.cpp:15
2# at :0
3# at :0
4# at :0
5#
0# my_func_2() at /home/ciro/stacktrace.cpp:5
1# my_func_1(double) at /home/ciro/stacktrace.cpp:10
2# at :0
3# at :0
4# at :0
5#
main
我认为由于优化,跟踪并不完美(缺少线条)。有了-O0
它就更好了:
0# my_func_2() at /home/ciro/stacktrace.cpp:5
1# my_func_1(int) at /home/ciro/stacktrace.cpp:15
2# at /home/ciro/stacktrace.cpp:19
3# at :0
4# at :0
5# at :0
6#
0# my_func_2() at /home/ciro/stacktrace.cpp:5
1# my_func_1(double) at /home/ciro/stacktrace.cpp:10
2# at /home/ciro/stacktrace.cpp:20
3# at :0
4# at :0
5# at :0
6#
我不知道为什么名字main
消失了,但是那条线就在那里。
类似下面的“额外”行main
:
3# at :0
4# at :0
5# at :0
6#
可能是在之前运行main
并最终调用的东西main
:在 C++ 中 main 之前发生了什么?
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件