-o
參數指定了編譯生成的可執行文件名爲 gdblianxi,使用參數-g
表示將源代碼信息編譯到可執行文件中。若是不使用參數-g
,會給後面的GDB調試形成不便。上面的信息表示已經執行完「n = 1;」,並顯示下一條要執行的代碼爲第15行的「n++;」。前端
下面咱們分別在第21行打印處、tempFunction 函數開頭各設置一個斷點(分別使用命令「b 21」「b tempFunction」):linux
命令 | 解釋 | 示例 |
---|---|---|
file <文件名> | 加載被調試的可執行程序文件。 由於通常都在被調試程序所在目錄下執行GDB,於是文本名不須要帶路徑 |
(gdb) file gdblianxi |
r | Run的簡寫,運行被調試的程序。 若是此前沒有下過斷點,則執行完整個程序;若是有斷點,則程序暫停在第一個可用斷點處。 |
(gdb) r |
c | Continue的簡寫,繼續執行被調試程序,直至下一個斷點或程序結束。 | (gdb) c |
b <行號> b <函數名稱> b * <函數名稱> b * <代碼地址> d [編號] |
b: Breakpoint的簡寫,設置斷點。兩可使用「行號」「函數名稱」「執行地址」等方式指定斷點位置。 其中在函數名稱前面加「*」符號表示將斷點設置在「由編譯器生成的prolog代碼處」。若是不瞭解彙編,能夠不予理會此用法。 d: Delete breakpoint的簡寫,刪除指定編號的某個斷點,或刪除全部斷點。斷點編號從1開始遞增。 |
(gdb) b 8 (gdb) b main (gdb) b * main (gdb) b * 0x804835c (gdb) d |
s, n | s: 執行一行源程序代碼,若是此行代碼中有函數調用,則進入該函數; n: 執行一行源程序代碼,此行代碼中的函數調用也一併執行。 s 至關於其它調試器中的「Step Into (單步跟蹤進入)」 n 至關於其它調試器中的「Step Over (單步跟蹤)」 這兩個命令必須在有源代碼調試信息的狀況下才可使用(GCC編譯時使用「-g」參數)。 |
(gdb) s (gdb) n |
si, ni | si命令相似於s命令,ni命令相似於n命令。 所不一樣的是,這兩個命令(si/ni)所針對的是彙編指令,而s/n針對的是源代碼。 |
(gdb) si (gdb) ni |
p <變量名稱> | Print的簡寫,顯示指定變量(臨時變量或全局變量)的值。 | (gdb) p i (gdb) p nGlobalVar |
display ... undisplay <編號> |
display,設置程序中斷後欲顯示的數據及其格式。 例如,若是但願每次程序中斷後能夠看到即將被執行的下一條彙編指令,可使用命令「display /i $pc」 其中 $pc 表明當前彙編指令,/i 表示以十六進行顯示。當須要關心彙編代碼時,此命令至關有用。 undispaly,取消先前的display設置,編號從1開始遞增。 |
(gdb) display /i $pc (gdb) undisplay 1 |
i | Info的簡寫,用於顯示各種信息,詳情請查閱「help i」。 | (gdb) i r |
q | Quit的簡寫,退出GDB調試環境。 | (gdb) q |
help [命令名稱] | GDB幫助命令,提供對GDB名種命令的解釋說明。 若是指定了「命令名稱」參數,則顯示該命令的詳細說明;若是沒有指定參數,則分類顯示全部GDB命令,供用戶進一步瀏覽和查詢。 |
(gdb) help display |
(1)相比GDB,增長了語法加亮的代碼窗口,顯示在GDB窗口的上部,隨GDB的調試位置代碼同步顯示。程序員
(2)斷點設置可視化 。正則表達式
(3)在代碼窗口中可以使用GDB經常使用命令 。編程
(4)在代碼窗口可進行代碼查找,支持正則表達式 。小程序
(1)代碼窗口windows
調試時同步顯示被調試程序源代碼,自動標記出程序運行到的位置。當焦點在代碼窗口時,能夠瀏覽代碼、查找代碼以及執行命令 ,操做方式同vi 。經常使用命令以下:數組
i : 焦點切換到GDB窗口 。 o :打開文件選擇框,可選擇要顯示的代碼文件 。 空格 :設置/取消斷點 。 k:向上移動 j:向下移動 /:查找
(2)狀態條窗口數據結構
(3)GDB窗口多線程
CGDB的操做界面,同GDB ,按ESC鍵則焦點切換到代碼窗口 。
啓動&退出——啓動:cgdb;退出:在代碼窗口或GDB窗口,執行quit命令 。
代碼實現:
「(gdb)」表示GDB已經啓動,等待咱們輸入命令。此時程序並未開始運行,輸入「run」開始運行程序。這種方式在GDB內部運行程序:
List n,m表示顯示n到m行的代碼
設置斷點,break n,用step單步執行(這裏break 21):
彙編級的調試或跟蹤,須要用到display命令「display /i $pc」,如上表所示,
「display /i $pc」 其中 $pc 表明當前彙編指令,/i 表示以十六進行顯示。當須要關心彙編代碼時,此命令至關有用。 undispaly,取消先前的display設置,編號從1開始遞增。
而且之後程序每次中斷都將顯示下一條彙編指定(「si」命令用於執行一條彙編代碼——區別於「s」執行一行C代碼)
接下來咱們試一下命令「b * <函數名稱> 」。 爲了更簡明,有必要先刪除目前全部斷點(使用「d」命令——Delete breakpoint)
當被詢問是否刪除全部斷點時,輸入「y」並按回車鍵便可。
下面使用命令「b *main」在 main 函數的 prolog 代碼處設置斷點(prolog、epilog,分別表示編譯器在每一個函數的開頭和結尾自行插入的代碼):
此時可使用「i r」命令顯示寄存器中的當前值———「i r」即「Infomation Register」,
也能夠輸入「i r 寄存器名」顯示任意一個指定的寄存器值:
最後輸入命令「q」,退出(Quit)GDB調試環境
打開終端命令行窗口,輸入命令vi testddd.c,創建testddd.c文件做爲以後調試的文件:
在testddd.c文件中輸入一些C語言的程序數據,DDD工具能夠調試不少種程序設置基於的代碼,本次調試以C語言做爲說明對象。
把testddd.c文件編譯成能夠執行的文件testddd,命令:gcc -g -o testddd testddd.c
,注意必定要帶-g參數,不然生成的可執行文件中沒有必要的調試信息,最終使用DDD工具不能調試。
運行DDD調試工具,直接輸入命令ddd就能夠打開DDD工具。
DDD工具打開後以下圖所示,上面較大空白部分爲代碼區,和工具區,分割線下面是調試生成信息區。
點擊菜單欄上的「文件」----->「打開程序」,準備打開咱們上面準備的testddd.c文件
在打開程序框中,定位到咱們要調試的程序的目錄下,在Files列表下選擇咱們要調試 信息,以後點擊左下方的打開按鈕。
調試程序打開後,在代碼區能夠看到咱們的代碼,右邊的一些按鈕是咱們調試要用的工具。
在代碼區點鼠標右鍵,會彈出如圖所示的菜單:
咱們能夠給程序設置斷點等,點擊工具區裏面的Run按鈕,能夠執行程序,在下面的調試信息區能夠看到程序的執行結果。
如上圖所示:在鼠標右鍵點擊的地方設置了斷點,在下方調試信息生成區顯示了程序運行的輸入信息。
PS:也能夠在Terminal中輸入ddd 文件名來直接打開ddd調試該文件的界面:
在懷疑程序哪一個變量爲可疑變量時,能夠在控制檯輸入以下命令
或者在主窗口原程序中點擊某個變量如sum選中該變量,右擊後選擇display sum 選項就會看到該變量的值在主窗口的上方。 接着往下單步運行,屢次點擊工具欄中的「Step」按鈕,觀察變量sum的結果。
若是問題出在count上。這時點擊命令工具欄上的「Kill」按鈕將程序斷掉,把初始化sum的那一句改正確。從新運行以後,發現結果正確,調試過程完畢。
run 執行程序 step 單步調試 kill 殺死正在運行的程序 interrupt 退出這次調試回到原始狀態
段錯誤產生的緣由:
(1) 訪問不存在的內存地址
(2) 訪問系統保護的內存地址
(3) 訪問只讀的內存地址
(4) 棧溢出
下面以緣由一訪問不存在的內存地址爲例,進行實踐。
從輸出中能夠看出,程序收到SIGSEGV信號,觸發段錯誤,並提示0x00000000004004e六、調用main報的錯,在Derro.c中23行。而且在代碼窗口第23行被標記出來。
適用場景
僅當能肯定程序必定會發生段錯誤的狀況下使用。 當程序的源碼能夠得到的狀況下,使用-g參數編譯程序。 通常用於測試階段,生產環境下gdb會有反作用:使程序運行減慢,運行不夠穩定,等等。 即便在測試階段,若是程序過於複雜,gdb也不能處理。
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
進程的終止存在兩個可能:(1)父進程先於子進程終止(init進程領養) (2)子進程先於主進程終止。對於後者,系統內核爲子進程保留必定的狀態信息:進程ID、終止狀態、CPU時間等;當父進程調用wait或waitpid函數時,獲取這些信息; 當子進程正常或異常終止時,系統內核向其父進程發送SIGCHLD信號;缺省狀況下,父進程忽略該信號,或者提供一個該信號發生時即被調用的函數。
#include <stdlib.h> void exit(int status);
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *stat_loc);
返回:終止子進程的ID-成功;-1-出錯;statloc存儲子進程的終止狀態(一個整數);
若是沒有終止的子進程,可是有一個或多個正在執行的子進程,則該函數將堵塞,直到有一個子進程終止或者wait被信號中斷時,wait返回。 當調用該系統調用時,若是有一個子進程已經終止,則該系統調用當即返回,並釋放子進程全部資源。
pidt waitpid(pidt pid, int *statloc, int options);
-1:要求知道任何一個子進程的返回狀態(等待第一個終止的子進程); >0:要求知道進程號爲pid的子進程的狀態; <-1: wait for any child process whose process group ID is equal to the absolute value of pid.
Options最經常使用的選項是WNOHANG,它通知內核在沒有已終止進程時不要堵塞。
調用wait或waitpid函數時,正常狀況下,可能會有如下幾種狀況:
阻塞(若是其全部子進程都還在運行); 得到子進程的終止狀態並當即返回(若是一個子進程已終止,正等待父進程存取其終止狀態); 出錯當即返回(若是它沒有任何子進程)
set follow-fork-mode mode
來設置fork跟隨模式。parent gdb只跟蹤父進程,不跟蹤子進程,這是默認的模式。 child gdb在子進程產生之後只跟蹤子進程,放棄對父進程的跟蹤。
show follow-fork-mode
來查看目前的跟蹤模式。能夠看到目前使用的模式是parent。
有時,咱們想同時調試父進程和子進程,以上的方法就不能知足了。Linux提供了set detach-on-fork mode
命令來供咱們使用。其使用的mode能夠是如下的一種:
on 只調試父進程或子進程的其中一個(根據follow-fork-mode來決定),這是默認的模式。 off 父子進程都在gdb的控制之下,其中一個進程正常調試(根據follow-fork-mode來決定)
show detach-on-fork
顯示了目前是的detach-on-fork模式,如圖所示。new 當發生exec的時候,若是這個選項是new,則新建一個inferior給執行起來的子進程,而父進程的inferior仍然保留,當前保留的inferior的程序狀態是沒有執行。 same 當發生exec的時候,若是這個選項是same(默認值),由於父進程已經退出,因此自動在執行exec的inferior上控制子進程。
線程:運行在單一進程上下文中的邏輯流,由內核進行調度,共享同一進程的虛擬地址空間。
簡而言之,線程就是把一個進程分爲不少片,每一片均可以是一個獨立的流程。這已經明顯不一樣於多進程了,進程是一個拷貝的流程,而線程只是把一條河流截成不少條小溪。它沒有拷貝這些額外的開銷,可是僅僅是現存的一條河流,就被多線程技術幾乎無開銷地轉成不少條小流程,它的偉大就在於它少之又少的系統開銷。
(1)函數pthread_create用來建立一個線程,它的原型爲:
extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));
第一個參數爲指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。
(2)函數pthread_join用來等待一個線程的結束。函數原型爲:
2extern int pthread_join __P ((pthread_t __th, void **__thread_return));
第一個參數爲被等待的線程標識符,第二個參數爲一個用戶定義的指針,它能夠用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束爲止,當函數返回時,被等待線程的資源被收回。
(3)一個線程的結束有兩種途徑,一種是象咱們上面的例子同樣,函數結束了,調用它的線程也就結束了;另外一種方式是經過函數pthread_exit來實現。它的函數原型爲:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
惟一的參數是函數的返回代碼,只要pthread_ join中的第二個參數thread_ return不是NULL,這個值將被傳遞給 thread_return。
最後要說明的是,一個線程不能被多個線程等待,不然第一個接收到信號的線程成功返回,其他調用pthread_join的線程則返回錯誤代碼ESRCH。
Linux系統下的多線程遵循POSIX線程接口,稱爲pthread。編寫Linux下的多線程程序,須要使用頭文件pthread.h,鏈接時須要使用庫libpthread.a。Linux下pthread的實現是經過系統調用clone()來實現的。clone()是Linux所特有的系統調用,它的使用方式相似fork。
下面代碼示例:
代碼分析:主線程作本身的事情,生成2個子線程,task1爲分離,任其自生自滅,而task2仍是繼續送外賣,須要等待返回。
編譯運行:
這篇GDB的深刻研究是我作的第一個加分項目,到此算是告一段落了。在作以前,一直感受GDB調試是很困難的一件事,可是本身真正去實踐才發現它並無我想象中的那麼難。此次我完成了GDB代碼調試、CGDB代碼調試、彙編代碼調試、DDD代碼調試以及多進程與多線程的學習,中途也遇到過不少問題,可是經過查閱資料,參考以前的學長學姐的經驗最終都解決了。不知不覺,寫博客已經有一年的時間了,這門課程也快結束了。從一開始的煩躁到後面的適應再到習慣,本身自主學習的能力提高了太多。之後繼續加油!