《軟件調試的藝術》學習筆記——GDB使用技巧摘要前端
《軟件調試的藝術》,由於名是The Art of Debugging with GDB, DDD, and Eclipse. 做者是美國的Norman Matloff和Peter Jay Salzman,中文版由張雲翻譯。是人郵出版社圖靈程序設計叢書第一版。
這裏稱爲"藝術",我的以爲有點過了,可是其中關於gdb以及在gdb基礎之上集成的DDD和Eclipse調試技巧的整理確實是作的很好,對於Linux/開源社區下的開發人員以及其餘平臺技術gdb調試的開發人員都是頗有幫助。畢竟有句話叫作:程序是調出來的,不是寫出來的。這句話的意思沒有抹殺程序設計思想、框架、優良編碼風格的意味,只是誰都知道,軟件怎麼可能沒有bug呢?
下面是整理的內容:
(DDD和Eclipse在調試時確實有其獨特之處,可是我都沒有怎麼用過,因此就沒有閱讀其中的內容,天然也沒有整理)
調試的原則
調試的本質:確認原則
其餘原則
從簡單工做開始調試
使用自頂向下的方法
使用調試工具肯定段錯誤的位置
經過發出中斷肯定無限循環的位置
使用二分搜索
調試的原則是和具體調試工具無關的,是程序員對代碼世界的分析、認識的一種方法學和思路,是對程序運行時狀態變化做出的一種習慣、相對正確的反應。python
修改充滿錯誤的程序(不少年輕的程序員開始都認爲本身的程序是沒有錯誤的,其實不少細節、編碼規範均可能存在問題),就是逐個去確認你認爲正確的代碼確實是正確的。當你發現其中某個假設不成立時,就表示已經找到了關於程序錯誤所在位置的線索(甚至是錯誤自己)。這就是確認原則。linux
這裏無心討論基於文本的調試工具和基於GUI的調試工具的差異,應該說二者各有千秋,用在合適的地方就是好,在合適的人手裏就是合適的武器。
值得介紹的是GDB有個-tui選項,和Ctrl+X+A組合鍵能夠在文本模式和TUI模式之間切換。CGDB (http://cgdb.sf.net) 是對其的一種加強和改善。程序員
開始介紹GDB的知識點。
要用gdb調試可以有代碼顯示,必須用-g選項進行編譯源碼。另外使用gdb的時候,儘可能多用tab鍵。
• 斷點
下斷點是break(縮寫是b),後面能夠接行號(或者文件名:行號)、函數名、內存指令地址;
tbreak是臨時斷點,有限次數爲一次;
對應的高級技巧有條件斷點。好比
(gdb) break 30 if num_y == 1
這表示在第三十行下條件斷點,條件爲num_y==1是中端程序運行
刪除斷點用delete,禁用/啓用斷點爲disable/enable
到斷點了能夠step/next/continue等命令執行,還有clear、condition等命令可使用,多看help
• 觀察點
還能夠用觀察點來查看變量、內存、表達式的值是否改變,一樣能夠加條件。多試試就記住了。命令是watch
• 查看變量
查看變量是print 後面接變量名,也能夠是表達式,函數等,灰常強大
調試的時候通常開兩個終端,一個是gdb調試終端,一個編輯、編譯終端,這樣gdb發現問題的時候就不用退出,直接在另一個終端修改好編譯後直接在gdb裏面從新ruan程序就行。這樣的好處是以前設置的斷點、宏等都還存在。算法
command命令能夠在每次發生斷點的時候自動執行一系列命令,解放程序員的雙手。
見示例:
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end"
>silent
>printf "fibonacci was passed %d./n", n
> continue
>end
(gdb) run
Starting program: fibonacci
fibonacci was passed 3.
fibonacci was passed 2.
fibonacci was passed 1.
fibonacci was passed 0.
fibonacci was passed 1.
Fibonacci(3) is 3.shell
Program exited normally.
(gdb)express
《軟件調試的藝術》學習筆記——GDB使用技巧摘要——程序崩潰處理編程
程序爲何會崩潰
內存中的程序佈局
當某個錯誤致使程序忽然和異常地中止執行時,程序崩潰。迄今爲止最爲常見的致使程序崩潰的緣由是試圖在未經容許的狀況下訪問一個內存位置。硬件會感知這件事,並執行對操做系統的跳轉。
Unix系列的平臺上,操做系統通常會宣佈程序致使了段錯誤(seg fault),並中止程序運行。
在微軟的windows系統上,對應的術語是通常保護錯誤(general protection fault)。不管是哪一個名稱,硬件都必須支持虛擬內存,並且操做系統必須使用虛擬內存纔會發生這個錯誤。
雖然這是現在的通用計算機的標準,可是讀者應記住,專用的小型計算機通常沒有這種狀況,好比用來控制機器的嵌入式計算機。windows
程序在內存中是如何分佈的?
在Unix平臺上,爲程序分配的虛擬地址的佈局一般以下:
.text
.data
.bss
堆內存
↓
未使用
↑
棧內存
env
0地址
這裏虛擬地址0在最下方,箭頭顯示了其中兩個組件(堆和棧)的增加方向,當它們增加時,消耗掉未使用的自由區域。
各個部分的做用以下:
• 文本區域(.text),由程序源代碼中的編譯器產生的機器指令組成。這一組件包括靜態連接代碼,包括作初始化工做而後調用main()的系統代碼/usr/lib/crt0.o
• .data 數據區域,包含在編譯時分配的全部程序變量,即全局變量。如 int x = 5;
• .bss 數據區域,包含的是存放未初始化數據的全局變量,如 int y;
• 當程序在運行時從操做系統請求額外的內存時(例如,C語言中調用malloc,或者C++中的new),請求的內存在名爲堆(heap)的區域中分配。若是堆空間不足,能夠經過調用brk()來擴展堆(這正是malloc及相關函數所作的事情)
• 棧區域(stack),是用來動態分配數據的空間。函數調用的數據(包括參數、局部變量和返回地址)都存儲在棧上。每次進行函數調用時棧都會增加,每次函數返回到其調用者時棧都會收縮。
• 上圖沒有顯示程序的動態連接代碼,它的位置與平臺相關,可是它確實在某個地方存在,並且在每種操做系統上,虛擬內存中關於動態連接代碼的地址是有規律的。
int q[ 200 ] ;安全
int main( void ) {
int i,n, *p;
p = malloc( sizeof ( int ) ) ;
scanf( "%d" , &n) ;
for (i = 0 ; i < 200 ; i++ )
q[i] = i;
printf ( "%x %x %x %x %x/n" , main, q, p, &i, scanf) ;
return 0 ;
}
雖然上述程序自己做用不大,可是能夠編寫成一個工具來非正式地探索虛擬地址空間的佈局。運行結果可能以下:
% a.out
5
80483f4 80496a0 9835008 bfb3abec 8048304
分別對應於文本區域、數據區域、堆、棧和動態連接函數位置。
另外能夠經過查看這一過程的maps文件來獲得程序在linux上的精確內存佈局狀況。加入進程號是21111,那麼查看的文件應該是/proc/21111/maps
頁的概念
虛擬地址空間是經過坐直成成爲頁(page)的塊來查看的。在pentium硬件上,默認的頁大小是4096字節。物理內存(包括RAM和ROM)也都是分紅頁來查看的。當程序被加載到內存中執行時,操做系統會安排程序的部分頁存儲在物理內存的頁中。這些頁成爲被"駐留",其他部分存儲在磁盤上。
在執行期間的各個階段,將須要一些當前沒有駐留的程序頁。當發生這種狀況的時候,硬件會感知到,將控制權轉移給操做系統。後者將所需頁帶到內存中,可能會替換掉當前駐留的另外一個程序頁(若是沒有可用的自由內存頁),而後將控制權返回給程序。若是有被驅逐的程序頁,就會變成非駐留頁,被存儲在磁盤上。
爲了管理全部這些操做,操做系統爲每一個過程設立了一個頁表(page table)。(pentium的頁表有一個層次結構,可是爲了簡單起見,假定只有一層,並且這裏討論的大多數內容都不是pentium特有的。)這一過程的每一個虛擬頁在表中都有對應的一個項(entry),其中包括以下信息:
這個頁在內存中或者磁盤上的當前物理位置。若是是在磁盤上,頁表對應的項會指示頁是非駐留的,可能包含一個指針,指向最終致使磁盤上的物理位置的一個列表。例如,它可能顯示:程序的虛擬頁12是駐留的,位於內存的物理頁200中。
該頁的權限分爲3種:讀、寫和執行
注意:操做系統不會將不完整的頁分配給程序。例如,若是要運行的程序總共有10 000字節,若是徹底加載,會佔用3個內存頁(3*4096),不會是佔用2.5個頁,由於頁是虛擬內存系統可以操做的最小內存單元。這是調試時要記住的很重要的一點,由於這一點暗示了程序的一些錯誤內存訪問不會觸發段錯誤。換言之,在調試會話期間,不能這麼想:"這行代碼必定沒有問題,由於它沒有引發段錯誤。"
產生段錯誤的真正緣由:權限不匹配
在程序的運行期間,生成的地址會是虛擬的。當程序試圖訪問某個虛擬地址處的內存時,好比y,硬件就會將其轉化爲虛擬頁號v,它等於y除以4096(其中除法是整除算法,捨去餘數)。而後硬件會檢查頁表中的頁表項v來查看該頁的權限是否與要執行的操做匹配。若是匹配,硬件會從這個表項中獲得所需位置的實際物理頁號,而後完成請求的內存。可是若是該表項顯示請求的操做不具備恰當的權限,硬件就會執行內部中斷。這會致使跳轉到操做系統的錯誤處理例程。而後,操做系統通常會宣告一個內存訪問違例,並中止程序的執行(即從進程表和內存中去掉程序)。
程序中的錯誤會致使權限不匹配,並在上面列出的某個類型的內存訪問週期生成段錯誤。
段錯誤能夠發生在數據區域、堆棧等位置。報告錯誤的地方,每每不是問題本質所在,須要在附近回溯一下。
經常使用的調試方法有核心文件(core file)或者signal.h文件中提供的signal()或者sigaction()兩個系統調用來捕獲。
《軟件調試的藝術》學習筆記——GDB使用技巧摘要——Pthread線程調試
Unix下最廣泛的線程包是POSIX標準的Pthreads。Pthreads使用的搶佔式線程管理策略,程序中的一個線程可能在任什麼時候候被另外一個線程中斷。因此,使用Pthreads開發的應用程序有些錯誤不太容易重現。
GDB線程相關命令彙總
info threads 給出關於當前全部線程的信息
thread n 改成線程n,或者說是進入線程n的棧中進行觀察
break line_num thread n 表示當線程n到達源碼行line_num時中止執行
break line_num thread n if expression 上一命令增長條件斷點而已
加入懷疑線程之間有死鎖,能夠用gdb進行調試定位。流程大體是:
• 用gdb啓動或者插入待調試程序
• 當程序掛起時候,經過按下Ctrl+C組合鍵中斷它;
• 這個時候用info threads查看全部線程都在幹嗎,而後找到本身的工做線程(注意排除main線程和pthreads的管理線程)
• 分別查看本身的工做線程在幹嗎,用bt(backtrace)查看對應的幀,記得用thread n切換進入對應線程的幀
• 關注像__pthread_wait_for_restart_signal()和lock等函數,若是有源碼的話,會比較方便地定位到具體的問題代碼位置
下面是一個簡單的例子。若是在worker線程裏面,上鎖和解鎖沒有匹配,則會發生死鎖
// finds the primes between 2 and n; uses the Sieve of Eratosthenes,
// deleting all multiples of 2, all multiples of 3, all multiples of 5,
// etc.; not efficient, e.g. each thread should do deleting for a whole
// block of values of base before going to nextbase for more
// usage: sieve nthreads n
// where nthreads is the number of worker threads
#include <stdio.h>
#include <math.h>
#include <pthread.h>
#define MAX_N 100000000
#define MAX_THREADS 100
// shared variables
int nthreads, // number of threads (not counting main())
n, // upper bound of range in which to find primes
prime[MAX_N+1], // in the end, prime[i] = 1 if i prime, else 0
nextbase; // next sieve multiplier to be used
int work[MAX_THREADS]; // to measure how much work each thread does,
// in terms of number of sieve multipliers checked
// lock index for the shared variable nextbase
pthread_mutex_t nextbaselock = PTHREAD_MUTEX_INITIALIZER;
// ID structs for the threads
pthread_t id[MAX_THREADS];
// "crosses out" all multiples of k, from k*k on
void crossout(int k)
{ int i;
for (i = k; i*k <= n; i++) {
prime[i*k] = 0;
}
}
// worker thread routine
void *worker(int tn) // tn is the thread number (0,1,...)
{ int lim,base;
// no need to check multipliers bigger than sqrt(n)
lim = sqrt(n);
do {
// get next sieve multiplier, avoiding duplication across threads
pthread_mutex_lock(&nextbaselock);
base = nextbase += 2;
pthread_mutex_unlock(&nextbaselock);
if (base <= lim) {
work[tn]++; // log work done by this thread
// don't bother with crossing out if base is known to be
// composite
if (prime[base])
crossout(base);
}
else return;
} while (1);
}
int main(int argc, char **argv)
{ int nprimes, // number of primes found
totwork, // number of base values checked
i;
void *p;
n = atoi(argv[1]);
nthreads = atoi(argv[2]);
for (i = 2; i <= n; i++)
prime[i] = 1;
crossout(2);
nextbase = 1;
// get threads started
for (i = 0; i < nthreads; i++) {
pthread_create(&id[i],NULL,(void *) worker,(void *) i);
}
// wait for all done
totwork = 0;
for (i = 0; i < nthreads; i++) {
pthread_join(id[i],&p);
printf(" %d
values of base done
/n
",work[i]);
totwork += work[i];
}
printf("
%d
total values of base done
/n
",totwork);
// report results
nprimes = 0;
for (i = 2; i <= n; i++)
if (prime[i]) nprimes++;
printf("the number of primes found was %d
/n
",nprimes);
}
軟件調試的藝術 The Art of Debugging with GDB, DDD, and Eclipse
馬特洛夫(Matloff, N.), (美)薩爾茲曼(Salzman, P J.)著 張雲譯
第1章是概覽。簡單卻有用的通用準則。
第2章着重介紹 一斷點
第3章 重點是介紹當到達斷點時如何方便地顯示樹中節點的內容。 用圖形顯示樹和其餘連接數據結構的DDD功能。
第4章包括了因爲段錯誤(即內存訪問錯誤)而而產生的致命運行時錯誤。崩潰時在底層狀況,包括程序的內存分配以及硬件與操做系統的協同做用。
第5章不但介紹並行編程,並且包拒活網絡代碼。L述基本內容:分時、進程與線程\競爭條件等。 使用線程的技術細節,要記住的通用原則,好比發生線程上下文切換時的時間選擇隨機性。用流行的MPI 和OpenMP程序包進行並行編程.
第6章包括其餘一些重要主題。鄉 上理因爲缺乏必要的庫形成的鏈接失敗問題,庫的類型以及如何將庫與主要代碼連掛接。顯示如何讓GDB, DD和Eclipse與curses窗口中的事件交互。
第7章介紹了部分輔助工 具,全書主要介紹的是C一編程的調試;
第8章則談到了其餘語言,包括,Java, Python, Perl和 彙編.
Norm Matloff.與Pete Salzman 於2008年6月9日
GDB yGNU項目調試器(GNU Project Debugger)診下載GCC編譯
DDD (Data Display Debugger,數據顯示調試器)用戶經過GUI發出命令,GUI將這些命令傳遞給 GDB .
Fedora Linux: yum install ddd 在Ubuntu Linux上,可使用命令apt-get.
1.3調試的原則 雖然調試是一門藝術而非科學,可是仍然有一些明確的原則來指導調試的實踐.
確認的基本原則(Fundamental Principle of Confirmation) 在本質上 是至關正式的原則。
1.3.2,調試的本質:確認原則 。確認的基本原則
修正充滿錯誤的程序,就是逐個確認,你自認爲正確的許多事情所對應的代碼確實是正確的。 當你發現其中某個假設不成立時,就表示已經找到了關於程序錯誤所在位置(可能並非準確的 位置)的線索。
當你認爲關於程序的某件事情是正確的,而在確認佑的過程當中卻失敗了,你就會感到驚訝。 這種驚訝是好事,由於這種發現會引導你找到程序錯誤所在的位置
1 .3.3其餘調試原則
0從簡單工做開始調試
A使用自頂向下的方法
.使用調試工具肯定段錯誤的位置
。經過發出中斷肯定無限循環的位置
0使用二分搜索
1.4.2折中方法
從版本6.1以來,GDB己經以名爲TUI (Terminal User Interface,終端用戶界面)的撈模式提供 了基於文本交互和圖形用戶交互之間的折中方法。在這一模式GDB將終端屏幕劃分爲相似於 DDD的源文本窗口和控制檯的多個子窗口:能夠在相似於源文本窗口的子窗口中跟蹤程序執進展過程,同時在相似於控制檯的子窗口中發出GDB命令。爲了以TUI模式運行GDB,能夠在調用GDB時在命令行上指定一tui選項,或者處於非TUI模 式時在GDB中使用Ctrl+X+A組合鍵。
另外一個可用的GDB界面是CGDB,該界面能夠從http://cgdb.sourceforge.net/得到。CGDB也提 供了一種基於文本的方法與GUI方法之間的折中方案。
在調用GDB時能夠指定啓動文件; $gdb -command=z x 表示要在可執行文件上運行GDB,首先要從文件:中讀取命令。
第2章 停下來環顧程序
調試器的好處在於:能夠通知它暫停程序的執行。暫停之後,調試器 提供了檢查變量、跟蹤執行路徑等的機會。
2.1暫停機制
斷點:通知GDB在程序中的特定位置暫停執行,
監視點:通知GDB當特定內存位置(或者涉及一個或多個位置的表達式)的值發生變化時暫停執行。
捕獲點:通知GDB當特定事件發生時暫停執行。
delete命令刪除斷點、監視點和捕獲點!
命令列表很是有用,可是將它們與條件中斷合併後,將具備更大的威力。
若是你在處理線程代碼,監視點的用處就有限;(GDB只能監視單個線程中的變量。
GDB其實是在va。的內存位置改變值時中斷。通常狀況下, 是否使用監視點監視變量或變量的地址並無關係,可是在特殊狀況下這一點可能很重要,好比 當處理指向指針的指針時。
第4章程序崩潰處理
不能根據沒有發生段錯誤來得出內存操做是正確的結論。
4.1.6,段錯誤與Unix信號
信號(signal)表示在程序執行期間服告的異常狀況,容許操做系統(或程序員本身的代碼) 反映多種事件。信號可能在某個進程上由系統的底層硬件拋出(SIGSEGV或,IGFPE ),或者由另外一個進程拋出(SIGUSR1或SIGUSSR2),甚至可能由該 進程自己發送(經過raise()庫調用)。
一個進程上能夠發出不少種不一樣的信號。;在Linux中,能夠經過在shell提示符後面鍵入以下 代碼來查看完整的信號列表。
man 7 signal
雖然有些信號處理程序不能被重寫,可是在不少狀況下能夠編寫本身的處理程序來替換操做 系統提供的默認處理程序。
4.2核心文件
轉儲核心。
4.2.1一核心文件的建立方式
核心文件包含程序崩潰時對程序狀態的詳細描述:棧的內容(fir者,若是程序是多線程的, 叭是各個線程的棧),CPU寄存器的內容(一樣,若是程序是多線程的,則是每餓程上的一組 寄存器值),程序的靜態分配變量的值(全局與stati。變量),等等 。
Unix命令file有助於指出轉儲這個特 定核心文件的可執行文件的名稱
4.2.2某些shell可能禁止建立核心文件
在bash中,可使用。limit命令控制核心文件的建立。
沒有獲得核心文件,對於bash使用ulimit -c檢查當前核心文件的限制, 對於tcsh或csh則使用limit -c檢查。
4.3.2在調試會話期間不要退出GDB
能夠保留咱們的斷點
調試會話是至關實用的,包括了調試的不少方面: 確認原則; 】使用核心文件進行崩潰進程的「死後」分析; 1糾正、編譯並從新運行程序,甚至不須要退出GDB; print代)風格調試的不足之處; 爾的智慧,這是無可替代的。
5.2調試多線程代碼
在真正複雜的網絡調試狀況中,可使用開源Ethereal程序跟蹤單個TCP/IP分組。
機roux系統上,能夠經過運行命令ps axH來查看系統上當前的全部i進程和線程 .
雖然有非搶佔線程系統,但}pthreads;使用的是搶佔線程管理策略,程序中的一個線程可能 在任什麼時候候被另外一個線程中斷。
5.3 -,調試並行應用程序
並行編程架構主要有兩種:共享內存和消息傳遞。
術語共享內存的確切含義是:多個CPU都具備對某些共同的物理內存的訪問權限。
多線程編程變 爲 在消息傳遞環境下,在各個CPU上運行的代碼只能訪問該CPU的本地內存,它經過通 信媒介上發送稱爲「消息」的字節串來與其餘CPU上的代碼通訊。一般這是某種網縱統統用協議(好比TCP/IP)或者適用於消息傳遞應用程序的專門軟件基礎結構。
5.3.1, 消息傳遞系統
先以流行的MPI (Message Passing Interface)包爲例,討論消息傳遞。多 這裏使用 的是MPICH實現,可是一樣的原理也適用於LAM和其餘MPI實現。
GDB容許使用進程號動態地將 調試器附加到己經運行的進程上。
debugwait的值敢自用戶提供的命令行,,表示等、,2表示不等待。
5.3.2共享內存系統
將真正的共享內存機制與軟件分佈式共享內-A 設置的狀況分開介紹。
1.真正的共享內爲存
在真正的共享內存環境中,一般使用線程來開發應用程序。 OpenMP這樣的機器上流行編程環境。 OpenmP指伶的多線 程實現對程序員基本上是透明的。
2,軟件分佈式共享內存系統
有多個處理器的大型共享內存仍然 狗替代品是工做站網絡((network of workstations, NOW架構使用了能夠形成共享內存錯覺的底層庫。這個庫對應用程序員基本上是透明 的,它主要從事維護不一樣節點之間共享變量的副本統一性這樣的網絡事務。
這種方法稱爲軟件分佈式共享內存(SDSM) ,另外一個優秀的軟件包是JIAJIA,能夠從Chinese Academy of Sciences (http://www-users.cs.umn.edu/~tianhe/paper/dist.htm )的站點下載.
5.4.一、OpenMP概述
本質上是線程管理操做的高級並行編程接口。
因爲OpenMP指令須要預處理,所以老是有失去原來的行號和變量及函數名的潛在問題。
從版本4.2起,GCC也能處理OpenMP代 碼了。只要在GCC命令行上添加-fopenmp標記便可。
omni編譯器 http://www.hpcc.jp/omni/),
二分搜索原則對於查找未知位置的語法錯誤很是有幫助。
6.1.2;缺乏庫
在Unix系統上,按慣例是在靜態庫文件名後面加上後全綴.a表明archive.另外,任何庫的 名稱通常都以lib開頭。
首先,使用ldd命令檢查程序須要哪一個庫,若是有,操做系統能夠在何處找到它們。
解決這種問題的一種方式是向該操做系統的正常搜索路徑中添加 ,若是要添加幾個目錄,將目錄名連同冒號的字符串做爲分隔符。
$LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/Debug/z
$export LD_LIBRARY_PATH
2.開源軟件中庫的用法
源代碼配 套的構建腳本(一般稱爲配置文件)找不到某些必需的庫。試圖經過設置LD_LIBRARY PATH環環境 變量可能會失敗。這種問題的根源經常在於配置文件調用的名爲pkgconfig的程序。這個程序會從某些元數據文 件處接收關於庫的信息。這樣的文件後綴爲.PC,前綴是庫的名稱。搜索..PC文件的默認目錄取決pkgconfig自己的位置。爲了解決這個問題, 設置環境變量PKG CONFIG PATH。在C:,,jTC shell中,執行以下shell命令。
%setenv PKG_CONFIG_PATH /usr/lib/pkgconfig:/usr/local/lib/pkgconfig像Vim和Emacs這樣的文本編輯器就是用curses編寫的。
若是使用的是Vim,咱們推薦Steve Outlline所著的Vi IMproved-Vim (New Riders , 2001)。
7.2 充分利用編譯器
若是不使用一Wall,就幾乎沒有必要使用GCC.‘要慎用一Wmissing一prototypes和-Wmissing一declarations。
7.3 C語言中的錯誤報告
用errno
系統與庫調用的失敗一般是因爲設置了名爲errn。的全局定義整數變量。在大多數GNU/ Linux系統上,errn。是在/usr/include/errno.h上聲明的,根據慣例,當庫函數或系統調用失敗時,它將errno 設置爲一個描述調用失敗緣由的值。
在程序中開始使用errn。以前,須要注意一些問題。
首先,使用errn。的代碼可能不是徹底可移植的。例 例如,ISO C標準僅定義了少許錯誤代碼, POSIX標準定義了不少錯誤代碼。是規定了符號錯誤代碼,它 們是一些以E爲前綴的宏常量,在errno頭文件中定義(或者在errno標題所包括的文件中)。
使用errn。最安全的方式以下:
(1)執行對庫或系統函數的調用。
(2)使用函數的返回值判斷是否發生了某個錯誤。
(3)若是發生了某個錯誤,使用errno肯定爲何發生這個錯誤。
E有兩個函數使得錯誤代砰 的解釋更容易:perror()和strerror()。
perror()的輸出是一個標準錯誤。
另外一個有助於將errno代碼翻譯成描述性消息的函數是strerror().
7.4更好地使用strace和ltrace
瞭解庫函數和系統調用之間的區別很重要。庫strace實用程序輸出程序進行的各個系統調用及其參數和返回值。
strace輸出的每一行對應於一個系統調用。
將全部輸出保存在 一個文件中比試圖在屏幕上查看要方便得多。坐 可使用重定向stder。的方式,可是也能夠 B-o LogfiLe切換,使strace將其全部輸出都寫到一個日誌文件中。另外 。爲了強制strace將字符串截短爲N個字符,可 使用一s N選項。最IfF :,若是在具備子進程分支的程序上運行strace,能夠用一0 LOG -ff切換, 單個子進程的strace輸出捕獲到一個名爲LOG二的文件中,其中二是子進程ID.
還有一個名爲ltrace的實用工具,它相似於strace,但它顯示了庫調用而不是系統調用。
strace和ltrace對於跟蹤程序錯誤和解決棘手且會引發不少麻煩的奇 怪行爲很是有用。
7.5靜態代碼檢查器:lint與其衍生
描代碼的工具,不編譯代碼,僅僅警告錯誤、可能的錯誤和與嚴 格C語言編碼標準的差距;這樣的稱爲靜態代碼檢查器。C語言的規範靜態檢查器由S. C. Johnson 乏編寫,稱爲lint.splint的目標是幫助編寫大部分有防護性、安全和儘量少出錯的程序。若是沒有++weak開關,splint一般由於太挑剔而用處不大。I +weak,弱檢查,一般用於無註解的C代碼; O +standard,默認模式; 。+checks,中度嚴格檢查; 1 +strict,高度嚴格檢查。 splint是在GNU GPL下發布的,其主頁爲http://www.splint.org/
7.6.1檢測DAM問題的的策
Electric Fence,這是對分配的內存地址實施「柵欄」功能的庫。訪問這些柵欄外的 內存一般會致使段錯誤和核心轉儲。
本節還會討論兩個GNU工具mtrace()和MALLOC一 CHEC戈,它 們向標準庫分配函數中添加鉤子,以保持關於當前分配內存的記錄。
這樣,庫就能夠對要讀、寫 或釋放的內存執行檢查。在使用幾個軟件工具時要當心,每一個工具都使用鉤子進行與堆相 關的函數調用,由於一個工具能夠在已經安裝的鉤子上再安裝一個鉤子。
7.6.2 Electric Fence
當EFence連接到代碼中時,致使程序在發生下列狀況之一時當即發生段錯誤並轉 儲核心在DAM邊界以外執行讀或寫操做。
對己經釋放的DAM執行讀或寫操做。
對沒有指向malloc()分配的DAM的指針執行free()(包括重複釋放的特殊狀況)。
7.6.3用GNU C庫工具調試以M問題
GNU C庫提供了一個名爲MALLOC CHECK的環境變量,像EFence同樣,可用來捕獲DAM訪問 違反,可是對它的使用不須要從新編譯程序。
這些設置及其效果以下所示。
0--關閉全部DAM檢查(若是沒有定義變量,也是這種狀況)。
1--當檢側到堆損壞時,顯示關於stderr的診斷消息。
2--當檢測到堆損壞時,當即異常中斷程序並轉儲內存。
3--1和2的綜合效果。
雖然MALLOC_CHECK比EFence用起來更方便,可是它有幾個嚴重的缺陷。不知道有問題代碼的源文件和行號,並且常常甚至不知 道是哪一個指針引發了問題。
其次,。這暗示了若是訪問錯誤發生後沒有調用與堆相關的函數,那麼MALLOC CHECK根本不 會報告錯誤。 再次,MALLOC CHECK_錯誤消息彷佛含義不是那麼明顯。最後,對於setuidRsetgidN序是禁用MALLOC_ CHECK的,由於懷有惡意的人能夠利用這種功能 組合進行安全攻擊。
總而言之,MALLOC_CHECK是一種方便的工具,在代碼開發期間用來捕獲與堆相關的編程故障。 然而,若是懷疑有DAM問題,或者要仔細掃描代碼查找可能存在的DAM問題,應當使用另外一個實用工具.
2.使用mcheck()Z具
UW,型爲: #include <mcheck.h>
int mcheck (void (*ABORTHANDLER) (enum mcheck status STATUS))
在調用任何與堆相關的函數前必須調用mcheck(),不然對mcheck()的調用會失敗。
3.使用mtrace()捕獲內存泄漏和重複釋放
mtrace()工具是GNU C庫的一部分,用來捕獲C和C++程序中的內存泄漏和重複釋放。
mtrace()的使用涉及5個步驟:
(1)將環境變量MALLOC TRAC「設置成有效的文件名。這; 這是mtrace()在其中放置消息的文件 名。
(2)包括mcheck.h頭文件。
(3)在程序最上方調用mtrace()。
(4)運行程序。若是檢測到任何問題,會用一種非人類可讀的形式將它們記到MALLOC_ TRACE 所指向的文件中。另外,爲了安全起見,mtrace()不會對setuid或setgid-If執行文件作任何事情。
(5)mtrace()配備了一個稱爲mtrace的Perl腳本,用來分析日誌文件,並將其內容顯示爲人類 可讀的標準輸出形式。
第8章 對某他語言使用GDB/DDD/Eclipse 用GNU的人也提供了一款J瓢潑大雨編譯器:GCJ DDD常常可用來做爲其餘語言特有的調試器的前端。 8.1.1直接使用GDB調試Java 趕用GNU的GCJ編譯器,能夠將Java源代碼編譯爲 本地機器代碼。 爲了在Eclipse中開發Perl代碼,咱們須要PadWalker Perl軟件包(能夠從CPAN下載),以及Perl 的EPIC Eclipse插件。 8.3.2在Eclipse中調試Python 須要安裝Pydev插件。安裝後,選擇Window-Preferences-Pydev,並將Python釋器的位 置(好比/lusrlbin/python)通知Eclipse. !使用Pydev Package Explorer做爲導航器透視圖。 由於沒有建立永久字節碼文件,因此沒有構建過程。 在創建運行/調試對話框時,注意以下內容:用但願從中開始執行的源文件名填充Main Module。 在Debug透視圖中,變量的值只能經過Variables視圖訪問,並且僅限於訪問局部變量。 Eclipse優於DDD的一個主要優勢是,DDD使用的底層調試引擎PDB不適用於多線程程序, 而Eclipse適用於這種程序。 8.4調試SWIG代碼 SWIG (Simplified Wrapper and Interface Generator)是一種流行的開源工具,用來將Java, Pert,Python和若干其餘解釋語言與C/C++接合。它容許使用解釋語言編寫應用程序的大部分代碼,並與程序員用C/C++編寫的特定部分結 合,從而加強性能。