C/C++ 編譯器和偵錯程式以及靜態庫、動態庫使用匯總(轉)

post0發表於2007-08-11
C/C++ 編譯器和偵錯程式以及靜態庫、動態庫使用匯總(轉)[@more@]

經常的,有朋友問到有關unix下麵條是的技術。我整理了大多數的unix系統下面的常用的除錯工具的除錯技術的文章。希望對大家有所幫助。

另外靜態庫、動態庫也是問的頻率比較高的問題。在這裡也做了總結。

######大多數unix系統下面的偵錯程式的使用方法如下:######

***************gdb介紹*********************

GNU 的偵錯程式稱為 gdb,該程式是一個互動式工具,工作在字元模式。在 X Window 系統中,有一個 gdb 的

前端圖形工具,稱為 xxgdb。gdb 是功能強大的除錯程式,可完成如下的除錯任務:

* 設定斷點;

* 監視程式變數的值;

* 程式的單步執行;

* 修改變數的值。

在可以使用 gdb 除錯程式之前,必須使用 -g 選項編譯原始檔。可在 makefile 中如下定義 CFLAGS 變數:

CFLAGS = -g

執行 gdb 除錯程式時通常使用如下的命令:

gdb progname

在 gdb 提示符處鍵入help,將列出命令的分類,主要的分類有:

* aliases:命令別名

* breakpoints:斷點定義;

* data:資料檢視;

* files:指定並檢視檔案;

* internals:維護命令;

* running:程式執行;

* stack:呼叫棧檢視;

* statu:狀態檢視;

* tracepoints:跟蹤程式執行。

鍵入 help 後跟命令的分類名,可獲得該類命令的詳細清單。

*********gdb 的常用命令***************

命令 解釋

break NUM 在指定的行上設定斷點。

bt 顯示所有的呼叫棧幀。該命令可用來顯示函式的呼叫順序。

clear 刪除設定在特定原始檔、特定行上的斷點。其用法為:clear FILENAME:NUM。

continue 繼續執行正在除錯的程式。該命令用在程式由於處理訊號或斷點而

導致停止執行時。

display EXPR 每次程式停止後顯示錶達式的值。表示式由程式定義的變數組成。

file FILE 裝載指定的可執行檔案進行除錯。

help NAME 顯示指定命令的幫助資訊。

info break 顯示當前斷點清單,包括到達斷點處的次數等。

info files 顯示被除錯檔案的詳細資訊。

info func 顯示所有的函式名稱。

info local 顯示當函式中的區域性變數資訊。

info prog 顯示被除錯程式的執行狀態。

info var 顯示所有的全域性和靜態變數名稱。

kill 終止正被除錯的程式。

list 顯示原始碼段。

make 在不退出 gdb 的情況下執行 make 工具。

next 在不單步執行進入其他函式的情況下,向前執行一行原始碼。

print EXPR 顯示錶達式 EXPR 的值。

******gdb 使用範例************************

-----------------

清單 一個有錯誤的 C 源程式 bugging.c

-----------------

#include

#include

static char buff [256];

static char* string;

int main ()

{

printf ("Please input a string: ");

gets (string);

printf (" Your string is: %s ", string);

}

-----------------

上面這個程式非常簡單,其目的是接受使用者的輸入,然後將使用者的輸入列印出來。該程式使用了一個未經過初

始化的字串地址 string,因此,編譯並執行之後,將出現 Segment Fault 錯誤:

$ gcc -o test -g test.c

$ ./test

Please input a string: asfd

Segmentation fault (core dumped)

為了查詢該程式中出現的問題,我們利用 gdb,並按如下的步驟進行:

1.執行 gdb bugging 命令,裝入 bugging 可執行檔案;

2.執行裝入的 bugging 命令;

3.使用 where 命令檢視程式出錯的地方;

4.利用 list 命令檢視呼叫 gets 函式附近的程式碼;

5.唯一能夠導致 gets 函式出錯的因素就是變數 string。用 print 命令檢視 string 的值;

6.在 gdb 中,我們可以直接修改變數的值,只要將 string 取一個合法的指標值就可以了,為此,我們在第

11 行處設定斷點;

7.程式重新執行到第 11 行處停止,這時,我們可以用 set variable 命令修改 string 的取值;

8.然後繼續執行,將看到正確的程式執行結果。

執行 gcc/egcs

**********執行 gcc/egcs***********************

GCC 是 GNU 的 C 和 C++ 編譯器。實際上,GCC 能夠編譯三種語言:C、C++ 和 Object C(C 語言的一種物件導向擴充套件)。利用 gcc 命令可同時編譯並連線 C 和 C++ 源程式。

如果你有兩個或少數幾個 C 原始檔,也可以方便地利用 GCC 編譯、連線並生成可執行檔案。例如,假設你有

兩個原始檔 main.c 和 factorial.c 兩個原始檔,現在要編譯生成一個計算階乘的程式。

清單 factorial.c

-----------------------

#include

#include

int factorial (int n)

{

if (n <= 1)

return 1;

else

return factorial (n - 1) * n;

}

-----------------------

-----------------------

清單 main.c

-----------------------

#include

#include

int factorial (int n);

int main (int argc, char **argv)

{

int n;

if (argc < 2) {

printf ("Usage: %s n ", argv [0]);

return -1;

}

else {

n = atoi (argv[1]);

printf ("Factorial of %d is %d. ", n, factorial (n));

}

return 0;

}

-----------------------

利用如下的命令可編譯生成可執行檔案,並執行程式:

$ gcc -o factorial main.c factorial.c

$ ./factorial 5

Factorial of 5 is 120.

GCC 可同時用來編譯 C 程式和 C++ 程式。一般來說,C 編譯器透過原始檔的字尾名來判斷是 C 程式還是 C+

+ 程式。在 Linux 中,C 原始檔的字尾名為 .c,而 C++ 原始檔的字尾名為 .C 或 .cpp。

但是,gcc 命令只能編譯 C++ 原始檔,而不能自動和 C++ 程式使用的庫連線。因此,通常使用 g++ 命令來完

完成 C++ 程式的編譯和連線,該程式會自動呼叫 gcc 實現編譯。

假設我們有一個如下的 C++ 原始檔(hello.C):

#include

void main (void)

{

cout << "Hello, world!" << endl;

}

則可以如下呼叫 g++ 命令編譯、連線並生成可執行檔案:

$ g++ -o hello hello.C

$ ./hello

Hello, world!

**********************gcc/egcs 的主要選項*********

gcc 命令的常用選項

選項 解釋

-ansi 只支援 ANSI 標準的 C 語法。這一選項將禁止 GNU C 的某些特色,

例如 asm 或 typeof 關鍵詞。

-c 只編譯並生成目標檔案。

-DMACRO 以字串“1”定義 MACRO 宏。

-DMACRO=DEFN 以字串“DEFN”定義 MACRO 宏。

-E 只執行 C 預編譯器。

-g 生成除錯資訊。GNU 偵錯程式可利用該資訊。

-IDIRECTORY 指定額外的標頭檔案搜尋路徑DIRECTORY。

-LDIRECTORY 指定額外的函式庫搜尋路徑DIRECTORY。

-lLIBRARY 連線時搜尋指定的函式庫LIBRARY。

-m486 針對 486 進行程式碼最佳化。

-o FILE 生成指定的輸出檔案。用在生成可執行檔案時。

-O0 不進行最佳化處理。

-O 或 -O1 最佳化生成程式碼。

-O2 進一步最佳化。

-O3 比 -O2 更進一步最佳化,包括 inline 函式。

-shared 生成共享目標檔案。通常用在建立共享庫時。

-static 禁止使用共享連線。

-UMACRO 取消對 MACRO 宏的定義。

-w 不生成任何警告資訊。

-Wall 生成所有警告資訊。

#######SCO UNIX下面dbaxtra的除錯技術#########

在sco unix下程式設計大多離不開C語言,即使是資料庫應用也有很多是與c搭配使用的,例如informix esql/c 就可以在c語言中嵌入sql 語句。很多人認為在unix下寫程式是件很痛苦的事情,其中一個很重要原因是不知道在unix下怎樣除錯程式。其實在sco unix原始碼偵錯程式是dbxtra或dbXtra,linux下是gdb。它們類似turbo c的偵錯程式,可以跟蹤原始碼變數。在unix 下除錯程式有如下傳統方法

---- 一、在要除錯語句之前,輸出要除錯的變數,利用printf()函式。

---- 二、寫日誌檔案,把結果輸出到檔案中避免螢幕混亂,利用fprintf()函式。

---- 三、利用sco 內建偵錯程式dbxtra或dbXtra。

---- dbxtra 適用字元介面,在sco unix的圖形介面用dbXtra。(編按:請注意大小寫)

以下是dbxtra基本命令:

c cont 在斷點後繼續執行

d delete 刪除所設斷點

h help 幫助

e edit 編輯源程式

n next 源程式區的內容向下翻一屏。

p print 顯示變數

q quit 退出dbxtra

r run 執行程式,直到遇上設定的斷點

rr rerun 再次執行

s step 單步執行

st stop 設定斷點

j status 顯示當前斷點

t where 顯示當前狀態,列出所有設定的變數值

di display 開顯示窗,用於檢視變數

ud undisplay 刪除顯示窗的條目

f forward 源程式區的內容向上 翻一屏。

B backward 源程式區的內容向下 翻一屏。

Stopi stop inst 設定斷點

tracei trace inst跟蹤子程式

dbxtra [options] [objectfile ]

---- dbxtra 在啟動時有個引數-Idir值得一提.我們在編寫一個較大程式的時候,通常源程式和編譯生成的可執行檔案都放在不同的目錄中,這樣便於管理。預設dbxtra將在可執行檔案所在的目錄下找匹配c的源程式。當我們啟動時,指定-I引數,dbxtra就會到我們指定的目錄下找匹配的c程式。 例如:

---- dbxtra -I"workc" program1

---- 源程式在用cc編譯時要帶上-g 引數,這樣是加上符號表等除錯資訊。只有這樣編譯過的檔案,dbxtra才可以除錯。除錯資訊使原始碼和機器碼關聯。

---- 下面這個C程式輸出結果和我們的預想結果不一樣,說明某些地方有錯誤。我們用偵錯程式來除錯它:

---- 程式一:

t.c

main()

{ int i=10 ,*p1;

float j=1.5,*p2;

p1=&

p2=&

p2=p1;

printf("%d,%d ",*p1,*p2);

}

首先帶上-g引數編譯 cc -g -o t t.c

啟動偵錯程式 dbxtra t

螢幕顯示:

1.main()

2.{ int i=10 ,*p1;

3. float j=1.5,*p2;

4. p1=&

5. p2=&

6. p2=p1;

7. printf("%d,%d ",*p1,*p2);

8.}

C[browse] File:t.c Func.-

Readubg symbolic information

Type 'help' for help

(dbxtra)

(dbxtra)

設定斷點:

(dbxtra)stop at 5

執行:

(dbxtra) run

程式自動在第5行停下。

這時我們可以看變數的值。

(dbxtra) print *p1

單步執行。

(dbxtra) step

程式將執行第5行原始碼,指標將移到第6行。

(dbxtra) print *p2

(dbxtra) step

程式執行了第6行原始碼後,將指標移到第7行。

(dbxtra) print *p1 , *p2

---- 我們發現 在執行了第6行原始碼後,*p1,*p2的值就不對了,所以問題就出在第6行上。仔細檢查後發現指標p1指向整型,指標p2指向實型。它們之間的賦值要進行強制型別轉換。這種錯誤在C程式中是很常見的。

---- 有時我們在除錯一些程式時,要在整個程式執行中時刻監視莫些變數的值,例如程式一中我們要時刻了解*p1,*p2的值,除了在每一行程式執行完後,打print *p1,*p2外,還可以開一個顯示視窗。

---- (dbxtra)display *p1,*p2

---- 用undisplay 刪掉不想要的變數。

---- 有些程式執行時要帶引數,mycat /etc/passwd 在除錯時候

---- (dbxtra) run '/etc/passwd'

---- 再執行時,無需再寫一遍引數。

---- (dbxtra) rerun

---- 在涉及到curses庫程式設計或螢幕有大量的人機介面時,為了除錯方便,我們可以把程式輸出結果重定向到個虛屏。

---- (dbxtra) run >/dev/tty03

---- 當然要先把tty03 disable 掉。(disable tty03)

#######建立和使用靜態庫#########

詳細的使用情況,請大家man手冊,這裡只介紹一下。靜態庫相對的比較簡單。

建立一個靜態庫是相當簡單的。通常使用 ar 程式把一些目標檔案(.o)組合在一起,

成為一個單獨的庫,然後執行 ranlib,以給庫加入一些索引資訊。

########建立和使用共享庫#########

特殊的編譯和連線選項

-D_REENTRANT 使得前處理器符號 _REENTRANT 被定義,這個符號啟用一些宏特性。

-fPIC 選項產生位置獨立的程式碼。由於庫是在執行的時候被調入,因此這個

選項是必需的,因為在編譯的時候,裝入記憶體的地址還不知道。如果

不使用這個選項,庫檔案可能不會正確執行。

-shared 選項告訴編譯器產生共享庫程式碼。

-Wl,-soname -Wl 告訴編譯器將後面的引數傳遞到聯結器。而 -soname 指定了

共享庫的 soname。

# 可以把庫檔案複製到 /etc/ld.so.conf 中列舉出的任何目錄中,並以

root 身份執行 ldconfig;或者

# 執行 export LD_LIBRARY_PATH='pwd',它把當前路徑加到庫搜尋路徑中去。

#######使用高階共享庫特性#########

1. ldd 工具

ldd 用來顯示執行檔案需要哪些共享庫, 共享庫裝載管理器在哪裡找到了需要的共享庫.

2. soname

共享庫的一個非常重要的,也是非常難的概念是 soname——簡寫共享目標名(short for shared object name)。這是一個為共享庫(.so)檔案而內嵌在控制資料中的名字。如前面提到的,每一個程式都有一個需要使用的庫的清單。這個清單的內容是一系列庫的 soname,如同 ldd 顯示的那樣,共享庫裝載器必須找到這個清單。

soname 的關鍵功能是它提供了相容性的標準。當要升級系統中的一個庫時,並且新庫的 soname 和老的庫的 soname 一樣,用舊庫連線生成的程式,使用新的庫依然能正常執行。這個特性使得在 Linux 下,升級使用共享庫的程式和定位錯誤變得十分容易。

在 Linux 中,應用程式透過使用 soname,來指定所希望庫的版本。庫作者也可以透過保留或者改變 soname 來宣告,哪些版本是相互相容的,這使得程式設計師擺脫了共享庫版本衝突問題的困擾。

檢視/usr/local/lib 目錄,分析 MiniGUI 的共享庫檔案之間的關係

3. 共享庫裝載器

當程式被呼叫的時候,Linux 共享庫裝載器(也被稱為動態聯結器)也自動被呼叫。它的作用是保證程式所需要的所有適當版本的庫都被調入記憶體。共享庫裝載器名字是 ld.so 或者是 ld-linux.so,這取決於 Linux libc 的版本,它必須使用一點外部互動,才能完成自己的工作。然而它接受在環境變數和配置檔案中的配置資訊。

檔案 /etc/ld.so.conf 定義了標準系統庫的路徑。共享庫裝載器把它作為搜尋路徑。為了改變這個設定,必須以 root 身份執行 ldconfig 工具。這將更新 /etc/ls.so.cache 檔案,這個檔案其實是裝載器內部使用的檔案之一。

可以使用許多環境變數控制共享庫裝載器的操作(表1-4+)。

表 1-4+ 共享庫裝載器環境變數

變數 含義

LD_AOUT_LIBRARY_PATH 除了不使用 a.out 二進位制格式外,與 LD_LIBRARY_PATH 相同。

LD_AOUT_PRELOAD 除了不使用 a.out 二進位制格式外,與 LD_PRELOAD 相同。

LD_KEEPDIR 只適用於 a.out 庫;忽略由它們指定的目錄。

LD_LIBRARY_PATH 將其他目錄加入庫搜尋路徑。它的內容應該是由冒號

分隔的目錄列表,與可執行檔案的 PATH 變數具有相同的格式。

如果呼叫設定使用者 ID 或者程式 ID 的程式,該變數被忽略。

LD_NOWARN 只適用於 a.out 庫;當改變版本號是,發出警告資訊。

LD_PRELOAD 首先裝入使用者定義的庫,使得它們有機會覆蓋或者重新定義標準庫。

使用空格分開多個入口。對於設定使用者 ID 或者程式 ID 的程式,

只有被標記過的庫才被首先裝入。在 /etc/ld.so.perload 中指定

了全域性版本號,該檔案不遵守這個限制。

4. 使用 dlopen

另外一個強大的庫函式是 dlopen()。該函式將開啟一個新庫,並把它裝入記憶體。該函式主要用來載入庫中的符號,這些符號在編譯的時候是不知道的。比如 Apache Web 伺服器利用這個函式在執行過程中載入模組,這為它提供了額外的能力。一個配置檔案控制了載入模組的過程。這種機制使得在系統中新增或者刪除一個模組時,都不需要重新編譯了。

可以在自己的程式中使用 dlopen()。dlopen() 在 dlfcn.h 中定義,並在 dl 庫中實現。它需要兩個引數:一個檔名和一個標誌。檔名可以是我們學習過的庫中的 soname。標誌指明是否立刻計算庫的依賴性。如果設定為 RTLD_NOW 的話,則立刻計算;如果設定的是 RTLD_LAZY,則在需要的時候才計算。另外,可以指定 RTLD_GLOBAL,它使得那些在以後才載入的庫可以獲得其中的符號。

當庫被裝入後,可以把 dlopen() 返回的控制程式碼作為給 dlsym() 的第一個引數,以獲得符號在庫中的地址。使用這個地址,就可以獲得庫中特定函式的指標,並且呼叫裝載庫中的相應函式

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-944579/,如需轉載,請註明出處,否則將追究法律責任。

相關文章