メモの日々


2010年10月15日(金) [長年日記]

[c++] C++でバックトレースを表示する

bkブログの

で全て語られているけれど、自分で使ってみたのでメモ。環境はUbuntu 10.04。

  • バックトレースはThe GNU C Libraryにあるbacktrace()とbacktrace_symbols()を使って取得できる。マニュアルはここ
  • C++の場合、backtrace_symbols()で得られる関数名がマングルされていて読みづらい。The GNU C++ Libraryにあるabi::__cxa_demangle()を使うと元の関数名を取得できる。マニュアルはここ

サンプルプログラムと実行結果をメモ。backtrace_symbols()のマニュアルにもあるけれど、関数名を得るにはリンカオプションとして -rdynamic を指定しないとうまくいかなかった。下に示すプログラムを -rdynamic なしでビルドしたときの出力結果は

Backtrace:
???: ./a.out() [0x8048e29]
???: ./a.out() [0x8049176]
???: ./a.out() [0x8049370]
???: ./a.out() [0x804937d]
???: ./a.out() [0x804938a]
__libc_start_main [error:status=-2]: /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0xb74dabd6]
???: ./a.out() [0x8048d71]

となり、-rdynamic ありでビルドしたときの出力結果は

Backtrace:
get_backtrace10(): ./a.out(_Z15get_backtrace10v+0x25) [0x8049e99]
f3(): ./a.out(_Z2f3v+0x14) [0x804a1e6]
f2(): ./a.out(_Z2f2v+0xb) [0x804a3e0]
f1(): ./a.out(_Z2f1v+0xb) [0x804a3ed]
main [error:status=-2]: ./a.out(main+0xb) [0x804a3fa]
__libc_start_main [error:status=-2]: /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0xb75d3bd6]
???: ./a.out() [0x8049de1]

となった。

#include <cstdlib>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

// backtrace()とbacktrace_symbols()はこのヘッダ
#include <execinfo.h>

// __cxa_demangle() はこのヘッダ
#include <cxxabi.h>

using std::string;
using std::vector;

// バックトレース情報を文字列で取得する
vector<string> get_backtrace10() {
    // backtrace()でバックトレース情報を取得できる
    const int trace_size = 10;
    void* trace[trace_size];
    int size = backtrace(trace, trace_size);

    // backtrace_symbols()でバックトレース情報を文字列に変換できる
    char** symbols = backtrace_symbols(trace, size);

    vector<string> result(symbols, symbols + size);

    // symbolsをfreeする必要がある
    free(symbols);

    return result;
}

// 取得したバックトレース文字列から関数名部分を切り出す
string cut_function_name_part(const string& raw_text) {
    // '(' ~ '+' までを切り出せばいいみたい
    string::size_type left = raw_text.find("(");
    string::size_type right = raw_text.find("+", left + 1);
    return
        (left == raw_text.npos || right == raw_text.npos)
        ? ""
        : raw_text.substr(left + 1, right - left - 1);
}

// 関数名をデマングルする
string demangle_function_name(const string& mangled) {
    // __cxa_demangle()でデマングルできる
    int status = 0;
    char* demangled = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);

    // demangledをfreeする必要がある
    string result = demangled ? demangled : mangled;
    free(demangled);

    if (status != 0) {
        std::ostringstream oss;
        oss << " [error:status=" << status << "]";
        result += oss.str();
    }

    return result;
}

void f3() {
    vector<string> backtrace_texts = get_backtrace10();

    std::cout << "Backtrace:\n";

    for (vector<string>::const_iterator
            it = backtrace_texts.begin(), end = backtrace_texts.end();
            it != end;
            ++it) {
        string mangled_func_name = cut_function_name_part(*it);

        std::cout
            << ((!mangled_func_name.empty())
                    ? demangle_function_name(mangled_func_name)
                    : "???")
            << ": " << *it << "\n";
    }
}

void f2() { f3(); }

void f1() { f2(); }

int main() { f1(); }

[life] toto当選した!

第475回の3等が。当せん金はなんと250円。2口当たっていたので合わせて500円。買ったのは8口で800円だったから赤字だ。

5年前にtotoGOAL3に当たったことはあった。totoGOAL3は当せん金が少なすぎるので最近はtoto一本をほぼ毎週800円買っていた。今回は天皇杯でJ1vsJ2の試合が多くて予想は簡単だったんだよなあ。

今日もtoto買った。1億当たってくれー。

やること

  • コンタクト