独自のassert
のようなマクロでスタックトレースを使用して、開発者のミスをキャッチします。エラーがキャッチされると、スタックトレースが出力されます。
Gccのペアbacktrace()
/backtrace_symbols()
メソッドが不十分であることがわかりました。
最初の問題は abi :: __ cxa_demangle で解決できます。
ただし、2番目の問題はより困難です。 backtrace_symbols()の置き換え が見つかりました。これは、gccのbacktrace_symbols()よりも優れています。これは、(-gでコンパイルされた場合)行番号を取得でき、-rdynamicでコンパイルする必要がないためです。
ホバーコードはGNUであるため、私見では商用コードでは使用できません。
提案はありますか?
PS
gdbは、関数に渡された引数を出力できます。おそらくそれは頼むには多すぎます:)
PS 2
同様の質問 (ありがとうnobar)
それほど前ではありません 同様の質問に答えました 。メソッド#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
したがって、スタックトレースを出力するスタンドアロン関数が必要ですgdbスタックトレースが持つすべての機能を備えており、アプリケーションを終了しません。答えは、非対話モードでのgdbの起動を自動化して、必要なタスクのみを実行することです。
これは、fork()を使用してgdbを子プロセスで実行し、アプリケーションがスクリプトの完了を待機している間にスタックトレースを表示するようにスクリプトを作成することによって行われます。これは、コアダンプを使用せずに、アプリケーションを中断せずに実行できます。この質問を見て、これを行う方法を学びました: スタックトレースを出力するには、プログラムからgdbを呼び出す方がよいでしょうか?
その質問で投稿された例は、書かれたとおり正確に機能しなかったので、ここに私の「修正済み」バージョンがあります(Ubuntu 9.04でこれを実行しました)。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.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;
int child_pid = fork();
if (!child_pid) {
dup2(2,1); // redirect output to stderr
fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
execlp("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のマンページは軽いものですが、完全なドキュメントが利用可能です here 。
これは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番目のインスタンスを適用できません)。
gcc C++アプリがクラッシュしたときにスタックトレースを生成する方法 で、本質的に同じ質問に関する堅牢な議論があります。実行時にスタックトレースを生成する方法に関する多くの議論を含む、多くの提案が提供されています。
私の個人的なお気に入りの答え そのスレッドから有効にすることでした コアダンプ whichクラッシュ時の完全なアプリケーション状態を表示できます(関数の引数、行番号、マングルされていない名前を含む)。このアプローチのもう1つの利点は、assertsだけでなく、セグメンテーションフォールトおよび未処理の例外でも機能することです。
異なるLinuxシェルは異なるコマンドを使用してコアダンプを有効にしますが、次のような方法でアプリケーションコード内から実行できます。
#include <sys/resource.h>
...
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
assert( setrlimit( RLIMIT_CORE, &core_limit ) == 0 ); // enable core dumps for debug builds
クラッシュ後、お気に入りのデバッガーを実行してプログラムの状態を調べます。
$ kdbg executable core
以下に出力例を示します...
コマンドラインでコアダンプからスタックトレースを抽出することもできます。
$ ( CMDFILE=$(mktemp); echo "bt" >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} temp.exe core )
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 22857]
#0 0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#0 0x00007f4189be5fb5 in raise () from /lib/libc.so.6
#1 0x00007f4189be7bc3 in abort () from /lib/libc.so.6
#2 0x00007f4189bdef09 in __assert_fail () from /lib/libc.so.6
#3 0x00000000004007e8 in recursive (i=5) at ./demo1.cpp:18
#4 0x00000000004007f3 in recursive (i=4) at ./demo1.cpp:19
#5 0x00000000004007f3 in recursive (i=3) at ./demo1.cpp:19
#6 0x00000000004007f3 in recursive (i=2) at ./demo1.cpp:19
#7 0x00000000004007f3 in recursive (i=1) at ./demo1.cpp:19
#8 0x00000000004007f3 in recursive (i=0) at ./demo1.cpp:19
#9 0x0000000000400849 in main (argc=1, argv=0x7fff2483bd98) at ./demo1.cpp:26
Google glogライブラリを使用してください。新しいBSDライセンスがあります。
Stacktrace.hファイルにGetStackTrace関数が含まれています。
[〜#〜] edit [〜#〜]
私はここで見つけました http://blog.bigpixel.ro/2010/09/09/stack-unwinding-stack-trace-with-gcc/ プログラムのアドレスを変換するaddr2lineというユーティリティがありますファイル名と行番号。
GPLライセンスコードは開発中に役立つことを目的としているため、最終製品に含めることはできません。 GPLでは、非GPL互換コードにリンクされたGPLライセンスコードの配布を制限しています。社内でGPLコードのみを使用している限り、問題ありません。
これが代替アプローチです。 debug_assert()マクロは、プログラムで条件付きブレークポイントを設定します。デバッガーで実行している場合、アサート式がfalseのときにブレークポイントにヒットします-およびライブスタックを分析できます(プログラムは終了しません)。デバッガーで実行していない場合、debug_assert()が失敗するとプログラムが中断し、スタックを分析できるコアダンプが得られます(以前の回答を参照)。
通常のアサートと比較して、このアプローチの利点は、debug_assertがトリガーされた後(デバッガーで実行中)にプログラムを実行し続けることができることです。つまり、debug_assert()はassert()よりもわずかに柔軟性があります。
#include <iostream>
#include <cassert>
#include <sys/resource.h>
// note: The assert expression should show up in
// stack trace as parameter to this function
void debug_breakpoint( char const * expression )
{
asm("int3"); // x86 specific
}
#ifdef NDEBUG
#define debug_assert( expression )
#else
// creates a conditional breakpoint
#define debug_assert( expression ) \
do { if ( !(expression) ) debug_breakpoint( #expression ); } while (0)
#endif
void recursive( int i=0 )
{
debug_assert( i < 5 );
if ( i < 10 ) recursive(i+1);
}
int main( int argc, char * argv[] )
{
rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
recursive();
}
注:デバッガー内での「条件付きブレークポイント」のセットアップが遅い場合があります。プログラムでブレークポイントを確立することにより、このメソッドのパフォーマンスは通常のassert()のパフォーマンスと同等になります。
注:書かれているように、これはIntel x86アーキテクチャに固有です-他のプロセッサにはブレークポイントを生成するための異なる命令がある場合があります。
少し遅れますが、refdbgが symsnarf.c で行うように、 libbfb を使用してファイル名と行番号を取得できます。 libbfbはaddr2line
およびgdb
によって内部的に使用されます
解決策の1つは、失敗したアサートハンドラの「bt」スクリプトでgdbを起動することです。そのようなgdb-startingを統合することは非常に簡単ではありませんが、バックトレースと引数の両方を提供し、名前をデマングルします(またはc ++ filtプログラムを介してgdb出力を渡すことができます)。
両方のプログラム(gdbとc ++ filt)はアプリケーションにリンクされないため、GPLではアプリケーション全体をオープンソース化する必要はありません。
Backtrace-symbolsで使用できる同じアプローチ(GPLプログラムを実行)。 %eipのASCIIリストとexecファイルのマップ(/ proc/self/maps)を生成し、別のバイナリに渡すだけです。
DeathHandler -信頼できるすべての処理を行う小さなC++クラスを使用できます。
行番号は現在のeip値に関連していると思いますか?
解決策1:
次に、Linuxで作業していることを除いて、 GetThreadContext() のようなものを使用できます。私は少しグーグルで検索して、似たようなものを見つけました ptrace() :
Ptrace()システムコールは、親プロセスが別のプロセスの実行を監視および制御し、そのコアイメージとレジスタを調べて変更する手段を提供します。 [...]親は、fork(2)を呼び出し、結果の子にPTRACE_TRACEMEを実行させ、その後(通常)exec(3)を実行することにより、トレースを開始できます。または、親はPTRACE_ATTACHを使用して既存のプロセスのトレースを開始できます。
今、私は考えていました、あなたはその子、あなたが取り組んでいる実際のプログラムに送信されるシグナルをチェックする「メイン」プログラムを行うことができます。 fork()
の後に waitid() を呼び出します:
これらのシステムコールはすべて、呼び出しプロセスの子で状態の変化を待機し、状態が変化した子に関する情報を取得するために使用されます。
また、SIGSEGV(または同様のもの)がキャッチされた場合は、ptrace()
を呼び出してeip
の値を取得します。
PS:これらのシステムコールを使用したことはありません(実際、これまでに見たことはありません;)ので、それが可能かどうかはわかりません。少なくともこれらのリンクが役に立つことを願っています。 ;)
解決策2:最初の解決策はかなり複雑ですよね? signal() を使用して、関心のあるシグナルをキャッチし、スタックに格納されているeip
値を読み取る単純な関数を呼び出します。
...
signal(SIGSEGV, sig_handler);
...
void sig_handler(int signum)
{
int eip_value;
asm {
Push eax;
mov eax, [ebp - 4]
mov eip_value, eax
pop eax
}
// now you have the address of the
// **next** instruction after the
// SIGSEGV was received
}
そのasm構文はBorlandのものであり、GAS
に適合させてください。 ;)
ここに私の3番目の答えがあります-まだコアダンプを利用しようとしています。
「assert-like」マクロがアプリケーションを終了する(assertのように)のか、スタックトレースを生成した後も実行を継続するのかは、完全には明らかではありませんでした。
この回答では、スタックトレースを表示して実行を継続したい場合に対処しています。コアダンプを生成するために、以下にcoredump()関数を作成し、そこからスタックトレースを自動的に抽出してから、プログラムの実行を続けます。
使用方法は、assert()と同じです。もちろん違いは、assert()はプログラムを終了しますが、coredump_assert()は終了しないことです。
#include <iostream>
#include <sys/resource.h>
#include <cstdio>
#include <cstdlib>
#include <boost/lexical_cast.hpp>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
std::string exename;
// expression argument is for diagnostic purposes (shows up in call-stack)
void coredump( char const * expression )
{
pid_t childpid = fork();
if ( childpid == 0 ) // child process generates core dump
{
rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit( RLIMIT_CORE, &core_limit ); // enable core dumps
abort(); // terminate child process and generate core dump
}
// give each core-file a unique name
if ( childpid > 0 ) waitpid( childpid, 0, 0 );
static int count=0;
using std::string;
string pid = boost::lexical_cast<string>(getpid());
string newcorename = "core-"+boost::lexical_cast<string>(count++)+"."+pid;
string rawcorename = "core."+boost::lexical_cast<string>(childpid);
int rename_rval = rename(rawcorename.c_str(),newcorename.c_str()); // try with core.PID
if ( rename_rval == -1 ) rename_rval = rename("core",newcorename.c_str()); // try with just core
if ( rename_rval == -1 ) std::cerr<<"failed to capture core file\n";
#if 1 // optional: dump stack trace and delete core file
string cmd = "( CMDFILE=$(mktemp); echo 'bt' >${CMDFILE}; gdb 2>/dev/null --batch -x ${CMDFILE} "+exename+" "+newcorename+" ; unlink ${CMDFILE} )";
int system_rval = system( ("bash -c '"+cmd+"'").c_str() );
if ( system_rval == -1 ) std::cerr.flush(), perror("system() failed during stack trace"), fflush(stderr);
unlink( newcorename.c_str() );
#endif
}
#ifdef NDEBUG
#define coredump_assert( expression ) ((void)(expression))
#else
#define coredump_assert( expression ) do { if ( !(expression) ) { coredump( #expression ); } } while (0)
#endif
void recursive( int i=0 )
{
coredump_assert( i < 2 );
if ( i < 4 ) recursive(i+1);
}
int main( int argc, char * argv[] )
{
exename = argv[0]; // this is used to generate the stack trace
recursive();
}
プログラムを実行すると、3つのスタックトレースが表示されます...
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24251]
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3 0x0000000000401f5f in recursive (i=2) at ./demo3.cpp:60
#4 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#5 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#6 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24259]
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3 0x0000000000401f5f in recursive (i=3) at ./demo3.cpp:60
#4 0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#5 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#6 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#7 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
Core was generated by `./temp.exe'.
Program terminated with signal 6, Aborted.
[New process 24267]
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#0 0x00007f2818ac9fb5 in raise () from /lib/libc.so.6
#1 0x00007f2818acbbc3 in abort () from /lib/libc.so.6
#2 0x0000000000401a0e in coredump (expression=0x403303 "i < 2") at ./demo3.cpp:29
#3 0x0000000000401f5f in recursive (i=4) at ./demo3.cpp:60
#4 0x0000000000401f70 in recursive (i=3) at ./demo3.cpp:61
#5 0x0000000000401f70 in recursive (i=2) at ./demo3.cpp:61
#6 0x0000000000401f70 in recursive (i=1) at ./demo3.cpp:61
#7 0x0000000000401f70 in recursive (i=0) at ./demo3.cpp:61
#8 0x0000000000401f8b in main (argc=1, argv=0x7fffc229eb98) at ./demo3.cpp:66
AFAICSは、これまでに提供されたすべてのソリューションで、共有ライブラリの関数名と行番号を出力しません。それが必要だったので、/ proc/id/mapsを使用して共有ライブラリのアドレスを解決するためにkarlphillipのソリューション(および同様の質問からの他の回答)を変更しました。
#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <execinfo.h>
#include <stdbool.h>
struct Region { // one mapped file, for example a shared library
uintptr_t start;
uintptr_t end;
char* path;
};
static struct Region* getRegions(int* size) {
// parse /proc/self/maps and get list of mapped files
FILE* file;
int allocated = 10;
*size = 0;
struct Region* res;
uintptr_t regionStart = 0x00000000;
uintptr_t regionEnd = 0x00000000;
char* regionPath = "";
uintmax_t matchedStart;
uintmax_t matchedEnd;
char* matchedPath;
res = (struct Region*)malloc(sizeof(struct Region) * allocated);
file = fopen("/proc/self/maps", "r");
while (!feof(file)) {
fscanf(file, "%jx-%jx %*s %*s %*s %*s%*[ ]%m[^\n]\n", &matchedStart, &matchedEnd, &matchedPath);
bool bothNull = matchedPath == 0x0 && regionPath == 0x0;
bool similar = matchedPath && regionPath && !strcmp(matchedPath, regionPath);
if(bothNull || similar) {
free(matchedPath);
regionEnd = matchedEnd;
} else {
if(*size == allocated) {
allocated *= 2;
res = (struct Region*)realloc(res, sizeof(struct Region) * allocated);
}
res[*size].start = regionStart;
res[*size].end = regionEnd;
res[*size].path = regionPath;
(*size)++;
regionStart = matchedStart;
regionEnd = matchedEnd;
regionPath = matchedPath;
}
}
return res;
}
struct SemiResolvedAddress {
char* path;
uintptr_t offset;
};
static struct SemiResolvedAddress semiResolve(struct Region* regions, int regionsNum, uintptr_t address) {
// convert address from our address space to
// address suitable fo addr2line
struct Region* region;
struct SemiResolvedAddress res = {"", address};
for(region = regions; region < regions+regionsNum; region++) {
if(address >= region->start && address < region->end) {
res.path = region->path;
res.offset = address - region->start;
}
}
return res;
}
void printStacktraceWithLines(unsigned int max_frames)
{
int regionsNum;
fprintf(stderr, "stack trace:\n");
// storage array for stack trace address data
void* addrlist[max_frames+1];
// retrieve current stack addresses
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
if (addrlen == 0) {
fprintf(stderr, " <empty, possibly corrupt>\n");
return;
}
struct Region* regions = getRegions(®ionsNum);
for (int i = 1; i < addrlen; i++)
{
struct SemiResolvedAddress hres =
semiResolve(regions, regionsNum, (uintptr_t)(addrlist[i]));
char syscom[256];
sprintf(syscom, "addr2line -C -f -p -a -e %s 0x%jx", hres.path, (intmax_t)(hres.offset));
system(syscom);
}
free(regions);
}
ここに私の解決策があります:
#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <zconf.h>
#include "regex"
std::string getexepath() {
char result[PATH_MAX];
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
return std::string(result, (count > 0) ? count : 0);
}
std::string sh(std::string cmd) {
std::array<char, 128> buffer;
std::string result;
std::shared_ptr<FILE> pipe(popen(cmd.c_str(), "r"), pclose);
if (!pipe) throw std::runtime_error("popen() failed!");
while (!feof(pipe.get())) {
if (fgets(buffer.data(), 128, pipe.get()) != nullptr) {
result += buffer.data();
}
}
return result;
}
void print_backtrace(void) {
void *bt[1024];
int bt_size;
char **bt_syms;
int i;
bt_size = backtrace(bt, 1024);
bt_syms = backtrace_symbols(bt, bt_size);
std::regex re("\\[(.+)\\]");
auto exec_path = getexepath();
for (i = 1; i < bt_size; i++) {
std::string sym = bt_syms[i];
std::smatch ms;
if (std::regex_search(sym, ms, re)) {
std::string addr = ms[1];
std::string cmd = "addr2line -e " + exec_path + " -f -C " + addr;
auto r = sh(cmd);
std::regex re2("\\n$");
auto r2 = std::regex_replace(r, re2, "");
std::cout << r2 << std::endl;
}
}
free(bt_syms);
}
void test_m() {
print_backtrace();
}
int main() {
test_m();
return 0;
}
出力:
/home/roroco/Dropbox/c/ro-c/cmake-build-debug/ex/test_backtrace_with_line_number
test_m()
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:57
main
/home/roroco/Dropbox/c/ro-c/ex/test_backtrace_with_line_number.cpp:61
??
??:0
「??」このトレースはlibcにあり、ソースにはないので、「??:0」