Linuxcプログラムが異常終了したときにスタック呼び出し情報を出力する

まず、3つの機能を理解しましょう

#include <execinfo.h>

 int backtrace(void ** buffer 、int  size );

 char ** backtrace_symbols(void * const * buffer 、int  size );

 void backtrace_symbols_fd(void * const * buffer 、int  size 、int  fd );

int backtrace(void ** buffer、int size)
   この関数は、現在のスレッドの呼び出しスタックを取得するために使用されます。取得された情報は、ポインターの配列であるバッファーに格納されます。sizeパラメーターは、バッファーに格納できるvoid *要素の数を指定するために使用されます。関数の戻り値は実際に取得されたポインターの数であり、最大サイズはバッファー内のポインターのサイズを超えません。実際にはスタックから取得された戻りアドレスであり、各スタックフレームには戻りアドレスがあります。
    一部のコンパイラ最適化オプションは正しい呼び出しスタックの取得を妨げることに注意してください。インライン関数にはスタックフレームがありません。フレームポインタを削除すると、スタックの内容を正しく解析できなくなります
   。char** backtrace_symbols(void * const * buffer、 int size)
    backtrace_symbolsは、backtrace関数から取得した情報を文字列の配列に変換します。パラメータバッファは、backtrace関数から取得した配列ポインタである必要があり、sizeは配列内の要素の数(backtraceの戻り値)であり、関数の戻り値は文字へのポインタです。文字列の配列へのポインタ。そのサイズはバッファと同じです。各文字列には、バッファ内の対応する要素に関連する印刷可能な情報が含まれます。関数名が含まれます。関数のオフセットアドレス、および実際の戻りアドレス
注:文字列用の十分なスペースを取得できない場合、関数の戻り値はNULLになります
    現在、ELFバイナリ形式のプログラムのみを使用すると、関数名とオフセットアドレスを取得できます。他のシステムでは、16進数の戻りアドレスしか取得できません。さらに、関数名関数をサポートするには、対応するフラグをリンカーに渡す必要があります(たとえば、GNU ldを使用するシステムでは、(-rdynamic)を渡す必要があります)
backtrace_symbolsによって生成される文字列はすべてmallocですが、最後の文字列を解放しません。backtrace_symbolsはbacktraceによって指定された呼び出しスタック層番号に基づいているため、mallocはメモリのブロックを生成して、結果の文字列を一度に格納します。上記のコードは同じですが、最後に、無料のbacktrace_symbolsのリターンポインタはOKです。これは、バックトレースのマニュアルにも具体的に記載されています。
    void backtrace_symbols_fd(void * const * buffer、int size、int fd)
    backtrace_symbols_fdはbacktrace_symbols関数と同じ関数を持っていますが、違いは、呼び出し元に文字列配列を返さないが、結果をファイル記述子fdInに書き込むことです。ファイル、各関数は1行に対応します。malloc関数を呼び出す必要がないため、関数が呼び出されない可能性がある状況に適しています。

プログラムテスト:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <execinfo.h>

void out_stack(char *sig);

void signal_exit(int dunno) 
{ 
	char* signal_str = "";
	char dunno_str[10] = {0};
	sprintf(dunno_str, "%d", dunno);
	switch (dunno) 
	{
		case 1:
			signal_str = "SIGHUP(1)";
			break;
		case 2:
			signal_str = "SIGINT(2:CTRL_C)"; //CTRL_C
			break;
		case 3:
			signal_str = "SIGQUIT(3)";
			break;
		case 6:
		{
			signal_str = "SIGABRT(6)";
			out_stack(signal_str);
		}
		break;
		case 9:
			signal_str = "SIGKILL(9)";
			break;
		case 15:
			signal_str = "SIGTERM(15 KILL)"; //kill 
			break;
		case 11:
		{
			signal_str = "SIGSEGV(11)"; //SIGSEGV 
			out_stack(signal_str);
		}
		break;	
		default:
			signal_str = "OTHER";
			break;
	}
	exit(0);
}

static void output_addrline(char addr[])
{
	char cmd[256];
	char line[256];
	char addrline[32]={0,};
	char *str1, *str2;
	FILE* file;

	str1 = strchr(addr,'[');
	str2 = strchr(addr, ']');
	if(str1 == NULL || str2 == NULL)
	{
		return;
	}	
	memcpy(addrline, str1 + 1, str2 -str1);
	snprintf(cmd, sizeof(cmd), "addr2line -e /proc/%d/exe %s ", getpid(), addrline);
	file = popen(cmd, "r");
	if(NULL != fgets(line, 256, file)) 
	{
		printf("%s\n", line);
	}
	pclose(file);	
}
void out_stack(char *sig)
{
	void *array[32];
	size_t size;
	char **strings;
	int i;
	
	printf("%s\n", sig);
	size = backtrace (array, 32);
	strings = backtrace_symbols (array, size);
	if (NULL == strings)
	{
		printf("backtrace_symbols\n");
		return ;
	}
	
	for (i = 0; i < size; i++)
	{
		printf("%s",strings[i]);
		output_addrline(strings[i]);	
	}
	
	free(strings);
}
void test3(int n)
{
	char *str;
	printf("in test3 [%d]\n", n);
	strcpy(str, "123");
}
void test2(int n)
{
	printf("in test2 [%d]\n", n);
	test3(3);
}
void test1(int n)
{
	printf("in test1 [%d]\n", n);
	test2(2);
}
int main()
{
	signal(SIGHUP, signal_exit); 
	signal(SIGINT, signal_exit);
	signal(SIGQUIT, signal_exit);
	signal(SIGABRT, signal_exit);
	signal(SIGKILL, signal_exit);
	signal(SIGTERM, signal_exit);
	signal(SIGSEGV, signal_exit);
	
	test1(1);
}

コンパイル時に-gおよび-rdynamicオプションを追加する必要があることに注意してください

おすすめ

転載: blog.csdn.net/u014608280/article/details/84974877