1. 程式人生 > >利用backtrace和ucontex定位segment錯誤

利用backtrace和ucontex定位segment錯誤

         C程式執行時,經常會碰到”segmentfault”錯誤。這是由於程式中非法訪問記憶體導致的。當作業系統的記憶體保護機制發現程序訪問了非法記憶體的時候會向此程序傳送一個SIGSEGV訊號,導致程序直接退出,並在shell中提示segment fault。

         因此,可以通過設定SIGSEGV訊號處理函式,在處理函式中呼叫backtrace系列函式得到異常時的函式呼叫棧資訊。

一:backtrace

         backtrace系列函式的原型如下:

#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返回呼叫程式的回溯資訊,也就是所謂的函式呼叫棧。buffer陣列中的元素是void*型別,也就是棧中儲存的返回地址。

         size引數指定buffer中可以儲存的地址的最大個數。如果實際的回溯資訊大於size,則只返回最近的size個地址。

         backtrace函式返回buffer中儲存的地址個數,返回值不會大於size。如果返回值小於size,則說明所有的回溯資訊都已經返回了,如果等於size,則有可能被截斷了。

         backtrace函式在buffer陣列中返回的都是一些虛擬地址,不適於分析。backtrace_symbols函式可以將backtrace返回的buffer中的地址,根據符號表中的資訊,轉換為字串(函式名+偏移地址)。size引數指明瞭buffer中的地址個數。

backtrace_symbols返回字串陣列的首地址,該字串是在backtrace_symbols中通過malloc分配的,因此,呼叫者必須使用free釋放記憶體。如果發生了錯誤,則backtrace_symbols返回NULL。

backtrace_symbols_fd類似於backtrace_symbols,只不過它是把字串資訊寫到檔案描述符fd所表示的檔案中。backtrace_symbols_fd不會呼叫malloc函式。

注意,編譯器的優化策略,可能導致得到的回溯資訊不準確。而且,對於GUN編譯器而言,必須使用-rdynamic連結選項,才能正確解析出符號名。

二:示例

#include <signal.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>

#define BTSIZE 100

static void sig_handler(int sig, siginfo_t *info, void *secret)
{
    ucontext_t *uc = (ucontext_t*) secret;

    void *buffer[BTSIZE];
    char **strings;
        int nptrs = 0;

    printf("in sig_handler\n");
    printf("sig is %d, SIGSEGV is %d\n", sig, SIGSEGV);
    printf("info.si_signo is %d, info.si_addr is %p\n", 
            info->si_signo, info->si_addr);

    if (sig == SIGSEGV)
    {
        nptrs = backtrace(buffer, BTSIZE);
        printf("backtrace() returned %d addresses\n", nptrs);

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

        printf("backtrace: \n");
        int j = 0;
        for (j = 0; j < nptrs; j++)
        {
            printf("[%d]%s\n", j, strings[j]);
        }
        free(strings);

        exit(0);
    }
}

void fun3()
{
    int *ptr = (int *)0x123;
    printf("this is fun3\n");

    *ptr = 0;
}

void fun2()
{
    printf("this is fun2\n");
    fun3();
}

void fun1()
{
    printf("this is fun1\n");
    fun2();
}

int main()
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = sig_handler;
    sigaction(SIGSEGV, &act, NULL);

    fun1();
}

main函式中,使用sigaction設定SIGSEGV訊號的處理函式,通過SA_SIGINFO標誌,可以得到訊號發生時的額外資訊,比如引起訊號的記憶體地址等。

在fun3函式中,嘗試將記憶體地址為0x123的記憶體賦值為0,這是一個明顯的非法記憶體訪問,將導致SIGSEGV訊號的產生。

在SIGSEGV訊號處理函式sig_handler中,首先打印出引起異常的記憶體地址info->si_addr,然後呼叫backtrace和backtrace_symbols打印出棧幀。

結果如下:

[[email protected] test]# gcc -o testbacktrace testbacktrace.c 
[[email protected] test]# ./testbacktrace 
this is fun1
this is fun2
this is fun3
in sig_handler
sig is 11, SIGSEGV is 11
info.si_signo is 11, info.si_addr is 0x123
backtrace() returned 7 addresses
backtrace: 
[0]./testbacktrace [0x80485d0]
[1][0xec8440]
[2]./testbacktrace [0x80486ba]
[3]./testbacktrace [0x80486d3]
[4]./testbacktrace [0x804872e]
[5]/lib/libc.so.6(__libc_start_main+0xdc) [0xa9cedc]
[6]./testbacktrace [0x80484a1]

打印出了info.si_addr的值為0x123。並且打印出了7個地址資訊。通過objdump,對testbacktrace進行反彙編,可以得到如下資訊:

080483e8 <[email protected]>:
 80483e8:   ff 25 40 9a 04 08       jmp    *0x8049a40
 80483ee:   68 10 00 00 00          push   $0x10
 80483f3:   e9 c0 ff ff ff          jmp    80483b8 <_init+0x18>

 
08048480 <_start>:
 ...
 8048497:   68 d5 86 04 08          push   $0x80486d5
 804849c:   e8 47 ff ff ff          call   80483e8 <[email protected]>
 80484a1:   f4                        hlt    
 ...


08048554 <sig_handler>:
 ...
 80485cb:   e8 78 fe ff ff          call   8048448 <[email protected]>
 80485d0:   89 45 f8                mov    %eax,0xfffffff8(%ebp)


 
0804867f <fun3>:
 ...
 8048685:   c7 45 fc 23 01 00 00    movl   $0x123,0xfffffffc(%ebp)
 804868c:   c7 04 24 b1 88 04 08    movl   $0x80488b1,(%esp)
 8048693:   e8 c0 fd ff ff          call   8048458 <[email protected]>
 8048698:   8b 45 fc                mov    0xfffffffc(%ebp),%eax
 804869b:   c7 00 00 00 00 00       movl   $0x0,(%eax)
 80486a1:   c9                      leave  
 ...
 

080486a3 <fun2>:
 ...
 80486b0:   e8 a3 fd ff ff          call   8048458 <[email protected]>
 80486b5:   e8 c5 ff ff ff          call   804867f <fun3>
 80486ba:   c9                      leave  
 ...

 
080486bc <fun1>:
 ...
 80486c9:   e8 8a fd ff ff          call   8048458 <[email protected]>
 80486ce:   e8 d0 ff ff ff          call   80486a3 <fun2>
 80486d3:   c9                      leave  
 ...   

 
080486d5 <main>:
 ...
 8048724:   e8 ff fc ff ff          call   8048428 <[email protected]>
 8048729:   e8 8e ff ff ff          call   80486bc <fun1>
 804872e:   81 c4 a4 00 00 00       add    $0xa4,%esp
 ...   

根據上面的反彙編資訊,可知backtrace返回的7個地址資訊,都是call指令後面緊跟著的指令地址。這是因為call指令在將子程式的起始地址送入指令暫存器(於是CPU的下一條指令就會轉去執行子程式)之前,首先會將call指令的下一條指令的所在地址入棧。所以,函式呼叫時的棧內容如下:

         backtrace返回的buffer中儲存的地址,就是所有call指令後續緊跟的返回地址。

         上面的結果,因為沒有加”-rdynamic”連結選項,所以打印出來的都是虛擬地址。增加”-rdynamic”後的結果如下:

[[email protected] test]# gcc -o testbacktrace testbacktrace.c -rdynamic
[[email protected] test]# ./testbacktrace 
this is fun1
this is fun2
this is fun3
in sig_handler
sig is 11, SIGSEGV is 11
info.si_signo is 11, info.si_addr is 0x123
backtrace() returned 7 addresses
backtrace: 
[0]./testbacktrace [0x80487b0]
[1][0xda2440]
[2]./testbacktrace(fun2+0x17) [0x804889a]
[3]./testbacktrace(fun1+0x17) [0x80488b3]
[4]./testbacktrace(main+0x59) [0x804890e]
[5]/lib/libc.so.6(__libc_start_main+0xdc) [0x3daedc]
[6]./testbacktrace [0x8048681]

         這樣可以在不使用objdump的情況下,大體瞭解函式呼叫的關係了。

三:指令地址

         上面通過backtrace可以大體得到”segmentfault”錯誤時的函式呼叫棧,然而僅憑backtrace還是不能得到引起異常的指令地址(甚至連引起異常的函式也無法得到)。

         在Redis的原始碼中,看到了列印指令地址的方法。使用ucontext_t結構,打印出指令暫存器的內容。

         程式碼如下:

#include <signal.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>

#define BTSIZE 100

static void *getMcontextEip(ucontext_t *uc) {
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
    /* OSX < 10.6 */
    #if defined(__x86_64__)
    return (void*) uc->uc_mcontext->__ss.__rip;
    #elif defined(__i386__)
    return (void*) uc->uc_mcontext->__ss.__eip;
    #else
    return (void*) uc->uc_mcontext->__ss.__srr0;
    #endif
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
    /* OSX >= 10.6 */
    #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
    return (void*) uc->uc_mcontext->__ss.__rip;
    #else
    return (void*) uc->uc_mcontext->__ss.__eip;
    #endif
#elif defined(__linux__)
    /* Linux */
    #if defined(__i386__)
    return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
    #elif defined(__X86_64__) || defined(__x86_64__)
    return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
    #elif defined(__ia64__) /* Linux IA64 */
    return (void*) uc->uc_mcontext.sc_ip;
    #endif
#else
    return NULL;
#endif
}

static void sig_handler(int sig, siginfo_t *info, void *secret)
{
    ucontext_t *uc = (ucontext_t*) secret;

    void *buffer[BTSIZE];
    char **strings;
    int nptrs = 0;

    printf("in sig_handler\n");
    printf("sig is %d, SIGSEGV is %d\n", sig, SIGSEGV);
    printf("info.si_signo is %d, info.si_addr is %p\n", 
        info->si_signo, info->si_addr);

    if (sig == SIGSEGV)
    {
        nptrs = backtrace(buffer, BTSIZE);
        printf("backtrace() returned %d addresses\n", nptrs);

        if (getMcontextEip(uc) != NULL)
            buffer[1] = getMcontextEip(uc);
            
        strings = backtrace_symbols(buffer, nptrs);
        if (strings == NULL) {
            perror("backtrace_symbols");
            exit(EXIT_FAILURE);
        }

        printf("backtrace: \n");
        int j;
        for (j = 0; j < nptrs; j++)
        {
            printf("[%d]%s\n", j, strings[j]);
        }
        free(strings);

        exit(0);
    }
}

void fun3()
{
    int *ptr = (int *)0x123;
    printf("this is fun3\n");

    *ptr = 0;
}

void fun2()
{
    printf("this is fun2\n");
    fun3();
}

void fun1()
{
    printf("this is fun1\n");
    fun2();
}

int main()
{
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = sig_handler;
    sigaction(SIGSEGV, &act, NULL);

    fun1();
}

         在使用sigaction函式設定SIGSEGV訊號的處理函式時,使用SA_SIGINFO標誌,可以得到訊號發生時的更多資訊。

當訊號發生呼叫處理函式sig_handler時,傳遞給該函式的第三個引數,是一個ucontext_t型別的結構,該結構在標頭檔案ucontext.h中定義,其中包含了訊號發生時的CPU狀態,也就是所有暫存器的內容。

         函式getMcontextEip用於返回指令暫存器的內容。使用該內容,替換buffer[1]的內容。程式碼執行結果如下:

[[email protected] test]# gcc -o testbacktrace testbacktrace.c -rdynamic
[[email protected] test]# ./testbacktrace 
this is fun1
this is fun2
this is fun3
in sig_handler
sig is 11, SIGSEGV is 11
info.si_signo is 11, info.si_addr is 0x123
backtrace() returned 7 addresses
backtrace: 
[0]./testbacktrace [0x80487bb]
[1]./testbacktrace(fun3+0x1c) [0x804889f]
[2]./testbacktrace(fun2+0x17) [0x80488be]
[3]./testbacktrace(fun1+0x17) [0x80488d7]
[4]./testbacktrace(main+0x59) [0x8048932]
[5]/lib/libc.so.6(__libc_start_main+0xdc) [0xd6dedc]
[6]./testbacktrace [0x8048681]

         可以看見buffer[1]的內容已經被替換成了訊號發生時的指令暫存器內容。通過objdump,得到fun3的彙編指令如下:

08048883 <fun3>:
 8048883:   55                      push   %ebp
 8048884:   89 e5                   mov    %esp,%ebp
 8048886:   83 ec 18                sub    $0x18,%esp
 8048889:   c7 45 fc 23 01 00 00    movl   $0x123,0xfffffffc(%ebp)
 8048890:   c7 04 24 b1 8a 04 08    movl   $0x8048ab1,(%esp)
 8048897:   e8 98 fd ff ff          call   8048634 <[email protected]>
 804889c:   8b 45 fc                mov    0xfffffffc(%ebp),%eax
 804889f:   c7 00 00 00 00 00       movl   $0x0,(%eax)
 80488a5:   c9                      leave  
 80488a6:   c3                      ret  

地址0x804889f就是引起異常的指令地址。

相關推薦

利用backtraceucontex定位segment錯誤

         C程式執行時,經常會碰到”segmentfault”錯誤。這是由於程式中非法訪問記憶體導致的。當作業系統的記憶體保護機制發現程序訪問了非法記憶體的時候會向此程序傳送一個SIGSEGV訊號,導致程序直接退出,並在shell中提示segment fault。

利用backtracebacktrace_symbols列印函式的呼叫關係

源程式如下 #include <stdio.h> #include <string.h> #include <stdint.h> typedef uint32_t UINT32; void fun3(void) {   void* a

利用DecoratorSourceMap優化JavaScript錯誤堆疊

配合[原始碼](https://github.com/TencentCloudBase/cloudbase-js-sdk/blob/master/packages/utilities/src/helpers/decorators.ts)閱讀體驗更佳。 最近收到使用者吐槽 @cloudbase/js-sdk(

Linux下利用backtrace追蹤函數調用堆棧以及定位錯誤[轉]

調試 寫入文件 如果 通過 來源 res c函數 glibc tac 來源:Linux社區 作者:astrotycoon 一般察看函數運行時堆棧的方法是使用GDB(bt命令)之類的外部調試器,但是,有些時候為了分析程序的BUG,(主要針對長時間運行程序的分析),在程序

嵌入式 linux下利用backtrace追蹤函式呼叫堆疊以及定位錯誤

一般察看函式執行時堆疊的方法是使用GDB(bt命令)之類的外部偵錯程式,但是,有些時候為了分析程式的BUG,(主要針對長時間執行程式的分析),在程式出錯時打印出函式的呼叫堆疊是非常有用的。在glibc標頭檔案"execinfo.h"中聲明瞭三個函式用於獲取當前執行緒的函式呼

Linux下利用backtrace追蹤函式呼叫堆疊以及定位錯誤

一般察看函式執行時堆疊的方法是使用GDB(bt命令)之類的外部偵錯程式,但是,有些時候為了分析程式的BUG,(主要針對長時間執行程式的分析),在程式出錯時打印出函式的呼叫堆疊是非常有用的。 在glibc標頭檔案"execinfo.h"中聲明瞭三個函式用於獲取當前執行緒的

利用H5構建地圖獲取定位地點

script false eight navi 高精度 位置 前端 err web 地圖與地理定位 定位在大部分項目中都需要實現,如何實現主要有如下的幾種方法 H5定位 在HTML5中navigator有很強大的功能,其中就有定位的方法 navigator.geo

(轉載)利用SIFTRANSAC算法(openCV框架)實現物體的檢測與定位,並求出變換矩陣(findFundamentalMatfindHomography的比較) 置頂

bsp 解釋 邊界 返回值 class 不同的 rip 很多 per 原文鏈接:https://blog.csdn.net/qq_25352981/article/details/46914837#commentsedit 本文目標是通過使用SIFT和RANSAC算

wex5 如何利用 百度地圖 定位 天氣外掛

引包: require("cordova!cordova-plugin-geolocation"); require("cordova!com.justep.cordova.plugin.baidulocation");     Model.prototy

VC++ 利用PDBdump檔案定位問題並進行除錯

轉載:https://blog.csdn.net/zfs_kuai/article/details/43646665 轉載:https://blog.csdn.net/i_chaoren/article/details/81453142 一、什麼是PDB檔案       &

簡單利用Apache Logs Viewer工具分析錯誤日誌頻繁掛掉原因

最近網站換成了騰訊雲的伺服器, 不知道為什麼dmz社群 apache老是掉線,更無語的是前幾天好不容易有點時間回下老家休息中途被使用者告知dmz社群無法訪問,大中午的個騎著個小毛驢重大山裡出來(大山沒訊號,收到使用者資訊純屬偶然),心中對於騰訊雲更是一萬個草泥馬奔騰著,不過分析日誌之後才發現我冤枉騰訊雲了!日

OpenCV - 利用SIFTRANSAC演算法實現物體的檢測與定位,並求出變換矩陣(findFundamentalMatfindHomography的比較)- 轉

本文目標是通過使用SIFT和RANSAC演算法,完成特徵點的正確匹配,並求出變換矩陣,通過變換矩陣計算出要識別物體的邊界(文章中有部分原始碼,整個工程我也上傳了,請點選這裡)。 SIFT演算法是目前公認的效果最好的特徵點檢測演算法。 整個實現過程可以複述如下:提供兩張初始圖片,一幅為模板影象

利用SIFTRANSAC演算法(openCV框架)實現物體的檢測與定位,並求出變換矩陣(findFundamentalMatfindHomography的比較)

本文目標是通過使用SIFT和RANSAC演算法,完成特徵點的正確匹配,並求出變換矩陣,通過變換矩陣計算出要識別物體的邊界(文章中有部分原始碼,整個工程我也上傳了,請點選這裡)。 SIFT演算法是目前公認的效果最好的特徵點檢測演算法,關於該演算法的就不多說了,網上的資料有很多

使用gdbcore dump迅速定位錯誤

一、什麼是core dump     core:記憶體、核心的意思;     dump:丟擲,扔出;     core dump:前提:當某程式崩潰的一瞬間,核心會丟擲當時該程式程序的記憶體詳細情況,儲存在一個名叫core.xxx(xxx為一個數字,比如core.6

導航定位(利用錨點jquery定位)

在網頁中,點選按鈕就會跳轉到相應的位置,滑動頁面,左邊的列表顏色就會變化到相應的顏色,滾動條也會相應改變。 這個功能利用了jquery裡的scroll事件。先講點選列表,跳轉到相應的位置。在htm

利用.dSYM.app檔案準確定位Crash位置

首先,確保在release(Ad Hoc或者App Store)一個版本時,儲存了對應的xxx.app和xxx.dSYM檔案。 其次,驗證xxx.crash、xxx.app和xxx.dSYM三者的uuid是否一致。 驗證方法: 1)檢視xxx.app的uuid。 $ d

利用dmesgaddr2line來對(動態庫裡的)段錯誤進行除錯

問題: 工作中,我們在varnish的基礎上,利用vmod機制,實現了一個可以定製策略,且策略可自動載入而不需重新啟動引擎的cache(平時,大家對varnish的利用,cache策略都定義在一個vcl配置檔案中,每次對策略進行修改,都需要重新啟動varnish,從而使得策

Linux日誌文件查看搜查命令(錯誤日誌排查定位

設備 日誌文件 語法 寫入 -i 字節數 不顯示 連接 linux 一、cat命令 cat 命令用於連接文件並打印到標準輸出設備上,主要用來查看文件內容,創建文件,文件合並,追加文件內容等功能。 語法格式 cat [-AbeEnstTuv] fileName  

利用Expressejs編寫簡單頁面

light logs 開發 ges 下載 highlight 視圖 script dem 1、創建臨時文件夾ejsdemo $ mkdir ejsdemo  2、進入ejsdemo 初始化項目 $ npm init 3、安裝express   $ npm

HTML中Float元素定位

分層 tom 依據 none 單位 正常 對象 ott 目前   浮動 1、float屬性——浮動   float:left;float:right;float:none; 2、清除浮動——clear   Clear:left\right\both\none 3、溢出處