linux backtrace()詳細使用說明,分析Segmentation fault
在此之前,開發eCos應用程序時,經常碰到程序掛掉后,串口打印輸出一大串讓人看不懂的數據。今天才明白,原來這些數據是程序掛掉時的堆棧幀數據(stack frame data)。
通過這些堆棧幀數據可以分析出程序當時的運行狀態和定位程序哪里出現了問題。
這就是本文要講的—
backtrace()和backtrace_symbols()函數的使用。
backtrace()和backtrace_symbols()函數
backtrace()和backtrace_symbols()函數,包括另一個函數:backtrace_symbols_fd(),它們是GNU對程序調試的擴展支持。
頭 文 件
#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);
函數描述
backtrace()函數,獲取函數調用堆棧幀數據,即回溯函數調用列表。數據將放在buffer中。參數size用來指定buffer中可以保存多少個void*元素(表示相應棧幀的地址,一個返回地址)。如果回溯的函數調用大於size,則size個函數調用地址被返回。為了取得全部的函數調用列表,應保證buffer和size足夠大。
backtrace_symbols()函數,參數buffer是從backtrace()函數獲取的數組指針,size是該數組中的元素個數(backtrace()函數的返回值)。該函數主要功能:將從backtrace()函數獲取的地址轉為描述這些地址的字符串數組。每個地址的字符串信息包含對應函數的名字、在函數內的十六進制偏移地址、以及實際的返回地址(十六進制)。需注意的是,當前,只有使用elf二進制格式的程序才能獲取函數名稱和偏移地址,此外,為支持函數名功能,可能需要添加相應的編譯鏈接選項如-rdynamic;否則,只有十六進制的返回地址能被獲取。backtrace_symbols()函數返回值是一個字符串指針,是通過malloc函數申請的空間,使用完后,調用者必需把它釋放掉。注:如果不能為字符串獲取足夠的空間,該函數的返回值為NULL。
backtrace_symbols_fd()函數,與backtrace_symbols()函數具有相同的功能,不同的是它不會給調用者返回字符串數組,而是將結果寫入文件描述符為fd的文件中,每個函數對應一行。它不會調用malloc函數,因此,它可以應用在函數調用可能失敗的情況下。
返 回 值
backtrace()函數返回通過buffer返回的地址個數,這個數目不會超過size。如果這個返回值小於size,那么所有的函數調用列表都被保存;如果等於size,那么函數調用列表可能被截斷,此時,一些最開始的函數調用沒有被返回。
成功時,backtrace_symbols()函數返回一個由malloc分配的數組;失敗時,返回NULL。
注意事項
這些函數對函數返回地址如何保存在棧內有一些假設,注意如下:
忽略幀指針(由gcc任何非零優化級別處理了)可能引起這些假設的混亂。
內聯函數沒有棧幀。
Tail-call(尾調用)優化會導致棧幀被其它調用覆蓋。
為支持函數名功能,可能需要添加相應的編譯鏈接選項如-rdynamic;否則,只有十六進制的返回地址能被獲取。
“static”函數名是不會導出的,也不會出現在函數調用列表里,即使指定了-rdynamic鏈接選項。
程序用例
/**
* \brief backtrace測試程序
*
* 編譯指令:gcc -g -rdynamic backtrace.c -o backtrace
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h> /* for signal */
#include <execinfo.h> /* for backtrace() */
#define SIZE 100
void dump(void)
{
int j, nptrs;
void *buffer[100];
char **strings;
nptrs = backtrace(buffer, SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < nptrs; j++)
printf(" [%02d] %s\n", j, strings[j]);
free(strings);
}
void handler(int signo)
{
printf("\n=========>>>catch signal %d (%s) <<<=========\n",
signo, (char *)strsignal(signo));
printf("Dump stack start...\n");
dump();
printf("Dump stack end...\n");
/* 恢復並發送信號 */
signal(signo, SIG_DFL);
raise(signo);
}
void printSigno(int signo)
{
static int i = 0;
printf("\n=========>>>catch signal %d (%s) i = %d <<<=========\n",
signo, (char *)strsignal(signo), i++);
printf("Dump stack start...\n");
dump();
printf("Dump stack end...\n");
}
void myfunc3(void)
{
/* 為SIGINT安裝信號處理函數,通過Ctrl + C發出該信號 */
signal(SIGINT, handler);
signal(SIGSEGV, handler); /* 為安裝SIGSEGV信號處理函數 */
signal(SIGUSR1, printSigno);
/* 打印當前函數調用棧 */
printf("Current function calls list is: \n");
printf("----------------------------------\n");
dump();
printf("----------------------------------\n\n");
while (1) {
;/* 在這里通過Ctrl + C發出一個SIGINT信號來結束程序的運行 */
}
}
/**
* \brief
* 使用static修飾函數,表明不導出這個符號。
* 即使用-rdynamic選項,看到的只能是個地址。
*/
static void myfunc2(void)
{
myfunc3();
}
void myfunc(int ncalls)
{
if (ncalls > 1)
myfunc(ncalls -1);
else
myfunc2();
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "%s num-calls\n", argv[0]);
exit(EXIT_FAILURE);
}
myfunc(atoi(argv[1]));
exit(EXIT_SUCCESS);
}
// ----------------------------------------------------------------------------
// End of backtrace.c
編譯指令:gcc -g -rdynamic backtrace.c -o backtrace
運行結果:
reille@ubuntu:backtrace$ ./backtrace 3
Current function calls list is:
———————————-
backtrace() returned 9 addresses
[00] ./backtrace(dump+0x1f) [0x8048943]
[01] ./backtrace(myfunc3+0x4b) [0x8048a88]
[02] ./backtrace [0x8048aa1]
[03] ./backtrace(myfunc+0x21) [0x8048ac4]
[04] ./backtrace(myfunc+0x1a) [0x8048abd]
[05] ./backtrace(myfunc+0x1a) [0x8048abd]
[06] ./backtrace(main+0x52) [0x8048b18]
[07] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x644b56]
[08] ./backtrace [0x8048891]
———————————-
^C (按鈕Ctrl + C鍵)
=========>>>catch signal 2 (Interrupt) <<<=========
Dump stack start…
backtrace() returned 10 addresses
[00] ./backtrace(dump+0x1f) [0x8048943]
[01] ./backtrace(handler+0x3c) [0x8048a11]
[02] [0x1f4400]
[03] ./backtrace [0x8048aa1]
[04] ./backtrace(myfunc+0x21) [0x8048ac4]
[05] ./backtrace(myfunc+0x1a) [0x8048abd]
[06] ./backtrace(myfunc+0x1a) [0x8048abd]
[07] ./backtrace(main+0x52) [0x8048b18]
[08] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x644b56]
[09] ./backtrace [0x8048891]
Dump stack end…
reille@ubuntu:backtrace$
reille@ubuntu:backtrace$
分析Segmentation fault
對於Segmentation fault錯誤,有多種分析定位方法,如利用linux產生的core文件、使用gdb進行分析定位。但對於一直運行的程序,特別是大型程序,當意外出現Segmentation fault錯誤時,其分析定位,則比較棘手。
我們知道,產生Segmentation fault錯誤時,一般會產生一個SIGSEGV信號。利用這個機制,上述問題傳統的做法是,在程序中安裝SIGSEGV信號,然后在該信號處理函數中,回溯函數調用列表,從而分析定位錯誤,一勞永逸。
上面的程序用例中,已安裝了SIGSEGV信號。
