linux back trace印刷関数を使用してスタックを呼び出し、セグメントエラーをキャプチャ
5161 ワード
大規模なプロジェクト開発では、現在の関数が呼び出されたセグメントエラーやクエリが頻繁に発生します.コードチェック手段に時間がかかりすぎて、次は関数ライブラリを利用してセグメントエラー時刻の関数呼び出しスタックを印刷し、コード異常点を見つけやすい.
glibcヘッダファイル「execinfo.h」に、現在のスレッドを取得するための3つの関数呼び出しスタックが宣言されています.
この関数は、現在のスレッドの呼び出しスタックを取得するために使用され、取得された情報はbufferに格納され、ポインタリストです.パラメータsizeはbufferにどれだけのvoid*要素を保存できるかを指定します.関数の戻り値は実際に取得したポインタの数であり、最大sizeサイズを超えないbufferのポインタは実際にスタックから取得した戻りアドレスであり、各スタックフレームワークには戻りアドレスがある.注意:一部のコンパイラの最適化オプションは正しい呼び出しスタックの取得に干渉し、また、インライン関数にはスタックフレームワークがない.フレームポインタを削除すると、スタックの内容が正しく解析されません.
backtrace_Symbolsはbacktrace関数から取得した情報を文字列配列に変換する.パラメータbufferはbacktrace関数から取得するポインタ配列であるべきであり、sizeはその配列中の要素個数(backtraceの戻り値)関数の戻り値であり、bufferと同じ大きさの文字列配列を指すポインタである.各文字列には、buffer内の対応する要素に対する印刷可能な情報が含む.関数名、関数のオフセットアドレス、および実際の戻りアドレスを含む現在、ELFバイナリフォーマットを用いたプログラムのみが関数名およびオフセットアドレスを取得できる.他のシステムでは、16進数の戻りアドレスしか取得できない.また、関数名機能をサポートするために、対応するシンボルをリンクに渡す必要がある場合があります(たとえば、GNU ldリンクを使用するシステムでは、(-rdynamic)、-rdynamicは、すべてのシンボルをダイナミックシンボルテーブルに追加することをリンクに通知するために使用できます.リンクが-rdynamicをサポートしている場合は、追加することをお勧めします!)この関数の戻り値はmalloc関数によって申請する空間であるため、呼び出し者はfree関数を用いてポインタを解放しなければならない.注意:文字列に十分な空間関数を取得できない場合はNULLになります.
backtrace_symbols_fdとbacktrace_Symbols関数は、呼び出し者に文字列配列を返すのではなく、結果をファイル記述子fdのファイルに書き込む、各関数が1行に対応する機能を有する.malloc関数を呼び出す必要はありません.したがって、関数を呼び出すと失敗する可能性がある場合に適用されます.
コンパイルおよび実行印刷は以下の通りです.
アドレス0 x 80487 db付近に異常が発生し、以下でコード行数を取得できます.
コード内の異常ブランチをキャプチャするために、assertまたは自分でmyassertを書くことができ、問題の位置決めを容易にすることができます.
コードの実行後は次のようになります.
glibcヘッダファイル「execinfo.h」に、現在のスレッドを取得するための3つの関数呼び出しスタックが宣言されています.
int backtrace(void **buffer,int size)
この関数は、現在のスレッドの呼び出しスタックを取得するために使用され、取得された情報はbufferに格納され、ポインタリストです.パラメータsizeはbufferにどれだけのvoid*要素を保存できるかを指定します.関数の戻り値は実際に取得したポインタの数であり、最大sizeサイズを超えないbufferのポインタは実際にスタックから取得した戻りアドレスであり、各スタックフレームワークには戻りアドレスがある.注意:一部のコンパイラの最適化オプションは正しい呼び出しスタックの取得に干渉し、また、インライン関数にはスタックフレームワークがない.フレームポインタを削除すると、スタックの内容が正しく解析されません.
char ** backtrace_symbols (void *const *buffer, int size)
backtrace_Symbolsはbacktrace関数から取得した情報を文字列配列に変換する.パラメータbufferはbacktrace関数から取得するポインタ配列であるべきであり、sizeはその配列中の要素個数(backtraceの戻り値)関数の戻り値であり、bufferと同じ大きさの文字列配列を指すポインタである.各文字列には、buffer内の対応する要素に対する印刷可能な情報が含む.関数名、関数のオフセットアドレス、および実際の戻りアドレスを含む現在、ELFバイナリフォーマットを用いたプログラムのみが関数名およびオフセットアドレスを取得できる.他のシステムでは、16進数の戻りアドレスしか取得できない.また、関数名機能をサポートするために、対応するシンボルをリンクに渡す必要がある場合があります(たとえば、GNU ldリンクを使用するシステムでは、(-rdynamic)、-rdynamicは、すべてのシンボルをダイナミックシンボルテーブルに追加することをリンクに通知するために使用できます.リンクが-rdynamicをサポートしている場合は、追加することをお勧めします!)この関数の戻り値はmalloc関数によって申請する空間であるため、呼び出し者はfree関数を用いてポインタを解放しなければならない.注意:文字列に十分な空間関数を取得できない場合はNULLになります.
void backtrace_symbols_fd (void *const *buffer, int size, int fd)
backtrace_symbols_fdとbacktrace_Symbols関数は、呼び出し者に文字列配列を返すのではなく、結果をファイル記述子fdのファイルに書き込む、各関数が1行に対応する機能を有する.malloc関数を呼び出す必要はありません.したがって、関数を呼び出すと失敗する可能性がある場合に適用されます.
/*************************************************************************
> File Name: backtrace.c
> Author: jinshaohui
> Mail: [email protected]
> Time: 18-10-07
> Desc:
************************************************************************/
#include
#include
#include
#include
#include
void dump(int signo)
{
void *buffer[30] = {0};
size_t size = 0;
size_t i = 0;
char ** strings = NULL;
size = backtrace(buffer,30);
strings = backtrace_symbols(buffer,size);
if(strings == NULL)
{
return;
}
for (i = 0 ; i< size; i++)
{
printf("%s
",strings[i]);
}
free(strings);
exit(0);
}
void trace_3()
{
int * p = NULL;
*p = 2;
}
void trace_2()
{
trace_3();
}
void trace_1()
{
trace_2();
}
int main()
{
signal(SIGSEGV,dump);
trace_1();
return;
}
コンパイルおよび実行印刷は以下の通りです.
# -g addr2line
[jsh@localhost work]$ gcc -g -rdynamic backtrace.c
[jsh@localhost work]$ ./a.out
./a.out(dump+0x4c) [0x8048750]
[0xb00400]
./a.out(trace_2+0x8) [**0x80487db**]
./a.out(trace_1+0x8) [0x80487e5]
./a.out(main+0x22) [0x8048809]
/lib/libc.so.6(__libc_start_main+0xe6) [0x209ce6]
./a.out() [0x8048671]
アドレス0 x 80487 db付近に異常が発生し、以下でコード行数を取得できます.
[jsh@localhost work]$ addr2line 0x80487db -f a.out
trace_2
/home/jsh/workspace/Linux-C-C--learning/C/work/backtrace.c:48
コード内の異常ブランチをキャプチャするために、assertまたは自分でmyassertを書くことができ、問題の位置決めを容易にすることができます.
/*************************************************************************
> File Name: backtrace.c
> Author: jinshaohui
> Mail: [email protected]
> Time: 18-10-07
> Desc:
************************************************************************/
#include
#include
#include
#include
#include
#include
#define myassert(flg)\
do{\
int pid = getpid();\
if(!flg)\
{\
printf("\r
file:%s,Line:%d,fuc:%s\r
",__FILE__,__LINE__,__FUNCTION__);\
kill(pid,SIGUSR1);\
}\
}while(0)
void dump(int signo)
{
void *buffer[30] = {0};
size_t size = 0;
size_t i = 0;
char ** strings = NULL;
size = backtrace(buffer,30);
strings = backtrace_symbols(buffer,size);
if(strings == NULL)
{
return;
}
for (i = 0 ; i< size; i++)
{
printf("%s
",strings[i]);
}
free(strings);
//exit(0);
}
void trace_3()
{
int * p = NULL;
myassert((p != NULL));
}
void trace_2()
{
trace_3();
}
void trace_1()
{
trace_2();
}
int main()
{
char c = 0;
/* myassert */
signal(SIGUSR1,dump);
/* assert */
signal(SIGABRT,dump);
trace_1();
c = getchar();
assert(0);
return;
}
コードの実行後は次のようになります.
[jsh@localhost work]$ gcc backtrace.c -g -rdynamic
[jsh@localhost work]$
[jsh@localhost work]$ ./a.out
file:backtrace.c,Line:52,fuc:trace_3
./a.out(dump+0x4c) [0x8048820]
[0xac9400]
./a.out(trace_2+0xb) [0x80488e1]
./a.out(trace_1+0xb) [0x80488ee]
./a.out(main+0x3b) [0x804892b]
/lib/libc.so.6(__libc_start_main+0xe6) [0x182ce6]
./a.out() [0x8048741]
a.out: backtrace.c:77: main: Assertion `0' failed.
./a.out(dump+0x4c) [0x8048820]
[0xac9400]
/lib/libc.so.6(abort+0x17a) [0x1983aa]
/lib/libc.so.6(-0xff52f215) [0x18fdeb]
/lib/libc.so.6(-0xff52f15a) [0x18fea6]
./a.out() [0x8048958]
/lib/libc.so.6(__libc_start_main+0xe6) [0x182ce6]
./a.out() [0x8048741]
Aborted (core dumped)