dim*_*mba 57 c++ linux gcc stack-trace
我们在专有的assert宏中使用堆栈跟踪来捕获开发人员的错误 - 当捕获错误时,打印堆栈跟踪.
我发现gcc的配对backtrace()/ backtrace_symbols()方法不足:
第一个问题可以通过abi :: __ cxa_demangle来解决.
然而,第二个问题更加艰难.我发现了替换backtrace_symbols().这比gcc的backtrace_symbols()更好,因为它可以检索行号(如果使用-g编译),并且不需要使用-rdynamic进行编译.
Hoverer代码是GNU许可的,所以恕我直言我不能在商业代码中使用它.
任何建议?
PS
gdb能够打印传递给函数的参数.可能已经要求太多了:)
PS 2
类似的问题(感谢nobar)
nob*_*bar 37
因此,您需要一个独立的函数来打印堆栈跟踪,其中包含gdb堆栈跟踪所具有的所有功能,并且不会终止您的应用程序.答案是在非交互模式下自动启动gdb,以执行您想要的任务.
这是通过使用fork()在子进程中执行gdb,并在应用程序等待它完成时编写脚本来显示堆栈跟踪来完成的.这可以在不使用核心转储且不中止应用程序的情况下执行.我从这个问题中学到了如何做到这一点:如何从程序中调用gdb来打印它的堆栈跟踪?
使用该问题发布的示例并不像我所写的那样对我有用,所以这是我的"修复"版本(我在Ubuntu 9.04上运行它).
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.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;
int child_pid = fork();
if (!child_pid) {
dup2(2,1); // redirect output to stderr
fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
execlp("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);
}
}
Run Code Online (Sandbox Code Playgroud)
如引用的问题所示,gdb提供了您可以使用的其他选项.例如,使用"bt full"而不是"bt"会生成更详细的报告(局部变量包含在输出中).gdb的联机帮助页很轻松,但这里提供了完整的文档.
由于这是基于gdb,因此输出包括demangled名称,行号,函数参数以及可选的局部变量.此外,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
Run Code Online (Sandbox Code Playgroud)
注意:我发现这与valgrind的使用不兼容(可能是由于Valgrind使用虚拟机).当您在gdb会话中运行程序时,它也不起作用(不能将"ptrace"的第二个实例应用于进程).
kar*_*lip 28
不久前,我回答了类似的问题.您应该查看方法#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\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());
}
Run Code Online (Sandbox Code Playgroud)
此代码应编译为: 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
Run Code Online (Sandbox Code Playgroud)
nob*_*bar 11
在以下基本相同的问题上有一个强有力的讨论:如何在我的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
Run Code Online (Sandbox Code Playgroud)
崩溃后,运行您最喜欢的调试器来检查程序状态.
$ kdbg executable core
Run Code Online (Sandbox Code Playgroud)
这是一些示例输出......

也可以在命令行从核心转储中提取堆栈跟踪.
$ ( 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
Run Code Online (Sandbox Code Playgroud)
使用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
这是另一种方法.甲debug_assert()宏编程设置一个条件断点.如果您在调试器中运行,则在断言表达式为false时将触发断点 - 并且您可以分析实时堆栈(程序不会终止).如果您没有在调试器中运行,则失败的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 ) \
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();
}
Run Code Online (Sandbox Code Playgroud)
注意:有时在调试器中设置"条件断点"可能会很慢.通过以编程方式建立断点,此方法的性能应与正常的assert()相当.
注意:如上所述,这是针对Intel x86架构的 - 其他处理器可能有不同的生成断点的指令.
这是我的解决方案:
#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;
}
Run Code Online (Sandbox Code Playgroud)
输出:
/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
Run Code Online (Sandbox Code Playgroud)
“??” 和“??:0”,因为此跟踪在 libc 中,而不是在我的源中
| 归档时间: |
|
| 查看次数: |
63758 次 |
| 最近记录: |