GCChtml
gcc是linux系統集成的編譯器。在linux環境下編輯程序,首先須要克服的即是沒有集成開發環境的一鍵式操做所帶來的麻煩。這其中涉及命令行操做、編譯選項的設定、文件依賴關係的書寫(makefile)等問題。這裏主要介紹的是關於gcc的經常使用命令行參數及其相應的做用。(若編譯C++文件,則只需將下列命令的 gcc 換爲 g++,源文件的後綴應爲 .C/.cpp/.c++/.cc等)linux
基本格式:gcc [options] file1 file2... //若不加入參數,則按默認參數依次執行編譯、彙編和連接操做,生成的可執行文件名爲 a.out 經常使用參數:-E //只執行預處理操做 -S //只執行到編譯操做完成,不進行彙編操做,生成的是彙編文件(.s 或 .asm),內容爲彙編語言 -c //執行編譯和彙編,但不進行連接,即只生成可重定位目標文件(.o),爲二進制文件,不生成完整的可執行文件 -o filename //將操做後的內容輸出到filename指定的文件中 -static //對於支持動態連接的系統,使用靜態連接而不是動態連接進行連接操做 -g //編譯時生成debug有關的程序信息(供gdb使用) --save-temps //生成編譯過程的中間結果文件(包括預處理文件(x.ii)、彙編代碼(x.s)、目標文件(x.o)和最終的可執行文件) -I PATH //在PATH指定的目錄下尋找相關的include文件,參數中間不加空格 -lxx //其中xx爲指定函數庫,對於Linux環境下的函數庫,靜態庫後綴爲.a,動態庫後綴爲.so,通常庫名爲libxx.a或libxx.so,如加入libm.so庫,則使用參數-lm(去除lib和後綴.a\so) -L PATH //在PATH指定的目錄下尋找相關的庫文件,即-lxx指定待連接的庫,-L指定尋找該庫的路徑。不指定時搜索默認的庫函數路徑。
-std=xx //指定編譯使用的語言標準,如 -std=c++11 使用 c++11 標準 -x language //指定待編譯文件的語言,而不是由編譯器根據文件後綴自行判斷。即默認狀況下gcc根據文件後綴判斷使用的編程語言。例如使用文件名hello做爲源文件名是不合適的,應使用hello.c -Wall //輸出一些簡單的錯誤以及一些可能存在問題的警告 -Wextra //輸出-Wall不包含的警告等 -Werror //將警告視爲錯誤輸出
-Wl,option //經過該選項將參數 option 做爲後續連接器 ld 使用的參數
-Wl,rpath=/path/to/lib //爲連接器指定一個非默認的運行時庫的搜索路徑,運行採用了該選項編譯的程序時,連接器會在-rpath 指定的目錄中搜索所需的 so 庫文件,以將其載入內存中
-D name=definition //加入宏定義,若不指定def,則默認爲1 -O一、-O2 //規定編譯器的優化等級,優化級數越高執行效率通常越好,可是優化會改變原有程序結構,使得其彙編不易理解 //一些進行緩衝區溢出實驗時可能須要的選項 -fstack-protector\-fno-stack-protector //是否開啓堆棧保護,這裏的保護是在返回地址以前加入一個驗證值來確保返回地址不被破壞 -z execstack //啓用可執行棧,默認是禁用的 //(echo 0 >/proc/sys/kernel/randomize_va_space 關閉地址隨機化,這是一個單獨的命令,操做須要root權限)
上述編譯使用的參數通常在直接使用 gcc / g++ 時做爲命令行參數指定。若是想要在某些調用了 gcc / g++ 的編譯過程當中加入所需的編譯參數,即沒法直接經過命令行參數的方式指定編譯參數時,能夠經過全局變量的方式( Linux 環境下 )指定所需的編譯參數。具體而言,使用 CXXFLAGS 指定 g++ 編譯參數,使用 CFLAGS 指定 gcc 編譯參數。c++
export CXXFLAGS="-std=c++11" //經過全局變量指定額外的編譯參數 export CFLAGS="-std=c99"
舉例說明程序員
(1)將源文件編輯爲可執行文件編程
gcc hello.c //默認生成名爲a.out的可執行文件,這樣若在同一文件夾下編譯另外一個程序,則會a.out會被後來文件覆蓋
(2)編譯文件,並輸出到hello.s數組
gcc -S -o hello.s hello.c
(3)生成兩個可重定位目標文件dom
gcc -c hello.c world.c //生成hello.o與world.o,不進行連接操做,即僅進行預處理、編譯、彙編,而不進行連接
(4)對庫文件、目標文件進行鏈接操做編程語言
gcc -static hello.o world.o -lm -L /usr/lib //以靜態連接的方式,將hello.o、world.o以及libm.a庫中的相關目標文件連接,在/usr/lib文件夾下尋找目標庫
GDBide
gdb是Linux下一款功能強大的調試工具,它既能在反彙編過程當中充當一件稱手的工具,也能在程序debug過程當中爲爲程序員提供幫助,其惟一美中不足的是在Linux環境下沒有圖像界面(固然沒有功能的封裝也是其功能強大的緣由之一,並且如今的ddd也提供了GUI)。這裏主要記錄筆者從一些學習指導中學習的關於gdb命令和用法的總結。函數
爲何要使用GDB?
1.在Windows環境下,許多IDE以圖形界面提供相似gdb的功能,通常也較爲好用。可是一方面,gdb提供給使用者更大的自由,另外一方面gdb也是目前幾乎全部Linux發行版本的自帶軟件,簡單易得;
2.調試程序時儘可能減小對諸如printf等輸出函數的依賴。許多做者給出的解釋是從新修改代碼和編譯是一件麻煩的差事。這一點筆者起初也並不理解,以爲上述操做確實不算麻煩(...)。後來發現,對於一個單一文件,代碼不超過100行的文件,上述操做確實在可接受範圍。但對於文件衆多,工程量巨大的項目,修改代碼、從新編譯文件是一件極其耗時且麻煩的操做。若是在Windows環境下進行大工程的debug所須要的修改、重編譯所帶來的頻繁鼠標或快捷鍵操做還不能使你回心轉意的話,相信我,在Linux的命令行模式下進行相同的操做會讓你有所改變的;
3.習慣是逐漸養成的,不論好壞都是。或許只有逐漸在看起來不那麼方便的GDB中鍛鍊起來,你才能在不管什麼編譯環境中debug的駕輕就熟,可能那時,你會嫌棄圖形界面提供的工具不夠給力的;
調試策略
不管進行何種調試工做,大致的調試策略都相似:使用二分法的方式對錯誤地點進行定位;使用斷點(breakpoint),使程序運行至斷點處時中止以便觀察程序狀態;使用單步執行,使程序運行一條指令後中止,從而觀察數據的變化狀況和程序控制流;對一個變量預設特定的值,跟蹤其在程序運行中的變化規律等等。根據二八定律,使用20%的GDB指令,通常就能夠解決80%的程序bug。這裏介紹的是可以常規使用GDB的命令,更多高級或特殊指令,能夠參考GDB官方文檔Degugging with GDB。
爲了更好的使用gdb的調試功能,在編譯程序時需加入 -g 選項,由編譯器生成某些用於調試的信息。
GDB經常使用命令(此部分譯自 Guide to Faster,Less Frustrating Debugging,細節有改動)
開始/結束gdb
使用 gdb filename 啓動gdb,其中 filename 應爲可執行文件。
gdb a.out //使用gdb對a.out進行調試
gdb以命令行環境運行,進入gdb後,程序會等待用戶的指令並執行,直至用戶選擇退出。使用 q 或 Ctrl + d 退出。
運行(r)指令
使用命令 r 運行(run)程序,另外也能夠加入程序運行所須要的參數,若原命令行模式下的運行指令爲 ./a.out > test.txt ,則在gdb運行時應爲 r > test.txt。且若是在同一調試過程當中須要屢次運行程序(run),後續再執行時即可直接使用 r 指令,系統會默認使用以前的參數。
r //運行程序 r [options] arguments //帶參數運行程序,參數與命令行環境下一致,使用 r 替換源程序文件便可
List( l )指令
可使用指令 l 來列出源文件中的部分源代碼。(須要編譯時加入 -g 選項生成對應的編譯符號)
l 10 //輸出源程序10行及先後幾行的源碼,能夠方便進行調試。若要繼續查看,按回車鍵會繼續向下顯示。
對於多個文件的而言,能夠經過 l source_file_name.c:col (l 源文件名:行號)來指定所需查看的源代碼
l hello.c:10 //輸出hello.c在10行先後的代碼
也能夠以函數爲總體進行輸出,命令格式爲 l function_name
l main //輸出main函數的源代碼
斷點(b)和繼續執行(c)指令
指令 b 能夠在須要地方放置斷點,使得程序在指令的位置中止運行,指令格式爲 b 斷點位置。其中,斷點位置能夠是行號,也能夠是函數名(指定方式與 l 指令相似),也能夠是地址。
b 10 //在源代碼10行處放置斷點 b main //在main函數開始處放置斷點 b *0x80480000 //在存放在0x80480000處的指令處放置斷點,直接使用地址時須要使用 *地址 的格式 b 10 if a<10 //能夠在斷點中加入中斷執行的條件,表示當a < 10 時纔會中斷程序執行
在斷點處檢查完畢後,可使用 c 指定繼續指令的執行。使用指令 disable/enable 斷點號 能夠啓用/停用某斷點。使用指令 d 可刪除全部的斷點,d 1 刪除breakpoint 1.
disable/enable n //停用/啓用編號爲n的斷點 d //刪除全部斷點 d n //刪除標號爲n的斷點
觀測點(watch)指令
指令watch能夠爲某一表達式設置觀察點,當程序執行過程當中,當表達式的值發生改變時,則 gdb 會中斷程序執行,並顯示錶達式的變化狀況。
watch a //當變量 a 的值發生變化時,中斷程序執行 watch -l a // watch指令指定了 -l 參數時,會將指令所接的表達式的計算結果做爲地址,觀察該地址處的值的變化狀況 rwatch a // 當 a 的值被讀取時,中斷表達式的執行
顯示(disp)和打印(p)指令
disp指令(display)能夠在每次程序暫定時顯示指定變量的值,指令格式爲 disp 變量名。若輸入的變量爲數組名,則每次顯示數組的全部元素,若爲結構體,則輸出結構體的全部成員的值。
disp temp //在每次程序暫停時輸出指定的變量的值(確保程序在指定變量的做用域內執行,如某個在特定函數中的局部變量在程序進入該函數執行以前是沒法被顯示的) undisplay //取消全部disp指定的自動顯示變量
p指定(print)一樣將變量的值打印出來,用法與diap相似,但結果只顯示一次。
除變量外,p指令還能夠輸出給定寄存器、給定地址處的值。同時,能夠經過一些參數對打印格式進行規定,如 /x 表示以16進制格式打印值,/t表示以二進制格式打印值。
p $eax //打印寄存器%eax存儲的值,注意使用$標誌寄存器名稱 p /x ($ebp + 8) //以十六進制的格式打印%ebp + 8 的值 p /t 100 //以二進制格式輸出100的值 p *0x08048000 //輸出位於0x08048000處的數據(此處實際存放的是機器代碼),注意地址需使用 * 標誌,不然會被默認爲常數 p *(int *)0xxxxxxxx //將指定地址處數據按照整數格式輸出,這裏通常須要指出指針類型方便gdb解釋數據
其餘顯示類info命令
info reg //輸出全部寄存器的當前值 info frame //輸出棧幀的使用狀況 info b n //其中 n 爲指定的斷點號,顯示指定斷點的狀態信息,不加參數 n 時,會顯示全部的斷點的信息
內存檢查(examine)指令
x 指令用於檢查內存中某一區域的值,指令格式爲 :x fmt address 。其中address爲內存地址的表達式,fmt由 /重複次數+格式化字符+尺寸字符 組成。格式化字符有o(octal,八進制),x(hex,十六進制), d(decimal,十進制),u(unsigned decimal,無符號十進制),t(binary,二進制),f(float,浮點),a(address,地址),i(instruction,指令),c(char,字符),s(string,字符串).尺寸字符有 b(byte),h(halfword), w(word), g(giant, 8 bytes)
x /4xb *0xxxxxx //將指定地址區域連續的四個字節以十六進制的格式輸出,通常內存地址均使用 * 標識
格式化輸出(printf)指令
該指令的使用方法與C語言中的格式化輸出函數類似
printf" %d , %d \n",X,Y //對於兩個變量整形X,Y進行輸出
使用指令whatis能夠方便的得知所需對象的類型,如 whatis temp 會顯示出temp的類型定義,在調試時有用。
執行(s與n)指令
s 與 n 指令都是表示執行下一條指令指令的意思。可是,當遇到函數調用時,s 指令會進入函數調用內部進行執行,即下一步爲被調函數的第一指令,而 n 指令不進入函數調用內部,會將整個函數的執行過程看成一步執行。
回溯(bt)指令
回溯指令(backtrace)能夠查看程序內存訪問越界等錯誤信息,顯示程序出錯的位置,從而幫助定位程序錯誤。
設置(set)指令
設置指令 set 能夠將指定的變量的值修改成調試所須要的值。如對於一個int型的變量X,可使用 set X = 12 將變量的值進行設置。
使用宏定義
可使用宏定義對一些經常使用指令進行定義。指令格式 :define 宏名,並根據提示輸入宏定義,以end做爲結尾標誌。
另外,在使用gdb進行調試過程當中,可能免不了須要從新編譯程序,這時沒必要將gdb退出,只需待程序從新編譯後使用 r 指令從新運行程序,gdb會自動更新程序狀態,這樣能夠節約時間。