スタック トレースを出力するスタンドアロン関数が必要な場合 gdb スタック トレースが持つすべての機能を備えており、アプリケーションを終了しません。答えは、非対話モードで gdb の起動を自動化し、必要なタスクだけを実行することです。
これは、fork() を使用して子プロセスで gdb を実行し、アプリケーションが完了するのを待っている間にスタック トレースを表示するスクリプトを作成することによって行われます。これは、コア ダンプを使用せずに、またアプリケーションを中止せずに実行できます。この質問を見て、これを行う方法を学びました:How it's better to invoke gdb from program to print it's stacktrace?
その質問とともに投稿された例は、書かれているとおりには機能しませんでした。そのため、ここに私の「修正済み」バージョンを示します (これを 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" の代わりに "bt full" を使用すると、さらに詳細なレポートが生成されます (ローカル変数が出力に含まれます)。 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\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());
}
このコードは次のようにコンパイルする必要があります: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