1. 程式人生 > >C/C++捕獲段錯誤,打印出錯的具體位置(精確到哪一行

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

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

背景知識:

· 在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,將這些打印出來的指令地址轉換為行號、函式名。

執行情況的一個示例:

[[email protected] 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

[[email protected] 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,呼叫路徑一目瞭然展示在你眼前啦!

相關推薦

C/C++捕獲錯誤出錯具體位置(精確一行

其實還可以使用 glibc 的 backtrace_symbols 函式,把棧幀各返回地址裡面的數字地址翻譯成符號描述的 背景知識: · 在linux/unix中的訊號處理機制,知道signal函式與sigaction的區別 · 段錯誤的概念,CPU中斷處理的步驟,中斷向量表的分類 · 知道CPU Exc

c語言中continue的運用同時學習接收字符字符遍歷字符

putc pre 讀取 ext cnblogs enter pri void blog 1 /************************************************************************* 2 > Fi

關於c語言內存分配,malloc,free,和錯誤內存泄露

今天 text new .net 決定 析構函數 靈活 如果 best 1. C語言的函數malloc和free (1) 函數malloc和free在頭文件<stdlib.h>中的原型及參數 void * malloc(size_t size

C程式設計——給一串數出裡面和為100的兩個數

**1、**程式 #include <stdio.h> #include <string.h> int main() { int arr[99] = {0}; int count = 0; printf("請輸入需要進行查詢的數列:"); do{

【劍指Offer】輸入一個正數s出所有和為s 的連續正數序列(序列大小至少為2) (C++)

題目用例: s = 15,那麼由於1+2+3+4+5 = 4+5+6=7+8 = 15,所以存在3個這樣的序列。 分析: 由於序列大小至少為2,我們定義兩個變數l和r,分別表示序列的最小值和最大值。 對於r而言,r取何值其實是有一個範圍的,即當序列只有2

題目:輸入一個字串出該字串中字元的所有排列。例如輸入字串abc則輸出由字元a、b、c所能排列出來的所有字串abc、acb、bac、bca、cab和cba。

題目:輸入一個字串,打印出該字串中字元的所有排列。例如輸入字串abc,則輸出由字元a、b、c所能排列出來的所有字串abc、acb、bac、bca、cab和cba。 /** * */ pack

c#輸入三個數出中間的數值

Console.WriteLine("請輸入三個數:"); int a = int.Parse(Console.WriteLine()); int b = int.Parse(Console.WriteLine()); int c = int.Parse(Console.W

C語言中“錯誤”出現的場景

oca auth pan clas log ted start 符號 cnblogs 1 /************************************************************************* 2 > Fil

C 語言的一個錯誤沒找出原因

amp n) string getc bsp can 什麽 數據 能夠 #include <stdio.h>#include <stdlib.h>#include <string.h> intmain(void){ char str[51

C#代碼在控制臺上圖案

c#using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace demo{ class Program {

C# 在PDF頁面添加按鈕

步驟 提升 添加引用 hit rec rgb create reat geb 簡述 在文檔中設置按鈕給我們提供了一種快速操作文檔的方式,簡潔省事,應用於程序中能夠有效的提升客戶滿意度。在前一篇文章中講述了如何在PDF文檔中設置頁面的跳轉按鈕,包括跳轉至指定頁,包括首頁、下一

C# DEBUG 調試信息及輸出詳解

ace tex 監聽器 dia 對話 使用 tel 狀態 blog https://blog.csdn.net/aaaaatiger/article/details/5583301 1.debug只在[debug模式下才執行](運行按鈕後面的下拉框可選) 2.deb

C/C++中的錯誤(Segmentation fault)

}3)其他其實大概的原因都是一樣的,就是段錯誤的定義。但是更多的容易出錯的地方就要自己不斷積累,不段發現,或者吸納前人已經積累的經驗,並且注意避免再次發生。例如:<1>定義了指標後記得初始化,在使用的時候記得判斷是否為NULL<2>在使用陣列的時候是否被初始化,陣列下標是否越界,陣列元

Linux下執行C++程式出現“錯誤(核心已轉儲)”的原因

轉載自:http://www.linuxidc.com/Linux/2015-09/122966.htm 今天Linux下寫程式出現了“段錯誤(核心已轉儲)"的問題,查了一下資料,加上自己的實踐,總結了以下幾個方面的原因。 1.記憶體訪問出錯 這類問題的典型代表就是陣列越界。 2.非法記憶體訪問 出現這類問

Java——Properties集合Object序列化流與反序列化流commons-IO文件工具類

都是 oos times odi store buffer src object 所有 一、properties集合 集合對象Properties類,繼承Hashtable,實現Map接口,可以和IO對象結合使用,實現數據的持久存儲。 p { margin-bottom:

ireport報表報表加載失敗的解決方法

.cn HR width gpo image img ron bsp nbsp 1、報表加載失敗圖示 2、解決方法 原創作者:DSHORE 出處:http://www.cnblogs.com/dshore123/ 歡迎轉載,轉載務必說明出處。(如果本

java代碼:用for循環求和求偶數和求奇數和水仙花數統計水仙花數

java代碼 for循環 求和 打印水仙花數統計水仙花 用for循環求和,求偶數和,求奇數和,打印水仙花數,統計水仙花數package loop; public class For1 {public static void main(String[] args) {int sum=0;for(

python基礎:匹配指定目錄下符合規則的文件文件全路徑

python# -*- coding:utf-8 -*- #遍歷目錄樹 import os,fnmatch def all_files(root, patterns=‘*‘, single_level=False, yield_folder=False): # 將模式從字符串中取出放入列表中

用switch語句判斷月份出當前月份所在的季節

class nth htm html ase date() div IT AS var today=new Date(); var abc=today.getMonth(); switch (abc) { case 0: