デバッグ時の backtrace 取得方法のまとめ
LD_PRELOAD
を利用すれば、対象の再ビルドを行わずに後から任意の箇所にbacktraceを埋め込むこともできる。
gdb を使用する
gdb の使用方法になるので省略
libSegFault.so を使用する
プログラム中で、SEGVが発生するときのみ使用可。
$ LD_PRELOAD=/lib/libSegFault.so hoge
# or
$ catchsegv hoge
__builtin_return_address()
を使用する
__builtin_return_address()
を利用することで、callerのアドレスを取得できる。
引数の数値を増やすことで、更に上の呼び出し元を参照できる。
#include <stdio.h>
#define __USE_GNU
#include <dlfcn.h>
void hoge() {
Dl_info info;
dladdr(__builtin_return_address(0), &info);
fprintf(stderr, "%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(0));
fprintf(stderr, "%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
fprintf(stderr, "%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
fprintf(stderr, "%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
fprintf(stderr, "%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);
dladdr(__builtin_return_address(1), &info);
fprintf(stderr, "%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(1));
fprintf(stderr, "%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
fprintf(stderr, "%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
fprintf(stderr, "%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
fprintf(stderr, "%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);
}
int main(int argc, char const* argv[])
{
hoge();
Dl_info info;
dladdr(__builtin_return_address(0), &info);
fprintf(stderr, "%s : __builtin_return_address => %p\n", __func__, __builtin_return_address(0));
fprintf(stderr, "%s : Dl_info.dli_fname => %s\n", __func__, info.dli_fname);
fprintf(stderr, "%s : Dl_info.dli_fbase => %p\n", __func__, info.dli_fbase);
fprintf(stderr, "%s : Dl_info.dli_sname => %s\n", __func__, info.dli_sname);
fprintf(stderr, "%s : Dl_info.dli_saddr => %p\n", __func__, info.dli_saddr);
return 0;
}
$ gcc test.c -ldl -rdynamic && ./a.out
hoge : __builtin_return_address => 0x4007d7
hoge : Dl_info.dli_fname => ./a.out
hoge : Dl_info.dli_fbase => 0x400000
hoge : Dl_info.dli_sname => (null)
hoge : Dl_info.dli_saddr => (nil)
hoge : __builtin_return_address => 0x7f8889a6c76d
hoge : Dl_info.dli_fname => /lib/x86_64-linux-gnu/libc.so.6
hoge : Dl_info.dli_fbase => 0x7f8889a4b000
hoge : Dl_info.dli_sname => __libc_start_main
hoge : Dl_info.dli_saddr => 0x7f8889a6c680
main : __builtin_return_address => 0x7f8889a6c76d
main : Dl_info.dli_fname => /lib/x86_64-linux-gnu/libc.so.6
main : Dl_info.dli_fbase => 0x7f8889a4b000
main : Dl_info.dli_sname => __libc_start_main
main : Dl_info.dli_saddr => 0x7f8889a6c680
関連: [Linux][C/C++]関数の復帰アドレス値と呼び出し元の関数名を取得する方法 - Qiita
存在しないスタックフレームを参照した場合(SEGVが発生)
この方法だと、数値を増やして遡っていった場合に存在しないアドレスにアクセスするとSEGVが発生するので注意。
#include <stdio.h>
int main(int argc, char const* argv[])
{
printf("%p\n", __builtin_return_address(0));
printf("%p\n", __builtin_return_address(1));
return 0;
}
$ gcc test.c && ./a.out
0x7f6c9450076d
Segmentation fault (core dumped)
glibc backtrace を使用する
glibcの backtrace()
, backtrace_symbols()
を使用して、バックトレースの出力を行う例。
// backtrace, backtrace_symbols
#include <execinfo.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
using RetVec = std::vector<std::string>;
RetVec my_backtrace() {
auto trace_size = 10;
void* trace[trace_size];
auto size = backtrace(trace, trace_size);
auto symbols = backtrace_symbols(trace, size);
RetVec result(symbols, symbols + size);
free(symbols);
return result;
}
RetVec hoge2() {
return my_backtrace();
}
RetVec hoge1() {
return hoge2();
}
int main(int argc, char const* argv[])
{
auto vec = hoge1();
int count = vec.size();
for (auto v : vec) {
count--;
fprintf(stderr, "%s : backtrace [%d] %s\n",
__func__,
count,
v.c_str()
);
}
return 0;
}
$ g++ -g -std=c++11 -o test test.cpp -rdynamic
$ ./test
main : backtrace [5] ./test(_Z12my_backtracev+0x97) [0x4021ff]
main : backtrace [4] ./test(_Z5hoge2v+0x18) [0x4022ac]
main : backtrace [3] ./test(_Z5hoge1v+0x18) [0x4022ca]
main : backtrace [2] ./test(main+0x1c) [0x4022ec]
main : backtrace [1] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7fc27153076d]
main : backtrace [0] ./test() [0x402079]
libunwind を使用する
libunwindを利用することでもbacktraceを取得することができる。
(libunwindはコールチェインを制御するライブラリ、『BINARY HACKS #73』参照)
ARMボード上で、backtrace()
がうまく動作しないケースがあったため、こちらでも実装を行った。
// backtrace, backtrace_symbols
#include <execinfo.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <dlfcn.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
void my_backtrace() {
unw_cursor_t cursor;
unw_context_t context;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
auto count = 0;
do {
unw_word_t offset, pc;
char fname[64];
unw_get_reg(&cursor, UNW_REG_IP, &pc);
fname[0] = '\0';
(void) unw_get_proc_name(&cursor, fname, sizeof(fname), &offset);
Dl_info info;
dladdr((void*)pc, &info);
fprintf(stderr, "%s : backtrace [%d] %s(%s+0x%lx) [%p]\n",
__func__,
count,
info.dli_fname,
fname,
offset,
(void*)pc
);
count++;
} while (unw_step(&cursor) > 0);
}
void hoge2() {
my_backtrace();
}
void hoge1() {
hoge2();
}
int main(int argc, char const* argv[])
{
hoge1();
return 0;
}
$ g++ -g -std=c++11 -o test test.cpp -rdynamic -lunwind -ldl
$ ./test
my_backtrace : backtrace [0] ./test(_Z12my_backtracev+0x29) [0x400c81]
my_backtrace : backtrace [1] ./test(_Z5hoge2v+0x9) [0x400d92]
my_backtrace : backtrace [2] ./test(_Z5hoge1v+0x9) [0x400d9d]
my_backtrace : backtrace [3] ./test(main+0x14) [0x400db3]
my_backtrace : backtrace [4] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7f12a138d76d]
my_backtrace : backtrace [5] ./test(_start+0x29) [0x400b69]
書籍
Binary Hacks に、上記の例の解説がいろいろあります。
参考
backtrace()
The GNU C Library: Backtraces
Return Address - Using the GNU Compiler Collection (GCC)
[Tomorrow is always fresh with no mistake in it.@備忘録 - C言語でバックトレース(スタックトレース)]
(http://www35.atwiki.jp/futoyama/pages/101.html)
C++でバックトレースを表示する - メモの日々(2010-10-15)
How to generate a stacktrace when my gcc C++ app crashes - Stack Overflow
libunwind
libunwind documentation
c - GCC return address of calling function in ARM architecture - Stack Overflow
Coding Relic: Pre-mortem Backtracing
ARM Linux環境でptrace使ってバックトレース - crimsonwoodsのブログ