我們學習的過程應該是一個積累的過程,有無到有,又少到多,而不能像下山的猴子。9月份的時候是第一次系統學習gcc, gdb, makefile(詳見前面部落格)。如今作為嵌入式系統的一個組成部分,決定重學一次,感覺理解加深了一些。
gcc編譯器
前提編輯工具的使用:vivimEmacs等。
理論篇
gcc可以使程式員靈活地控制編譯過程。編譯過程一般可以分為下面四個階段,每個階段分別調用不同的工具進行處理,9-18所示。
Linux系統中可執行檔有兩種格式。第一種格式是a.out格式,這種格式用於早期的Linux系統以及Unix系統的原始格式。a.out來自於Unix C編譯器預設的可執行檔名。當使用共用庫時,a.out格式就會發生問題。把a.out格式調整為共用庫是一種非常複雜的操作,由於這個原因,一種新的檔案格式被引入Unix系統5的第四版本和Solaris系統中。它被稱為可執行和串連的格式(ELF)。這種格式很容易實現共用庫。
ELF格式已經被Linux系統作為標準的格式採用。gcc編譯器產生的所有的二進位檔案都是ELF格式的檔案(即使可執行檔的預設名仍然是a.out)。較舊的a.out格式的程式仍然可以運行在支援ELF格式的系統上。
註:GCC支援數種調試和剖析選項。在這些選項裡,最常用的是-g和-pg選項。
實踐篇
gcc的使用格式:gcc [options][filenames]
其中filenames為所要編譯的程式源檔案。options見下文gcc的主要參數。
當使用gcc時,gcc會完成預先處理、編譯、彙編和串連。前三步分別產生目標檔案,串連時,把產生的目標檔案連結成可執行檔。gcc可以針對支援不同的來源程式檔案進行不同處理,檔案格式以檔案的尾碼來識別。
vi hello.c
一、常見步驟:
對於只有一個源檔案的簡單程式,常常只有編譯,運行兩步。
1,gcc hello.c -o hello
2,./hello
二、gcc編譯流程
gcc and g++分別是gnu的c & c++編譯器gcc/g++在執行編譯工作的時候,總共需要4步
hello.c(源碼)
1,hello.i產生預先處理檔案,
參數是“-E”,把hello.c -> hello.i。完整命令為gcc hello.c -o hello.i -E
2,hello.s編譯產生彙編檔案,
參數是“-S”,把hello.i -> hello.s。完整命令為gcc hello.i -o hello.s -S
3,hello.o將彙編檔案變為目標代碼,
參數是“-c”,把hello.s -> hello.o。完整命令為gcc hello.s -o hello.o -c
4,hello連結目標代碼,產生可執行程式,
參數無,把hello.o -> hello。完整命令為gcc hello.o -o hello
./hello(運行)
三、gcc的主要參數
1,母體參數
-E只進行先行編譯,不做其他處理
-S只是編譯不彙編,產生彙編代碼
-c只是編譯不連結,產生目標檔案“.o”
-o file把輸出檔案輸出到file裡
-g在可執行程式中包含標準調試資訊
-v列印出編譯器內部編譯各過程的命令列資訊和編譯器的版本
-I dir在標頭檔的搜尋路徑列表中添加dir目錄
-L dir在庫檔案的搜尋路徑列表中添加dir目錄
-static連結靜態庫
-llibrary串連名為library的庫檔案
2,警告和出錯參數。
-w關閉警告
-ansi顯示不符合ANSI C標準文法的警告資訊
-pedantic
-Wall跟蹤調試的有力工具,最後養成使用此參數的習慣。
3,尋找選項
gcc一般使用預設路徑尋找標頭檔和庫檔案。如果檔案所用的標頭檔或庫檔案不在預設目錄下,則編譯時間要指定它們的尋找路徑。
-I選項:指定標頭檔的搜尋目錄
例:gcc –I/export/home/st –o test1 test1.c
-L選項:指定庫檔案的搜尋目錄
例:gcc –L/usr/X11/R6/lib–o test1 test1.c
4,最佳化參數。
通過參數“-On”來產生最佳化代碼。其中n是一個代表最佳化層級的整數,較典型範圍是從0到2或3.數字越大最佳化的等級越高,程式運行速度越快。常用-O2,因為它在最佳化長度,編譯時間和代碼大學之間取得一個比較理想的平衡點。比較:1-8.c(代碼略)
gcc 1-8.c -o 1-8
time ./1-8
gcc 1-8.c -o 1-8 -O2
time ./1-8
註:如下場合應避免最佳化代碼。
(1)程式開發時。只有到軟體發行或開發結束時,才考慮對最終產生的程式碼進行最佳化。
(2)資源受限時。如記憶體資源非常緊張時(一些即時嵌入式裝置)。
(3)跟蹤調試時。最佳化可能會刪除、改寫或重組代碼,從而使跟蹤調試變得異常困難。
gdb調試器
理論篇
gdb調試的不是.c源檔案而是可執行檔,然而,並不是所有的可執行檔都可以用gdb調試。如果要讓產生的可執行檔可以用來調試,需在執行gcc指令編譯器時,加上-g參數,指定程式在編譯時間包含調試資訊。調試資訊包含程式裡的每個變數的類型和在可執行檔裡的地址映射以及原始碼的行號。gdb利用這些資訊使原始碼和機器碼相關聯。
實踐篇
gdb使用格式:gdb filename
其中,filename是要調試的可執行檔。用這種方式運行gdb可以直接指定想要調試的程式。這和啟動gdb後執行file filename(file命令:裝入想要調試的可執行檔)命令效果完全一樣。也可以用gdb去檢查一個因程式異常終止而產生的core檔案,或者與一個正在啟動並執行程式相連。
gdb支援很多的命令且能實現不同的功能,這些命令從簡單的檔案裝入到允許你檢查所調用的堆棧內容的複雜命令。
1,編輯源檔案。
例如,vi 1-9
添加如下內容
#include <stdio.h>
int min(int x, int y);
int main()
{
int a1, a2, min_int;
printf("please input the frist int number:/n");
scanf("%d", &a1);
printf("please input the second int number:/n");
scanf("%d", &a2);
min_int = min(a1, a2);
printf("the min number is:%d/n", min_int);
return 0;
}
int min(int x, int y)
{
if (x < y)
return x;
else
return y;
}
2,編譯時間要加上選項“-g”,這樣編譯出的可執行代碼才包含調試資訊。
gcc 1-9.c -o 1-9 -g
3,進入gdb調試環境。
gdb 1-9
斷行符號就進入了gdb偵錯模式。在gdb的調試環境中,提示符是“(gdb)”。
4,用gdb偵錯工具。
(1)查看源檔案
文法:'l'是list縮寫。list<行號>|<函數名>。查看原始碼,一次顯示10行
命令(gdb)l
(2)設定斷點
文法:break行號|函數名<條件運算式>
本例可以輸入命令
(gdb)b min在自訂的min函數出設定斷點。
(gdb)b 17功能同上
(3)查看斷點資訊
文法:info break# info命令是顯示XX資訊,常用有files,func,local,proc.
命令(gdb)info b
(4)運行程式
文法:run#執行當前被調試的程式
命令(gdb)r
註:gdb預設從第一行開始運行,如果要從出現中指定行開始運行,只需輸入“r”+行號。
(5)查看變數值
文法:p變數名。程式運行到斷點處會自動暫停,此時可查看指定變數的值。
本例命令
(gdb)p a1
(gdb)p a2
(gdb)p min_int
偵錯工具時,如需修改變數值,可在程式運行至斷點處是,輸入“set變數=設定值”。
例,給變數“a2”賦值11,輸入“set a2=11”。
(6)單步調試
文法:
“n”(next),若有函數,不進入函數調用。
“s”(step),若有函數,則進入函數調用。
(7)繼續運行程式
文法:continue
命令(gdb)c
(8)退出gdb調試環境。
文法:quit
命令(gdb)q
其他常用命令還有:
Tbreak命令:設定臨時斷點。它的文法與break相同。區別在於用tbreak設定的斷點執行一次之後立即消失。
watch命令:設定監視點,監視運算式的變化。
awatch命令:設定讀寫監視點。當要監視的運算式被讀或寫時將應用程式掛起。它的文法與watch命令相同。
rwatch命令:設定讀監視點,當監視運算式被讀時將程式掛起,等侍調試。此命令的文法與watch相同。
display命令:在應用程式每次停止運行時顯示運算式的值。
print命令:顯示運算式的值。
delete命令:刪除斷點。指定一個斷點號碼,則刪除指定斷點。不指定參數則刪除所有的斷點。
Shell命令:執行Linux Shell命令。
make命令:不退出gdb而重新編譯產生可執行檔。
註:這些指令可以在以後使用過程中查,不必死記硬背。以後用得多了,自然就會記住。