Linux backtrace()系列函數


backtrace()系列函數

backtrace()系列函數有3個:backtrace,backtrace_symbols,backtrace_symbols_fd。主要用於應用程序反調試(self-debugging)。

參見man 3 BACKTRACE,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);

backtrace()

backtrace() 返回調用程序的回溯(跟蹤)信息,存儲在由buffer指向的數組中。對於特定程序,backtrace就是一系列當前激活的函數調用(active function call)。

  • 參數
    buffer 由buffer指向的數組,每一項都是void*類型,存儲的是相應(調用函數的)棧幀的返回地址。
    size 指定存儲在buffer中的地址最大數量。

  • 返回值
    返回buffer中實際地址的數量,應當<=size。如果返回值 < size,那么完整的回溯信息被存儲;如果返回值 = size,那么它可能被截斷,最舊的棧幀可能沒有返回。

backtrace_symbols()

backtrace() 返回一組地址,backtrace_symbols()象征性地翻譯這些地址為一個描述地址的字符串數組。

  • 參數
    buffer 一個字符串數組,由backtrace()返回的buffer,每項代表一個函數地址。backtrace_symbols()會用字符串描述每個函數地址,字符串包括:函數名稱,一個16進制偏移(offset),實際的返回地址(16進制)。
    size 表明buffer中的地址個數。

  • 返回值
    成功時,返回一個指向由malloc(3)分配的array;失敗時,返回NULl。
    arrary是一個二維數組,該數組的每個元素 指向一個代表backtrace()返回的函數地址的符號信息的字符串,數組由函數內部調用malloc分配空間,必須由調用者free。
    注意:指向字符串的指針的數組,不必釋放,而且不應該釋放。應該釋放的是返回的二維數組指針。

backtrace_symbols_fd()

backtrace_symbols_fd()的參數buffer、size同backtrace_symbos(),不同之處在於,backtrace_symbols_fd()並不會返回一個字符串數組給調用者,而是將字符串寫入fd對應文件。backtrace_symbols_fd()也不會調用malloc分配二維數組空間,因此可應用於malloc可能會失敗的情形。

版本說明

backtrace,backtrace_symbols,backtrace_symbols_fd在glibc 2.1以后就提供了。

3個函數是GNU 擴展(GNU extensions),因此只能用於GNU gcc/g++系列編譯器。

應用示例

用backtrace和backtrace_symbols,打印函數的調用棧信息。

注意:

  • backtrace的實現依賴於棧指針(fp寄存器),編譯時,任何非0優化等級(-On),或加入棧指針優化-fomit-frame-pointer參數后,將不能得到正確的程序調用棧信息。
  • backtrace_symbols的實現需要符號名稱的支持,編譯時,需要加上-rdynamic選項。

代碼如下:

// backtrace.c
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
#include <unistd.h>

using namespace std;

void myfunc3()
{
    int j, nptrs;
#define SIZE 128
    void *buffer[100];
    char **strings;

    nptrs = backtrace(buffer, SIZE);
    printf("backtrace() return %d address\n", nptrs);

    strings = backtrace_symbols(buffer, nptrs);
    if (strings == NULL) {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }

    for (j = 0; j < nptrs; j++)
        printf("%s\n", strings[j]);

    free(strings);
}

static void myfunc2()
{
    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]));

/*
    printf("main address: %p\n", main);
    printf("myfunc address: %p\n", myfunc);
    printf("myfunc2 address: %p\n", myfunc2);
    printf("myfunc3 address: %p\n", myfunc3);
*/
    exit(EXIT_SUCCESS);
    return 0;
}

這里我們用g++編譯器編譯(當然也可以用gcc編譯器)。

$ g++ -rdynamic -std=c++11 backtrace.c -o backtrace

運行結果:

$ ./backtrace 2
backtrace() return 7 address
./backtrace(_Z7myfunc3v+0x1f) [0x400a8c]
./backtrace() [0x400b45]
./backtrace(_Z6myfunci+0x25) [0x400b6c]
./backtrace(_Z6myfunci+0x1e) [0x400b65]
./backtrace(main+0x59) [0x400bc7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf5) [0x7f7170ed1f45]
./backtrace() [0x4009a9]

可以看到一共返回了7個地址,從上到下,可以推測出調用棧對應函數依次為:myfunc3、無名函數、myfunc2、myfunc1、main、__libc_start_main、無名函數。
而從$ ./backtrace 2,我們可以知道調用函數順序為:main、myfunc、myfunc、myfunc2、myfunc3。與推測的函數棧調用順序基本一致。

參考

在Linux中如何利用backtrace信息解決程序崩潰的問題 | CSDN


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM