LoginSignup
42
36

More than 5 years have passed since last update.

[Linux][C/C++] backtrace取得方法まとめ

Last updated at Posted at 2016-06-30

デバッグ時の 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のアドレスを取得できる。
引数の数値を増やすことで、更に上の呼び出し元を参照できる。

test.c
#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が発生するので注意。

存在しないスタックフレームを参照した場合(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() を使用して、バックトレースの出力を行う例。

test.cpp
// 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言語でバックトレース(スタックトレース)
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のブログ

42
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
36