<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    C/C++捕獲段錯誤,打印出錯的具體位置(精確到哪一行)

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    修訂:2013-02-16

    其實還可以使用 glibc 的 backtrace_symbols 函數,把棧幀各返回地址里面的數字地址翻譯成符號描述的

     

    修訂:2011-06-11

    背景知識:

    · 在linux/unix中的信號處理機制,知道signal函數與sigaction的區別

    · 段錯誤的概念,CPU中斷處理的步驟,中斷向量表的分類

    · 知道CPU Exception分為Fault、trap和abort,了解他們的基本區別

    · 段錯誤和浮點錯誤屬于Fault,產生Fault時會將出錯指令的地址入棧,而不是下一條將執行指令的地址

    · 在linux/unix里可以通過調用backstrace來獲取棧幀的信息

    · 文中用到的幾個頭文件和函數,都屬于glibc,所以不用擔心出現找不到頭文件和鏈接錯誤的情況

    · addr2line是個系統自帶的小工具,用來轉換編譯出來的地址和源碼行號

     

    背景知識大家可以看書,google,看手冊(建議可以簡單閱讀一下本文列出來的參考資料)…,這里不想粘貼大量的背景知識,本文主要介紹在 linux / unix 里面,如何捕獲段錯誤并輸出發生錯誤時的代碼執行路徑,最后還提供了一個封裝好的頭文件。

    OK,下面直奔主題:

     

    ——先要抓住段錯誤,別讓它跑了

         捕獲段錯誤的方式很簡單,針對段錯誤的信號調用 sigaction 注冊一個處理函數就可以了。

         struct sigaction act;

         int sig = SIGSEGV;

         sigemptyset(&act.sa_mask);

         act.sa_sigaction = OnSIGSEGV;

         act.sa_flags = SA_SIGINFO;

         if(sigaction(sig, &act, NULL)<0)

         {

                  perror("sigaction:");

         }

             信號處理函數

    void OnSIGSEGV(int signum, siginfo_t *info, void *ptr)

    {

    //TO DO: 輸出堆棧信息

             abort();

    }

     

    ——接下來,分析出錯時的函數調用路徑

             發生段錯誤時的函數調用關系體現在棧幀上,可以通過在信號處理函數中調用 backstrace 來獲取棧幀信息,backstrace 的具體描述可google之/閱讀頭文件execinfo.h。修改后的處理函數如下:

    void OnSIGSEGV(int signum, siginfo_t *info, void *ptr)

    {

             void * array[25]; /* 25 層,太夠了 : ),你也可以自己設定個其他值 */

             int nSize = backtrace(array, sizeof(array)/sizeof(array[0]));

             for (int i=nSize-3; i>=2; i--){ /* 頭尾幾個地址不必輸出,看官要是好奇,輸出來看看就知道了 */

    /* 修正array使其指向正在執行的代碼 */[f1] 

                       printf("SIGSEGV catched when running code at %x\n", (char*)array[i] - 1);

             }

             abort();

    }

     

    ——進一步定位到出錯的具體位置

             要想輸出出錯的具體位置,必須用到信號處理函數的第三個參數,在linux/unix環境下,該指針指向一個ucontext_t結構。這個結構的具體情況,可以通過閱讀頭文件ucontext.h得知。此結構體里面包含了發生段錯誤時的寄存器現場,其中就包含EIP寄存器,該寄存器的內容正是段錯誤時的指令地址(因為段錯誤是一種Fault)。

    進一步修正后的信號處理函數如下:

    void OnSIGSEGV(int signum, siginfo_t *info, void *ptr)

    {

         void * array[25];

         int nSize = backtrace(array, sizeof(array)/sizeof(array[0]));

         for (int i=nSize-3; i>2; i--){ /* 頭尾幾個地址不必輸出 */

                  /* 對array修正一下,使地址指向正在執行的代碼 */

                  printf("signal[%d] catched when running code at %x\n", signum, (char*)array[i] - 1);

         }

        

         if (NULL != ptr){

                  ucontext_t* ptrUC = (ucontext_t*)ptr;

                  int *pgregs = (int*)(&(ptrUC->uc_mcontext.gregs));

                  int eip = pgregs[REG_EIP];

                  if (eip != array[i]){ /* 有些處理器會將出錯時的 EIP 也入棧 */

                      printf("signal[%d] catched when running code at %x\n", signum, (char*)array[i] - 1);

                  }

                  printf("signal[%d] catched when running code at %x\n", signum, eip); /* 出錯地址 */

         }else{

                  printf("signal[%d] catched when running code at unknown address\n", signum);

         }

     

             abort();

    }

     

    ——調用函數的路徑、出錯的位置都輸出了,但是你能看懂輸出么

             好了,現在棧幀里面的地址和出錯位置的地址都已經以十六進制的形式輸出了,但是這是編譯后的地址,而不是源碼的行號,你能看懂么?所以還需要借助一個linux/unix自帶的小工具addr2line,將這些打印出來的指令地址轉換為行號、函數名。

    執行情況的一個示例:

    [root@suse tcpBreak]# ./a.out

    signal[11] catched when running code at 804861d

    signal[11] catched when running code at 8048578

    signal[11] catched when running code at 804855a

     

    [root@ suse tcpBreak]# addr2line 804861d 8048578 804855a -s -C -f -e a.out

    main

    newsig.cpp:55

    oops()

    newsig.cpp:32

    error(int)

    newsig.cpp:27

     

             上面輸出的內容,其具體含義是:

    捕獲的信號序號是 11 (SIGSEGV)

    執行路徑是第52行--第32行--第27行

    調用關系是main--oops--error,在error函數內部,即文件的第27行發生了段錯誤。

     

    ——一點討論

             · 你可能已經閱讀了 execinfo.h,發現其中有一個 backtrace_symbols,想通過調用這個函數來輸出stack frame上面的函數名…你不妨試一下

             · 將 backtrace 得到的 array 地址元素減 1 就能得到調用地點么?的確是這樣的,減 1 不保證地址落到函數調用時跳轉指令的起始處,但可以保證指向了該指令的最后一個字節,而該指令地址經addr2line轉換后[f2] ,就對應了發生函數調用的行號。

    · 可不可以不調用 backstrace 來得到棧幀中的內容?可以的,因為這些內容都在棧里,你要是明確地知道偏移,就可以得知函數調用棧,但是要費很多心思,而且估計你自己寫的模仿 backstrace 的代碼,可移植性成了問題。

             · 通過 gdb 調試 core文件 不是直接看得到內存映像么,還有必要搞得這么復雜么?一般情況下當然不必要,上面所列解決方法的優點在無法正常產生 core 文件的情況[f3] 下才得以體現。

             · 需要在編譯時添加選項 -g 么?當然需要了,不在可執行文件中記錄行號信息,addr2line上哪里去找行號。否則只能得到函數名稱,無法得到行號信息。

     

    ——頭疼,想直接用行不行,能來個直接可以用的代碼么

             這里提供一個頭文件(見附件 segvCatch.rar ),但是不保證沒有bug哦。使用方法很簡單,只需要在main函數所在源文件包含該頭文件即可。

             該頭文件捕獲了浮點錯誤和段錯誤,像上面示例所說的,在出錯時會向 STDOUT 輸出一系列地址后退出程序,再使用 addr2line 對輸出的地址進行轉換,bingo,調用路徑一目了然展示在你眼前啦!

     

     標注:

     [f1]調用函數時,會將函數返回地址入棧,此返回地址為返回后將執行的指令地址。

     [f2]事實上,test()翻譯成若干匯編指令,指向這些指令對應區域的所有地址將被addr2line轉換為調用test()對應的行號。

     [f3]權限不夠、ulimit未打開、core文件太大…等情況

     

     

     

    參考資料:

    中斷與異常

    http://blog.csdn.net/shaohaigod1981/archive/2009/11/04/4767915.aspx

    http://hi.baidu.com/hilyjiang/blog/item/cdd7ebb417f8be728bd4b2a1.html

    http://www.logix.cz/michal/doc/i386/chp09-08.htm

    ucontext_t說明 http://www.gnu.org/s/libc/manual/html_node/System-V-contexts.html

    信號概述 http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html?ca=drs-

    Backstrace http://hi.baidu.com/sunkang_2/blog/item/e7dd68df51db585c94ee378f.html

    man 7 signal

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全