C/C++で関数を書きました.デバッグに超便利なので,まるごと公開します.
backtraceを取得して,FILE* に出力します.出力時の書式は format の引数で変更できます.CおよびC++両方に対応しています.
C++のシンボル名は,自動で demangle(デマングル)します.demangle の実装は abi::__cxa_demangle() を使っています.
backtraceについては過去の記事を参照してください http://d.hatena.ne.jp/pyopyopyo/20061018
コード
プロトタイプ宣言
extern "C" void dump_backtrace(FILE *fp, const char *format);
です.extern "C" をつけているので C言語でも C++からでも利用できます
実装
#include <execinfo.h> #include <strings.h> #include <stdio.h> extern "C" void dump_backtrace(FILE *fp, const char *format) { void *trace[128]; int n = backtrace(trace, sizeof(trace) / sizeof(trace[0])); char **strings = backtrace_symbols(trace, n); if (!strings) { perror("backtrace_symbols() returns NULL"); return; } size_t demangled_sz = 1024; char *demangled_str = (char*)malloc(demangled_sz); if (!demangled_str) { perror("malloc() returns NULL"); return; } int j; for (j = 0; j < n; j++) { // strings[j] には "ファイル名 ( シンボル名 + オフセット) アドレス" という形式の文字列が格納されてるので // "(" から "+" の部分を探して,シンボル名を切り出す char *start = index(strings[j], '('); char *offset = index(start, '+'); if (offset - start > 1) { // シンボル名が見つかった場合は demangle する *start = '\0'; *offset = '\0'; char *symbol = (start+1); char *ret = abi::__cxa_demangle(symbol, demangled_str, &demangled_sz, NULL); if (ret) { demangled_str = ret; } else { ret = NULL; } char tmp[1024*4]; const char *tail = tmp + sizeof(tmp); char *cur = tmp; cur += snprintf(cur, tail-cur, strings[j]); cur += snprintf(cur, tail-cur, "(%s+%s)", ret?ret:"", (offset+1)); fprintf(fp, format, tmp); } else { // シンボル名が見つからない場合は,そのまま何も変換せず,出力 fprintf(fp, format, strings[j]); } } free(demangled_str); free(strings); }
使い方
使用方法は関数を呼ぶだけです.たとえばバックトレースを /dev/stderr に出力する場合は
dump_backtrace(stderr, "%s\n");
ファイルに出力する場合,たとえばファイル名が log.txt の場合は
FILE *fp = fopen("log.txt", "w"); if (fp) { dump_backtrace(fp, "%s\n"); fclose(fp); }
という感じです
abi::__cxa_demangle() について
abi::__cxa_demangle() は気をつけないとメモリリークします.
abi::__cxa_demangle() の定義は以下の通りですが
char* abi::__cxa_demangle ( const char * mangled_name, char * output_buffer, size_t * length, int * status )
ここで戻り値に注意しないと memory leak します
戻り値は
- demangle に成功した場合は realloc(output_buffer) の結果
- demangle に失敗した場合は NULL
になります.この点を考慮していないコード,たとえば以下のコードはメモリリークする場合があります
// メモリリークする可能性があるコード(その1) size_t sz = 128; char *buf = malloc(sz): abi::__cxa_demangle ("シンボル名", buf, &sz, NULL); free(buf);
このコードは,abi::__cxa_demangle ()が内部で realloc(buf) を実行し, バッファのアドレスがbufから別アドレスに変わった場合にメモリリークします.
// メモリリークする可能性があるコード(その2) size_t sz = 128; char *buf = malloc(sz): buf = abi::__cxa_demangle ("シンボル名", buf, &sz, NULL); free(buf);
abi::__cxa_demangle ()はdemangle に失敗するとNULLを返します.つまり buf がNULLに置き換わります.結果,末尾の free(buf)では mallocで確保したメモリが開放できず メモリリークします.
abi::__cxa_demangle の戻り値が NULL か 非NULLか判定するとこれらのリークを回避できます
// 正しい使い方の例 size_t sz = 128; char *buf = malloc(sz): char *ret = abi::__cxa_demangle ("シンボル名", buf, &sz, NULL); if (ret) { buf = ret; } free(buf);