GDB 調試程序 詳解 使用實例

用GDB調試程序

GDB概述
————

GDB是GNU開源組織發布的一個強大的UNIX下的程序調試工具。或許,各位比較喜歡那種圖形界面方式的,像VC、BCB等IDE的調試,但如果你是在UNIX平臺下做軟件,你會發現GDB這個調試工具有比VC、BCB的圖形化調試器更強大的功能。所謂“寸有所長,尺有所短”就是這個道理。

一般來說,GDB主要幫忙你完成下面四個方面的功能:

???1、啟動你的程序,可以按照你的自定義的要求隨心所欲的運行程序。
???2、可讓被調試的程序在你所指定的調置的斷點處停住。(斷點可以是條件表達式)
???3、當程序被停住時,可以檢查此時你的程序中所發生的事。
???4、動態的改變你程序的執行環境。

從上面看來,GDB和一般的調試工具沒有什么兩樣,基本上也是完成這些功能,不過在細節上,你會發現GDB這個調試工具的強大,大家可能比較習慣了圖形化的調試工具,但有時候,命令行的調試工具卻有著圖形化工具所不能完成的功能。讓我們一一看來。


一個調試示例
——————

源程序:tst.c

????1 #include <stdio.h>
????2
????3 int func(int n)
????4 {
????5????????int sum=0,i;
????6????????for(i=0; i<n; i++)
????7????????{
????8????????????????sum+=i;
????9????????}
???10????????return sum;
??? 11 }
??? 12
??? 13
??? 14main()
??? 15 {
???16????????int i;
???17????????long result = 0;
???18????????for(i=1; i<=100; i++)
???19????????{
???20????????????????result += i;
???21????????}
??? 22
???23???????printf("result[1-100] = %d \n", result );
???24???????printf("result[1-250] = %d \n", func(250) );
??? 25}

編譯生成執行文件:(Linux下)
???hchen/test> cc -g tst.c -o tst

使用GDB調試:

hchen/test> gdbtst? <---------- 啟動GDB
GNU gdb 5.1.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License,and you are
welcome to change it and/or distribute copies of it under certainconditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.? Type"show warranty" for details.
This GDB was configured as "i386-suse-linux"...
(gdb)l????<-------------------- l命令相當于list,從第一行開始例出原碼。
1???????#include <stdio.h>
2
3???????int func(int n)
4???????{
5???????????????int sum=0,i;
6???????????????for(i=0; i<n; i++)
7???????????????{
8???????????????????????sum+=i;
9???????????????}
10??????????????return sum;
(gdb)??????<-------------------- 直接回車表示,重復上一次命令
11??????}
12
13
14??????main()
15??????{
16??????????????int i;
17??????????????long result = 0;
18??????????????for(i=1; i<=100; i++)
19??????????????{
20??????????????????????result +=i;???
(gdb) break16???<-------------------- 設置斷點,在源程序第16行處。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func?<-------------------- 設置斷點,在函數func()入口處。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break?<-------------------- 查看斷點信息。
NumType??????????Disp EnbAddress???What
1??breakpoint????keep y?? 0x08048496 in main attst.c:16
2??breakpoint????keep y?? 0x08048456 in func attst.c:5
(gdb)r??????????<--------------------- 運行程序,run命令簡寫
Starting program: /home/hchen/test/tst

Breakpoint 1, main () attst.c:17???<---------- 在斷點處停住。
17??????????????long result = 0;
(gdb)n?????????<--------------------- 單條語句執行,next命令簡寫。
18??????????????for(i=1; i<=100; i++)
(gdb) n
20??????????????????????result += i;
(gdb) n
18??????????????for(i=1; i<=100; i++)
(gdb) n
20??????????????????????result += i;
(gdb)c?????????<--------------------- 繼續運行程序,continue命令簡寫。
Continuing.
result[1-100] =5050??????<----------程序輸出。

Breakpoint 2, func (n=250) attst.c:5
5???????????????int sum=0,i;
(gdb) n
6???????????????for(i=1; i<=n; i++)
(gdb) pi???????<--------------------- 打印變量i的值,print命令簡寫。
$1 = 134513808
(gdb) n
8???????????????????????sum+=i;
(gdb) n
6???????????????for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8???????????????????????sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6???????????????for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb)bt???????<--------------------- 查看函數堆棧。
#0? func (n=250) at tst.c:5
#1? 0x080484e4 in main () at tst.c:24
#2? 0x400409ed in __libc_start_main () from/lib/libc.so.6
(gdb) finish???<--------------------- 退出函數。
Run till exit from #0? func (n=250) attst.c:5
0x080484e4 in main () at tst.c:24
24?????????????printf("result[1-250] = %d \n", func(250) );
Value returned is $6 = 31375
(gdb)c????<--------------------- 繼續運行。
Continuing.
result[1-250] =31375???<----------程序輸出。

Program exited with code 027.<--------程序退出,調試結束。
(gdb)q????<--------------------- 退出gdb。
hchen/test>

好了,有了以上的感性認識,還是讓我們來系統地認識一下gdb吧。

?


使用GDB
————

一般來說GDB主要調試的是C/C++的程序。要調試C/C++的程序,首先在編譯時,我們必須要把調試信息加到可執行文件中。使用編譯器(cc/gcc/g++)的-g 參數可以做到這一點。如:

???> cc -g hello.c -o hello
???> g++ -g hello.cpp -o hello

如果沒有-g,你將看不見程序的函數名、變量名,所代替的全是運行時的內存地址。當你用-g把調試信息加入之后,并成功編譯目標代碼以后,讓我們來看看如何用gdb來調試他。

啟動GDB的方法有以下幾種:

???1、gdb <program>
??????program也就是你的執行文件,一般在當然目錄下。

???2、gdb <program> core
??????用gdb同時調試一個運行程序和core文件,core是程序非法執行后core dump后產生的文件。

???3、gdb <program><PID>
??????如果你的程序是一個服務程序,那么你可以指定這個服務程序運行時的進程ID。gdb會自動attach上去,并調試他。program應該在PATH環境變量中搜索得到。

?

GDB啟動時,可以加上一些GDB的啟動開關,詳細的開關可以用gdb-help查看。我在下面只例舉一些比較常用的參數:

???-symbols <file>
??? -s<file>
???從指定文件中讀取符號表。

???-se file
???從指定文件中讀取符號表信息,并把他用在可執行文件中。

???-core <file>
??? -c<file>
??? 調試時coredump的core文件。

???-directory <directory>
??? -d<directory>
???加入一個源文件的搜索路徑。默認搜索路徑是環境變量中PATH所定義的路徑。



GDB的命令概貌
———————

啟動gdb后,就你被帶入gdb的調試環境中,就可以使用gdb的命令開始調試程序了,gdb的命令可以使用help命令來查看,如下所示:

???/home/hchen> gdb
??? GNU gdb5.1.1
??? Copyright2002 Free Software Foundation, Inc.
??? GDB is freesoftware, covered by the GNU General Public License, and youare
??? welcome tochange it and/or distribute copies of it under certainconditions.
??? Type "showcopying" to see the conditions.
??? There isabsolutely no warranty for GDB.? Type "showwarranty" for details.
??? This GDB wasconfigured as "i386-suse-linux".
??? (gdb)help
??? List ofclasses of commands:

???aliases -- Aliases of other commands
??? breakpoints-- Making program stop at certain points
??? data --Examining data
??? files --Specifying and examining files
??? internals --Maintenance commands
??? obscure --Obscure features
??? running --Running the program
??? stack --Examining the stack
??? status --Status inquiries
??? support --Support facilities
??? tracepoints-- Tracing of program execution without stopping the program
??? user-defined-- User-defined commands

???Type "help" followed by a class name for a list of commands in thatclass.
??? Type "help"followed by command name for full documentation.
??? Command nameabbreviations are allowed if unambiguous.
???(gdb)

gdb的命令很多,gdb把之分成許多個種類。help命令只是例出gdb的命令種類,如果要看種類中的命令,可以使用help <class> 命令,如:helpbreakpoints,查看設置斷點的所有命令。也可以直接help<command>來查看命令的幫助。


gdb中,輸入命令時,可以不用打全命令,只用打命令的前幾個字符就可以了,當然,命令的前幾個字符應該要標志著一個唯一的命令,在Linux下,你可以敲擊兩次TAB鍵來補齊命令的全稱,如果有重復的,那么gdb會把其例出來。
???
???示例一:在進入函數func時,設置一個斷點。可以敲入break func,或是直接就是b func
??? (gdb) bfunc
??? Breakpoint 1at 0x8048458: file hello.c, line 10.
?
???示例二:敲入b按兩次TAB鍵,你會看到所有b打頭的命令:
??? (gdb)b
???backtrace?break?????bt
???(gdb)

???示例三:只記得函數的前綴,可以這樣:
??? (gdb) bmake_ <按TAB鍵>
???(再按下一次TAB鍵,你會看到:)
???make_a_section_from_file????make_environ
???make_abs_section????????????make_function_type
???make_blockvector????????????make_pointer_type
???make_cleanup????????????????make_reference_type
???make_command????????????????make_symbol_completion_list
??? (gdb) bmake_
???GDB把所有make開頭的函數全部例出來給你查看。

???示例四:調試C++的程序時,有可以函數名一樣。如:
??? (gdb) b'bubble( M-?
???bubble(double,double)???bubble(int,int)
??? (gdb) b'bubble(
???你可以查看到C++中的所有的重載函數及參數。(注:M-?和“按兩次TAB鍵”是一個意思)

要退出gdb時,只用發quit或命令簡稱q就行了。

?

GDB中運行UNIX的shell程序
————————————

在gdb環境中,你可以執行UNIX的shell的命令,使用gdb的shell命令來完成:

???shell <command string>
???調用UNIX的shell來執行<commandstring>,環境變量SHELL中定義的UNIX的shell將會被用來執行<commandstring>,如果SHELL沒有定義,那就使用UNIX的標準shell:/bin/sh。(在Windows中使用Command.com或cmd.exe)

還有一個gdb命令是make:
??? make<make-args>
???可以在gdb中執行make命令來重新build自己的程序。這個命令等價于“shell make<make-args>”。

?


在GDB中運行程序
————————

當以gdb<program>方式啟動gdb后,gdb會在PATH路徑和當前目錄中搜索<program>的源文件。如要確認gdb是否讀到源文件,可使用l或list命令,看看gdb是否能列出源代碼。

在gdb中,運行程序使用r或是run命令。程序的運行,你有可能需要設置下面四方面的事。

1、程序運行參數。
??? set args可指定運行時參數。(如:set args 10 20 30 40 50)
??? show args命令可以查看設置好的運行參數。

2、運行環境。
??? path<dir> 可設定程序的運行路徑。
??? show paths查看程序的運行路徑。
??? setenvironment varname [=value] 設置環境變量。如:set env USER=hchen
??? showenvironment [varname] 查看環境變量。

3、工作目錄。
??? cd<dir> 相當于shell的cd命令。
??? pwd顯示當前的所在目錄。

4、程序的輸入輸出。
??? infoterminal 顯示你程序用到的終端的模式。
???使用重定向控制程序輸出。如:run > outfile
???tty命令可以指寫輸入輸出的終端設備。如:tty /dev/ttyb


調試已運行的程序
————————

兩種方法:
1、在UNIX下用ps查看正在運行的程序的PID(進程ID),然后用gdb<program> PID格式掛接正在運行的程序。
2、先用gdb<program>關聯上源代碼,并進行gdb,在gdb中用attach命令來掛接進程的PID。并用detach來取消掛接的進程。

?

暫停 / 恢復程序運行
—————————

調試程序中,暫停程序運行是必須的,GDB可以方便地暫停程序的運行。你可以設置程序的在哪行停住,在什么條件下停住,在收到什么信號時停往等等。以便于你查看運行時的變量,以及運行時的流程。

當進程被gdb停住時,你可以使用info program來查看程序的是否在運行,進程號,被暫停的原因。

在gdb中,我們可以有以下幾種暫停方式:斷點(BreakPoint)、觀察點(WatchPoint)、捕捉點(CatchPoint)、信號(Signals)、線程停止(ThreadStops)。如果要恢復程序運行,可以使用c或是continue命令。


一、設置斷點(BreakPoint)
???
???我們用break命令來設置斷點。正面有幾點設置斷點的方法:
???
??? break<function>
???????在進入指定函數時停住。C++中可以使用class::function或function(type,type)格式來指定函數名。

???break <linenum>
???????在指定行號停住。

???break +offset
??? break-offset
???????在當前行號的前面或后面的offset行停住。offiset為自然數。

???break filename:linenum
???????在源文件filename的linenum行處停住。

???break filename:function
???????在源文件filename的function函數的入口處停住。

???break *address
???????在程序運行的內存地址處停住。

???break
???????break命令沒有參數時,表示在下一條指令處停住。

???break ... if <condition>
???????...可以是上述的參數,condition表示條件,在條件成立時停住。比如在循環境體中,可以設置break ifi=100,表示當i為100時停住程序。

???查看斷點時,可使用info命令,如下所示:(注:n表示斷點號)
??? infobreakpoints [n]
??? info break[n]
???

二、設置觀察點(WatchPoint)
???
???觀察點一般來觀察某個表達式(變量也是一種表達式)的值是否有變化了,如果有變化,馬上停住程序。我們有下面的幾種方法來設置觀察點:
???
??? watch<expr>
???????為表達式(變量)expr設置一個觀察點。一量表達式值有變化時,馬上停住程序。
???????
??? rwatch<expr>
???????當表達式(變量)expr被讀時,停住程序。
???????
??? awatch<expr>
???????當表達式(變量)的值被讀或被寫時,停住程序。
???
??? infowatchpoints
???????列出當前所設置了的所有觀察點。


三、設置捕捉點(CatchPoint)

???你可設置捕捉點來補捉程序運行時的一些事件。如:載入共享庫(動態鏈接庫)或是C++的異常。設置捕捉點的格式為:
???
??? catch<event>
???????當event發生時,停住程序。event可以是下面的內容:
???????1、throw 一個C++拋出的異常。(throw為關鍵字)
???????2、catch 一個C++捕捉到的異常。(catch為關鍵字)
???????3、exec 調用系統調用exec時。(exec為關鍵字,目前此功能只在HP-UX下有用)
???????4、fork 調用系統調用fork時。(fork為關鍵字,目前此功能只在HP-UX下有用)
???????5、vfork 調用系統調用vfork時。(vfork為關鍵字,目前此功能只在HP-UX下有用)
???????6、load 或 load <libname>載入共享庫(動態鏈接庫)時。(load為關鍵字,目前此功能只在HP-UX下有用)
???????7、unload 或 unload <libname>卸載共享庫(動態鏈接庫)時。(unload為關鍵字,目前此功能只在HP-UX下有用)

???tcatch <event>
???????只設置一次捕捉點,當程序停住以后,應點被自動刪除。


使用gdb調試

我們將會使用GNU調試器,gdb,來調試這個程序。這是一個可以免費得到并且可以用于多個Unix平臺的功能強大的調試器。他也是Linux系統上的默認調試器。gdb已經被移植到許多其他平臺上,并且可以用于調試嵌入式實時系統。

啟動gdb

讓我們重新編譯我們的程序用于調試并且啟動gdb。

$ cc -g -o debug3 debug3.c
$ gdb debug3
GNU gdb 5.2.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License,and you are
welcome to change it and/or distribute copies of it under certainconditions.
Type “show copying” to see the conditions.
There is absolutely no warranty for GDB. Type “show warranty” fordetails.
This GDB was configured as “i586-suse-linux”...
(gdb)

gdb具有豐富的在線幫助,以及可以使用info程序進行查看或是在Emacs中進行查看的完整手冊。

(gdb) help
List of classes of commands:
aliases — Aliases of other commands
breakpoints — Making program stop at certain points
data — Examining data
files — Specifying and examining files
internals — Maintenance commands
obscure — Obscure features
running — Running the program
stack — Examining the stack
status — Status inquiries
support — Support facilities
tracepoints — Tracing of program execution without stopping theprogram
user-defined — User-defined commands
Type “help” followed by a class name for a list of commands in thatclass.
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)

gdb本身是一個基于文本的程序,但是他確實了一些有助于重復任務的簡化操作。許多版本具有一個命令行編輯歷史,從而我們可以在命令歷史中進行滾動并且再次執行相同的命令。所有的版本都支持一個"空白命令",敲擊Enter會再次執行上一條命令。當我們使用step或是next命令在一個程序中分步執行特殊有用。

運行一個程序

我們可以使用run命令執行這個程序。我們為run命令所指定的所有命令都作為參數傳遞給程序。在這個例子中,我們并不需要任何參數。

我們在這里假設我們的系統與作者的類似,也產生了內存錯誤的錯誤信息。如果不是,請繼續閱讀。我們就會發現當我們自己的程序生成一個內存錯誤時應怎么辦。如果我們并不沒有得到內存錯誤信息,但是我們在閱讀本書時希望運行這個例子,我們可以拾起第一個內存訪問問題已經被修復的debug4.c。

(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug3
Program received signal SIGSEGV, Segmentation fault.
0x080483c0 in sort (a=0x8049580, n=5) at debug3.c:23
23?

???????????????????????????if(a[j].key > a[j+1].key) {
(gdb)

如前面一樣,我們程序并沒有正確運行。當程序失敗時,gdb會向我們顯示原因以及位置。現在我們可以檢測問題背后的原因。

依據于我們的內核,C庫,以及編譯器選項,我們所看到的程序錯誤也許有所不同,例如,也許當數組元素交換時是在25行,而不是數組元素比較時的23行。如果是這種情況,我們也許會看到如下的輸出:

Program received signal SIGSEGV, Segmentation fault.
0x8000613 in sort (a=0x8001764, n=5) at debug3.c:25
25????????????????????????????????????a[j] = a[j+1];

我們仍然可以遵循如下的gdb例子會話。

棧追蹤

程序已經在源文件debug3.c的第23行處的sort函數停止。如果我們并沒有使用額外的調試信息來編譯這個程序,我們就不能看到程序在哪里失敗,也不能使用變量名來檢測數據。

我們可以通過使用backstrace命令來查看我們是如何到達這個位置的。

(gdb) backtrace
#0 0x080483c0 in sort (a=0x8049580, n=5) at debug3.c:23
#1 0x0804849b in main () at debug3.c:37
#2 0x400414f2 in __libc_start_main () from /lib/libc.so.6
(gdb)

這個是一個非常簡單的程序,而且追蹤信息很短小,因為我們并沒有在其他的函數內部來調用許多函數。我們可以看到sort是由同一個文件debug3.c中37行處的main來調用的。通常,問題會更為復雜,而我們可以使用backtrace來發現我們到達錯誤位置的路徑。

backtrace命令可以簡寫為bt,而且為了與其他調試器兼容,where命令也具有相同的功能。

檢測變量

當程序停止時由gdb所輸出的信息以及在棧追蹤中的信息向我們顯示了函數能數的值。

sort函數是使用一個參數a來調用的,而其值為0x8049580。這是數組的地址。依據于所使用的編譯器以及操作系統,這個值在不同的操作系統也會不同。

所影響的行號23,是一個數組元素與另一個數組元素進行比較的地方。

if(a[j].key > a[j+1].key) {

我們可以使用調試器來檢測函數參數,局部變量以及全局數據的內容。print命令可以向我們顯示變量以及其他表達式的內容。

(gdb) print j
$1 = 4

在這里我們可以看到局部變量j的值為4。類似這樣由gdb命令所報告的所有值都會保存在偽變量中以備將來使用。在這里變量$1賦值為4以防止我們在以后使用。以后的命令將他們的結果存儲為$2,$3,依次類推。

j的值為4的事實意味著程序試著執行語句

if(a[4].key > a[4+1].key)

我們傳遞給sort的數組,array,只有5個元素,由0到4進行索引。所以這條語句讀取并不存在的array[5]。循環變量j已經讀取一個錯誤的值。

如果我們嘗試這個例子,而我們程序在25行發生錯誤,我們系統只有在交互元素時才會檢測到一個超過數組邊界的讀取,執行

a[j] = a[j+1];

此時將j設置為4,結果為

a[4] = a[4+1];

我們可以使用print通過表達式來查看所傳遞的數組元素。使用gdb,我們幾乎可以使用任何合法的C表達式來輸出變量,數組元素,以及指針的值。

(gdb) print a[3]
$2 = {data = “alex”, ‘\000’ <repeats 4091times>, key = 1}
(gdb)

gdb將命令的結果保存在一個偽變量中,$<number>。上一個結果總是為$,而之前的一個為$$。這可以使得在一個結果可以用在另一個命令中。例如,

(gdb) print j
$3 = 4
(gdb) print a[$-1].key
$4 = 1

列出程序

我們可以使用list命令在gdb內查看程序源代碼。這會打印出當前位置周圍的部分代碼。持續的使用list會輸出更多的代碼。我們也可以為list指定一個行號或是函數名作為一個參數,而gdb就會顯示那個位置的代碼。

(gdb) list
18?????int s = 1;
19?????
20?????for(; i < n && s !=0; i++) {
21?????????????s = 0;
22?????????????for(j = 0; j < n; j++) {
23?????????????????????if(a[j].key > a[j+1].key) {
24?????????????????????????????item t = a[j];
25??? a[j] =a[j+1];
26??? a[j+1] =t;
27??? s++;
(gdb)

我們可以看到在22行循環設置為當變量j小于n時才會執行。在這個例子中,n為5,所以j的最終值為4,總是小1。4會使得a[4]與a[5]進行比較并且有可能進行交換。這個問題的解決方法就是修正循環的結束條件為j< n-1。

讓我們做出修改,將這個新程序稱之為debug4.c,重新編譯,并再次運行。

for(j = 0; j < n-1; j++) {

$ cc -g -o debug4 debug4.c
$ ./debug4
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}

程序仍不能正常工作,因為他輸出了一個不正確的排序列表。下面我們使用gdb在程序運行時分步執行。

設置斷點

查找出程序在哪里失敗,我們需要能夠查看程序運行他都做了什么。我們可以通過設置斷點在任何位置停止程序。這會使得程序停止并將控制權返回調試器。我們將能夠監視變量并且允許程序繼續執行。

在sort函數中有兩個循環。外層循環,使用循環變時i,對于數組中的每一個元素運行一次。內層循環將其與列表中的下一個元素進行交換。這具有將最小的元素交換到最上面的效果。在外層循環的每一次執行之后,最大的元素應位置底部。我們可通過在外層循環停止程序進行驗證并且檢測數組狀態。

有許多命令可以用于設置斷點。通過gdb的help breakpoint命令可以列表這些命令:

(gdb) help breakpoint
Making program stop at certain points.
List of commands:
awatch — Set a watchpoint for an expression
break — Set breakpoint at specified line or function
catch — Set catchpoints to catch events
clear — Clear breakpoint at specified line or function
commands — Set commands to be executed when a breakpoint ishit
condition — Specify breakpoint number N to break only if COND istrue
delete — Delete some breakpoints or auto-display expressions
disable — Disable some breakpoints
enable — Enable some breakpoints
hbreak — Set a hardware assisted breakpoint
ignore — Set ignore-count of breakpoint number N to COUNT
rbreak — Set a breakpoint for all functions matching REGEXP
rwatch — Set a read watchpoint for an expression
tbreak — Set a temporary breakpoint
tcatch — Set temporary catchpoints to catch events
thbreak — Set a temporary hardware assisted breakpoint
watch — Set a watchpoint for an expression
Type “help” followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.

讓我們在20行設置一個斷點并且運行這個程序:

$ gdb debug4
(gdb) break 20
Breakpoint 1 at 0x804835d: file debug4.c, line 20.
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug4
Breakpoint 1, sort (a=0x8049580, n=5) at debug4.c:20
20????????????for(; i < n && s !=0; i++) {

我們可以輸出數組值并且使用cont可以使得程序繼續執行。這個會使得程序繼續運行直到遇到下一個斷點,在這個例子中,直到他再次執行到20行。在任何時候我們都可以有多個活動斷點。

(gdb) print array[0]
$1 = {data = “bill”, ‘\000’ <repeats 4091times>, key = 3}

要輸出多個連續的項目,我們可以使用@<number>結構使得gdb輸出多個數組元素。要輸出array的所有五個元素,我們可以使用

(gdb) print array[0]@5
$2 = {{data = “bill”, ‘\000’ <repeats 4091times>, key = 3}, {
??? data =“neil”, ‘\000’ <repeats 4091 times>,key = 4}, {
??? data =“john”, ‘\000’ <repeats 4091 times>,key = 2}, {
??? data =“rick”, ‘\000’ <repeats 4091 times>,key = 5}, {
??? data =“alex”, ‘\000’ <repeats 4091 times>,key = 1}}

注意,輸出已經進行簡單的處理從而使其更易于閱讀。因為這是第一次循環,數組并沒有發生變量。當我們允許程序繼續執行,我們可以看到當處理執行時array的成功修改:

(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049580, n=4) at debug4.c:20
20????????????for(; i < n && s !=0; i++) {
(gdb) print array[0]@5
$3 = {{data = “bill”, ‘\000’ <repeats 4091times>, key = 3}, {
??? data =“john”, ‘\000’ <repeats 4091 times>,key = 2}, {
??? data =“neil”, ‘\000’ <repeats 4091 times>,key = 4}, {
??? data =“alex”, ‘\000’ <repeats 4091 times>,key = 1}, {
??? data =“rick”, ‘\000’ <repeats 4091 times>,key = 5}}
(gdb)

我們可以使用display命令來設置gdb當程序在斷點處停止時自動顯示數組:

(gdb) display array[0]@5
1: array[0] @ 5 = {{data = “bill”, ‘\000’ <repeats4091 times>, key = 3}, {
??? data =“john”, ‘\000’ <repeats 4091 times>,key = 2}, {
??? data =“neil”, ‘\000’ <repeats 4091 times>,key = 4}, {
??? data =“alex”, ‘\000’ <repeats 4091 times>,key = 1}, {
??? data =“rick”, ‘\000’ <repeats 4091 times>,key = 5}}

而且我們可以修改斷點,從而他只是簡單的顯示我們所請求的數據并且繼續執行,而不是停止程序。在這樣做,我們可以使用commands命令。這會允許我們指定當遇到一個斷點時執行哪些調試器命令。因為我們已經指定了一個display命令,我們只需要設置斷點命令繼續執行。

(gdb) commands
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just “end”.
> cont
> end

現在我們允許程序繼續,他會運行完成,在每次運行到外層循環時輸出數組的值。

(gdb) cont
Continuing.
Breakpoint 1, sort (a=0x8049684, n=3) at debug4.c:20
20????????????for(; i < n && s !=0; i++) {
1: array[0] @ 5 = {{data = “john”, ‘\000’ <repeats4091 times>, key = 2}, {
??? data =“bill”, ‘\000’ <repeats 4091 times>,key = 3}, {
??? data =“alex”, ‘\000’ <repeats 4091 times>,key = 1}, {
??? data =“neil”, ‘\000’ <repeats 4091 times>,key = 4}, {
??? data =“rick”, ‘\000’ <repeats 4091 times>,key = 5}}
Breakpoint 1, sort (a=0x8049684, n=2) at debug4.c:20
20????????????for(; i < n && s !=0; i++) {
1: array[0] @ 5 = {{data = “john”, ‘\000’ <repeats4091 times>, key = 2}, {
??? data =“alex”, ‘\000’ <repeats 4091 times>,key = 1}, {
??? data =“bill”, ‘\000’ <repeats 4091 times>,key = 3}, {
??? data =“neil”, ‘\000’ <repeats 4091 times>,key = 4}, {
??? data =“rick”, ‘\000’ <repeats 4091 times>,key = 5}}
array[0] = {john, 2}
array[1] = {alex, 1}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 044.
(gdb)

gdb報告程序并沒有以通常的退出代碼退出。這是因為程序本身并沒有調用exit也沒有由main返回一個值。在這種情況下,這個退出代碼是無意義的,而一個有意義的退出代碼應由調用exit來提供。

這個程序看起來似乎外層循環次數并不是我們所期望的。我們可以看到循環結束條件所使用的參數值n在每個斷點處減小。這意味著循環并沒有執行足夠的次數。問題就在于30行處n的減小。

n--;

這是一個利用在每一次外層循環結束時array的最大元素都會位于底部的事實來優化程序的嘗試,所以就會有更少的排序。但是,正如我們所看到的,這是與外層循環的接口,并且造成了問題。最簡單的修正方法就是刪除引起問題的行。讓我們通過使用調試器來應用補丁測試這個修正是否有效。

使用調試進行補丁

我們已經看到了我們可以使用調試器來設置斷點與檢測變量的值。通過使用帶動作的斷點,我們可以在修改源代碼與重新編譯之前試驗一個修正,稱之為補丁。在這個例子中,我們需要在30行設置斷點,并且增加變量n。然后,當30行執行,這個值將不會發生變化。

讓我們從頭啟動程序。首先,我們必須刪除我們的斷點與顯示。我們可以使用info命令來查看我們設置了哪些斷點與顯示:

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:?? y array[0] @ 5
(gdb) info break
NumType???????????Disp EnbAddress???What
1??breakpoint?????keep y?? 0x0804835d in sort atdebug4.c:20
????????breakpoint already hit 4 times
????????cont

我們可以禁止這些或是完全刪除他們。如果我們禁止他們,那么我們可以在以后需要他們時重新允許這些設置:

(gdb) disable break 1
(gdb) disable display 1
(gdb) break 30
Breakpoint 2 at 0x8048462: file debug4.c, line 30.
(gdb) commands 2
Type commands for when breakpoint 2 is hit, one per line.
End with a line saying just “end”.
>set variable n = n+1
>cont
>end
(gdb) run
Starting program: /home/neil/BLP3/chapter10/debug4
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????????????????????n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????????????????????n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????????????????????n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30????????????????????n--;
Breakpoint 2, sort (a=0x8049580, n=5) at debug4.c:30
30???????????????????n--;
array[0] = {alex, 1}
array[1] = {john, 2}
array[2] = {bill, 3}
array[3] = {neil, 4}
array[4] = {rick, 5}
Program exited with code 044.
(gdb)

這個程序運行結束并且會輸出正確的結果。現在我們可以進行修正并且繼續使用更多的數據進行測試。

了解更多有關gdb的內容

GNU調試器是一個強大的工具,可以提供大量的有關運行程序內部狀態的信 息。在支持一個名叫硬件斷點(hardwarebreakpoint)的實用程序的系統上,我們可以使用gdb來實時的查看變量的改變。硬件斷點是某些CPU的一個特性;如果出現特定的條件,通常是在指定區域的內存訪問,這些處理器能夠自動停止。相對應的,gdb可以使用watch表達式。這就意味著,出于性能考慮,當一個表達式具有一個特定的值時,gdb可以停止這個程序,而不論計算發生在程序中的哪個位置。

斷點可以使用計數以及條件進行設置,從而他們只在一定的次數之后或是當滿足一個條件時才會被引發。

gdb也能夠將其本身附在已經運行的程序中。這對于我們調試客戶端/服務器系統時是非常有用的,因為我們可以調試一個正在運行的行為不當的服務器進程,而不需要停止與重啟服務器。例如,我們可以使用gcc -O-g選項來編譯我們的程序,從而得到優化與調試的好處。不足之處就是優化也許會重新組織代碼,所以當我們分步執行時,我們也許會發出我們自身的跳轉來達到與原始源代碼相同的效果。

我們也可以使用gdb來調試已經崩潰的程序。Linux與Unix經常會在一個程序失敗時在一個名為core的文件中生成一個核心轉儲信息。這是一個程序內存的圖象并且會包含失敗時全局變量的值。我們可以使用gdb來查看當程序崩潰時程序運行到哪里。查看gdb手冊頁我們可以得到更為詳細的信息。

gdb以GPL許可證發布并且絕大多數的Unix系統都會支持他。我們強烈建議了解gdb。

更多的調試工具

除了強大的調試工具,例如gdb,Linux系統通常還會提供一些其他們的我們可以用于診治調試進程的工具。其中的一些會提供關于一個程序的靜態信息;其他的會提供動態分析。

靜態分析只由程序源代碼提供信息。例如ctags,cxref,與cflow這樣的程序與源代碼一同工作,并且會提供有關函數調用與位置的有用信息。

動態會析會提供有關一個程序在執行過程中如何動作的信息。例如prof與gprof這樣的程序會提供有關執行了哪個函數并且執行多長時間的信息。

下面我們來看一下其中的一些工具及其輸出。并不是所有的這些工具都可以在所有的系統上得到,盡管其中的一些都有自由版本。

Lint:由我們的程序中移除Fluff

原始的Unix系統提供了一個名為lint的實用程序。他實質是一個C編譯的前端,帶有一個測試設計來適應一些常識并且生成警告。他會檢測變量在設計之前在哪里使用以及哪里函數參數沒有被使用,以及其他的一些情況。

更為現代的C編譯器可以編譯性能為代價提供類似的警告。lint本身已經被C標準所超越。因為這個工具是基于早期的C編譯器,他并不能處理所有的ANSI語法。有一些商業版本的lint可以用于Unix,而且在網絡上至少有一個名為splint可以用于Linux。這就是過去所知的LClint,他是MIT一個工程的一部分,來生成用于通常規范的工具。一個類似于lint的工具splint可以提供查看注釋的有用代碼。splint可以在htt://www.splin.org處得到。

下面是一個編輯過的splint例子輸出,這是運行我們在前面調試的例子程序的早期版本中所產生的輸出:

neil@beast:~/BLP3/chapter10> splint -strictdebug0.c
Splint 3.0.1.6 --- 27 Mar 2002
debug0.c:14:22: Old style function declaration
?

Function definition is in old style syntax.Standard prototype syntax is
? preferred. (Use -oldstyle to inhibitwarning)
debug0.c: (in function sort)
debug0.c:20:31: Variable s used before definition
? An rvalue is used that may not be initialized toa value on some execution
? path. (Use -usedef to inhibit warning)
debug0.c:20:23: Left operand of & is not unsignedvalue (boolean):
??????????????????i < n & s != 0
? An operand to a bitwise operator is not anunsigned values. This may have
? unexpected results depending on the signedrepresentations. (Use
? -bitwisesigned to inhibit warning)
debug0.c:20:23: Test expression for for not boolean, type unsignedint:
??????????????????i < n & s != 0
? Test expression type is not boolean or int. (Use-predboolint to inhibit
? warning)
debug0.c:20:23: Operands of & are non-integer(boolean) (in post loop test):
??????????????????i < n & s != 0
? A primitive operation does not type checkstrictly. (Use -strictops to
? inhibit warning)
debug0.c:32:14: Path with no return in function declared to returnint
? There is a path through a function declared toreturn a value on which there
? is no return statement. This means the executionmay fall through without
? returning a meaningful result to the caller.(Use -noret to inhibit warning)
debug0.c:34:13: Function main declared without parameter list
? A function declaration does not have a parameterlist. (Use -noparams to
? inhibit warning)
debug0.c: (in function main)
debug0.c:36:17: Return value (type int) ignored: sort(array,5)
? Result returned by function call is not used. Ifthis is intended, can cast
? result to (void) to eliminate message. (Use-retvalint to inhibit warning)
debug0.c:37:14: Path with no return in function declared to returnint
debug0.c:14:13: Function exported but not used outside debug0:sort
?? debug0.c:15:17: Definition ofsort
Finished checking --- 22 code warnings
$

這個程序報告舊風格的函數定義以及函數返回類型與他們實際返回類型之間的不一致。這些并不會影響程序的操作,但是應該注意。

他還在下面的代碼片段中檢測到兩個實在的bug:

int s;

for(; i < n & s != 0; i++) {
????????s = 0;

splint已經確定在20行使用了變量s,但是并沒有進行初始化,而且操作符&已經被更為通常的&&所替代。在這個例子中,操作符優先級修改了測試的意義并且是程序的一個問題。

所有這些錯誤都在調試開始之前在代碼查看中被修正。盡管這個例子有一個故意演示的目的,但是這些錯誤真實世界的程序中經常會出現的。

函數調用工具

三個實用程序-ctags,cxref與cflow-形成了X/Open規范的部分,所以必須在具有軟件開發功能的Unix分枝系統上提供。

ctags

ctags程序創建函數索引。對于每一個函數,我們都會得到一個他在何處使用的列表,與書的索引類似。

ctags [-a] [-f filename] sourcefile sourcefile ...
ctags -x sourcefile sourcefile ...

默認情況下,ctags在當前目錄下創建一個名為tags的目錄,其中包括在輸入源文件碼中所聲明的每一個函數,如下面的格式

announce app_ui.c /^static void announce(void) /

文件中的每一行由一個函數名,其聲明所在的文件,以及一個可以用在文件中查找到函數定義所用的正則表達式所組成。一些編輯器,例如Emacs可以使用這種類型的文件在源碼中遍歷。

相對應的,通過使用ctags的-x選項,我們可以在標準輸出上產生類似格式的輸出:

find_cat 403 app_ui.c static cdc_entry find_cat(

我們可以通過使用-ffilename選項將輸出重定向到另一個不同的文件中,或是通過指定-a選項將其添加到一個已經存在的文件中。

cxref

cxref程序分析C源代碼并且生成一個交叉引用。他顯示了每一個符號在程序中何處被提到。他使用標記星號的每一個符號定義位置生成一個排序列表,如下所示:

SYMBOL??FILE??? FUNCTIONLINE
?BASENID?prog.c?????—??? *12 *96 124126 146 156 166
?BINSIZE?prog.c?????—??? *30 197 198199 206
?BUFMAX??????????????prog.c? —??*44? 45 90
? BUFSIZ /usr/include/stdio.h?—??? *4
????EOF /usr/include/stdio.h?—?? *27
???argc??????????????prog.c?—??? 36
??????????????????????prog.c main *37? 61 81
???argv??????????????prog.c?—??? 36
??????????????????????prog.c main *38? 61
calldata??????????????prog.c?—??? *5
??????????????????????prog.c main? 64 188
??calls??????????????prog.c? —??*19
??????????????????????prog.c main? 54

在作者的機子上,前面的輸入在程序的源碼目錄中使用下面的命令來生成的:

$ cxref *.c *.h

但是實際的語法因為版本的不同而不同。查看我們系統的文檔或是man手冊可以得到更多的信息。

cflow

cflow程序會輸出一個函數調用樹,這是一個顯示函數調用關系的圖表。這對于查看程序結構來了解他是如何操作的以及了解對于一個函數有哪些影響是十分有用的。一些版本的cflow可以同時作用于目標文件與源代碼。查看手冊頁我們可以了解更為詳細的操作。

下面是由一個cflow版本(cflow-2.0)所獲得的例子輸出,這個版本的cflow版本是由MartyLeisner維護的,并且可以網上得到。

1? file_ungetc {prcc.c 997}
2? main {prcc.c 70}
3?????????getopt {}
4?????????show_all_lists {prcc.c 1070}
5??????????????????display_list {prcc.c 1056}
6??????????????????????????printf {}
7??????????????????exit {}
8?????????exit {}
9?????????usage {prcc.c 59}
10?????????????????fprintf {}
11?????????????????exit {}

從這個輸出中我們可以看到main函數調用show_all_lists,而show_all_lists調用display_list,display_list本身調用printf。

這個版本cflow的一個選項就是-i,這會生成一個反轉的流程圖。對于每一個函數,cflow列出調用他的其他函數。這聽起來有些復雜,但是實際上并不是這樣。下面是一個例子。

19 display_list {prcc.c 1056}
20????????show_all_lists {prcc.c 1070}
21 exit {}
22????????main {prcc.c 70}
23?????????show_all_lists {prcc.c 1070}
24?????????usage {prcc.c 59}
...
74? printf {}
75?????????display_list {prcc.c 1056}
76?????????maketag {prcc.c 487}
77? show_all_lists {prcc.c 1070}
78?????????main {prcc.c 70}
...
99? usage {prcc.c 59}
100????????main {prcc.c 70}

例如,這告訴我們調用exit的函數有main,show_all_lists與usage。

使用prof/gprof執行性能測試

當我們試著追蹤一個程序的性能問題時一個十分有用的技術就是執行性能測試(executionprofiling)。通常被特殊的編譯器選項以及輔助程序所支持,一個程序的性能顯示他在哪里花費時間。

prof程序(以及其GNU版本gprof)會由性能測試程序運行時所生成的執行追蹤文件中輸出報告。一個可執行的性能測試是由指定-p選項(對prof)或是-pg選項(對gprof)所生成的:

$ cc -pg -o program program.c

這個程序是使用一個特殊版本的C庫進行鏈接的并且被修改來包含監視代碼。對于不同的系統結果也許不同,但是通常是由安排頻繁被中斷的程序以及記錄執行位置來做到的。監視數據被寫入當前目錄中的一個文件,mon.out(對于gprof為gmon.out)。

$ ./program
$ ls -ls
?? 2 -rw-r--r-- 1 neil users 1294Feb 4 11:48 gmon.out

prof/gprof程序讀取這些監視數據并且生成一個報告。查看其手冊頁可以詳細了解其程序選項。下面以gprof輸出作為一個例子:

cumulative??self???self?? total
??time??? secondsseconds? calls ms/callms/call???????????name
??18.5??????0.10???0.10??8664???0.01???0.03????_doscan [4]
??18.5??????0.20???0.10???????????????????????????mcount (60)
??14.8??????0.28???0.08?43320???0.00???0.00????_number [5]
???9.3??????0.33???0.05??8664???0.01??? 0.01_format_arg [6]
???7.4??????0.37??? 0.04112632???0.00???0.00????_ungetc [8]
???7.4??????0.41???0.04??8757???0.00???0.00??? _memccpy[9]
???7.4??????0.45???0.04?????1?? 40.00?390.02??????_main [2]
???3.7??????0.47???0.02????53???0.38???0.38?????_read [12]
???3.7??????0.49???0.02????????????????????????????w4str [10]
???1.9??????0.50???0.01?26034???0.00???0.00??? _strlen[16]
???1.9??????0.51???0.01??8664???0.00???0.00??? strncmp[17]

斷言

在程序的開發過程中,通常使用條件編譯的方法引入調試代碼,例如printf,但是在一個發布的系統中保留這些信息是不實際的。然而,經常的情況是問題出現與不正確的假設相關的程序操作過程中,而不是代碼錯誤。這些都是"不會發生"的事件。例如,一個函數也許是在認為其輸入參數總是在一定范圍下而編寫的。如果給他傳遞一些不正確的數據,也許整個系統就會崩潰。

對于這些情況,系統的內部邏輯在哪里需要驗證,X/Open提供了assert宏,可以用來測試一個假設是否正確,如果不正確則會停止程序。

#include <assert.h>
void assert(int expression)

assert宏會計算表達式的值,如果不為零,則會向標準錯誤上輸出一些診斷信息,并且調用abort來結束程序。

頭文件assert.h依據NDEBUG的定義來定義宏。如果頭文件被處理時定義了NDEBUG,assert實質上被定義為空。這就意味著我們可以通過使用-DNDEBUG在編譯時關閉斷言或是在包含assert.h文件之前包含下面這行:

#define NDEBUG

這種方法的使用是assert的一個問題。如果我們在測試中使用assert,但是卻對生產代碼而關閉,比起我們測試時的代碼,我們的生產代碼就不會太安全。在生產代碼中保留斷言開啟狀態并不是通常的選擇,我們希望我們的代碼向用戶顯示一條不友好的錯誤assertfailed與一個停止的程序嗎?我們也許會認為最好是編寫我們自己的檢測斷言的錯誤追蹤例程,而不必在我們的生產代碼中完全禁止。

我們同時要小心在assert斷言沒有臨界效果。例如,如果我們在一個臨界效果中調用一個函數,如果移除了斷言,在生產代碼中就不會出現這個效果。

試驗--assert

下面的程序assert.c定義了一個必須傳遞正值參數的函數。通過使用一個斷言可以避免不正常參數的可能。

在包含assert.h頭文件和檢測參數是否為正的平方根函數之后,我們可以編寫如下的函數:

#include <stdio.h>
#include <math.h>
#include <assert.h>
double my_sqrt(double x)
{
??? assert(x>= 0.0);
??? returnsqrt(x);
}
int main()
{
??? printf(“sqrt+2 = %g\n”, my_sqrt(2.0));
??? printf(“sqrt-2 = %g\n”, my_sqrt(-2.0));
???exit(0);
}

當我們運行這個程序時,我們就會看到當我們傳遞一個非法值時就會違背這個斷言。事實上的斷言失敗的消息格式會因系統的不同而不同。

$ cc -o assert assert.c -lm
$ ./assert
sqrt +2 = 1.41421
assert: assert.c:7: my_sqrt: Assertion `x >= 0.0’failed.
Aborted
$

工作原理

當我們試著使用一個負數來調用函數my_sqrt時,斷言就會失敗。assert宏會提供違背斷言的文件和行號,以及失敗的條件。程序以一個退出陷井結束。這是assert調用abort的結果。

如果我們使用-DNDEBUG選項來編譯這個程序,斷言就會被編譯在外,而當我們由my_sqrt中調用sqrt函數時我們就會得到一個算術錯誤。

$ cc -o assert -DNDEBUG assert.c -lm
$ ./assert
sqrt +2 = 1.41421
Floating point exception
$

一些最近的算術庫版本會返回一個NaN(Not a Number)值來表示一個不可用的結果。

sqrt –2 = nan

內存調試

富含bug而且難于跟蹤調試的一個區域就是動態內存分配。如果我們編譯一個使用malloc與free來分配內存的程序,很重要的一點就是我們要跟蹤我們所分配的內存塊,并且保證不要使用已經釋放的內存塊。

通常,內存是由malloc分配并且賦給一個指針變量的。如果指針變量被修改了,而又沒有其他的指針來指向這個內存塊,他就會變為不可訪問的內存塊。這就是一個內存泄露,而且會使得我們程序尺寸變大。如果我們泄露了大量的內存,那么我們的系統就會變慢并且會最終用盡內存。

如果我們在超出一個分配的內存塊的結束部分(或是在一個內存塊的開始部分)寫入數據,我們很有可能會破壞malloc庫來跟蹤分配所用的數據結構。在這種情況下,在將來的某個時刻,調用malloc,或者甚至是free,就會引起段錯誤,而我們的程序就會崩潰。跟蹤錯誤發生的精確點是非常困難的,因為很可能他在引起崩潰的事件發生以前很一段時間就已經發生了。

不必奇怪的是,有一些工具,商業或是自由的,可以有助于處理這兩種問題類型。例如,有許多不同的malloc與free版本,其中的一些包含額外的代碼在分配與回收上進行檢測嘗試檢測一個內存塊被釋放兩次或是其他一些濫用類型的情況。

ElectricFence

ElectricFence 庫是由BrucePerens開發的,并且在一些Linux發行版本中作為一個可選的組件來提供,例如RedHat,而且已經可以在網絡上獲得。他嘗試使用Linux的虛擬內存工具來保護malloc與free所使用的內存,從而在內存被破壞時終止程序。

試驗--ElectricFence

下面的程序,efence.c,使用malloc分配一個內存塊,然后在超出塊結束處寫入數據。讓我們看一下會發生什么情況。

#include <stdio.h>
#include <stdlib.h>
int main()
{
??? char *ptr =(char *) malloc(1024);
??? ptr[0] =0;
???
??? ptr[1024] =0;
???exit(0);
}

當我們編譯運行這個程序時,我們并不會看到進一步的行為。然而,似乎malloc所分配的內存區域有一些問題,而我們實際上已經遇到了麻煩。

$ cc -o efence efence.c
$ ./efence
$

然而,如果我們使用ElectricFence庫,libefence.a來鏈接這個程序,我們就會得到一個即時的響應。

$ cc -o efence efence.c -lefence
$ ./efence
? Electric Fence 2.2.0 Copyright (C) 1987-1999Bruce Perens <bruce@perens.com>
Segmentation fault
$

在調試器下運行可以定位這個問題:

$ cc -g -o efence efence.c -lefence
$ gdb efence
?(gdb) run
Starting program: /home/neil/BLP3/chapter10/efence
[New Thread 1024 (LWP 1869)]
?? Electric Fence 2.2.0 Copyright(C) 1987-1999 Bruce Perens<bruce@perens.com>
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1024 (LWP 1869)]
0x080484ad in main () at efence.c:10
10??????????ptr[1024] = 0;
(gdb)

工作原理

Electric替換malloc并且將函數與計算機處理器的虛擬內存特性相關聯來阻止非法的內存訪問。當這樣的訪問發生時,就會拋出一個段錯誤信息從而可以終止程序。

valgrind

valgrind是一個可以檢測我們已經討論過的許多問題的工具。事實上,他可以檢測數據訪問錯誤與內存泄露。也許他并沒有被包含在我們的Linux發行版本中,但是我們可以在http://developer.kde.org/~sewardj處得到。

程序并不需要使用valgrind重新編譯,而我們甚至可以調用一個正在運行的程序的內存訪問。他很值得一看,他已經用在主要的開發上,包含KDE版本3。

試驗--valgrind

下面的程序,checker.c,分配一些內存,讀取超過那塊內存限制的位置,在其結束處之外寫入數據,然后使其不能訪問。

#include <stdio.h>
#include <stdlib.h>
int main()
{
??? char *ptr =(char *) malloc(1024);
??? charch;
???
??? ch =ptr[1024];
???
??? ptr[1024] =0;
?
? ptr = 0;
? exit(0);
}

要使用valgrind,我們只需要簡單的運行valgrind命令,傳遞我們希望檢測的選項,其后是使用其參數運行的程序。

當我們使用valgrind來運行我們的程序時,我們可以看到診斷出許多問題:

$ valgrind --leak-check=yes -v ./checker
==3436== valgrind-1.0.4, a memory error detector for x86GNU/Linux.
==3436== Copyright (C) 2000-2002, and GNU GPL’d, by JulianSeward.
==3436== Estimated CPU clock rate is 452 MHz
==3436== For more details, rerun with: -v
==3436==
==3436== Invalid read of size 1
==3436==??? at0x8048397: main (checker.c:10)
==3436==??? by0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436==??? by0x80482D1: exit@@GLIBC_2.0 (in/home/neil/BLP3/chapter10/checker)
==3436==???Address 0x42AD1424 is 0 bytes after a block of size 1024alloc’d
==3436==??? at0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436==??? by0x8048389: main (checker.c:6)
==3436==??? by0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436==??? by0x80482D1: exit@@GLIBC_2.0 (in/home/neil/BLP3/chapter10/checker)
==3436==
==3436== Invalid write of size 1
==3436==??? at0x80483A4: main (checker.c:13)
==3436==??? by0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436==??? by0x80482D1: exit@@GLIBC_2.0 (in/home/neil/BLP3/chapter10/checker)
==3436==???Address 0x42AD1424 is 0 bytes after a block of size 1024alloc’d
==3436==??? at0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436==??? by0x8048389: main (checker.c:6)
==3436==??? by0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436==??? by0x80482D1: exit@@GLIBC_2.0 (in/home/neil/BLP3/chapter10/checker)
==3436==
==3436== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0from 0)
==3436== malloc/free: in use at exit: 1024 bytes in 1 blocks.
==3436== malloc/free: 1 allocs, 0 frees, 1024 bytesallocated.
==3436== For counts of detected errors, rerun with: -v
==3436== searching for pointers to 1 not-freed blocks.
==3436== checked 3468724 bytes.
==3436==
==3436== definitely lost: 1024 bytes in 1 blocks.
==3436== possibly lost:?? 0 bytesin 0 blocks.
==3436== still reachable: 0 bytes in 0 blocks.
==3436==
==3436== 1024 bytes in 1 blocks are definitely lost in loss record1 of 1
==3436==??? at0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436==??? by0x8048389: main (checker.c:6)
==3436==??? by0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436==??? by0x80482D1: exit@@GLIBC_2.0 (in/home/neil/BLP3/chapter10/checker)
==3436==
==3436== LEAK SUMMARY:
==3436==???definitely lost: 1024 bytes in 1 blocks.
==3436==???possibly lost:?? 0 bytes in 0blocks.
==3436==???still reachable: 0 bytes in 0 blocks.
==3436== Reachable blocks (those to which a pointer was found) arenot shown.
==3436== To see them, rerun with: --show-reachable=yes
==3436== $

這里我們可以看到錯誤的讀取與寫入已經被捕獲,而所關注的內存塊與他們被分配的位置相關聯。我們可以使用調試器在出錯點斷開程序。

valgrind有許多選項,包含特定的錯誤類型表達式與內存泄露檢測。要檢測我們的例子泄露,我們必須使用一個傳遞給valgrind的選項。當程序結束時要檢測內存泄露,我們需要指定 --leak-check=yes。我們可以使用valgrind --help得到一個選項列表。

工作原理

我們的程序在valgrind的控制下執行,這會檢測我們程序所執行的各種動作,并且執行許多檢測,包括內存訪問。如果程序訪問一個已分配的內存塊并且訪問是非法的,valgrind就會輸出一條信息。在程序結束時,一個垃圾收集例程就會運行來檢測是否在存在分配的內存塊沒有被釋放。這些孤兒內存也會被報告。

小結

在這一章,我們了解了一些調試工具與技術。Linux提供了一些強大的工具可以用于由程序中移除缺陷。我們使用gdb來消除程序中的bug,并且了解了如cflow與splint這樣的數據分析工具。最后我們了解了當我們使用動態分配內存時會出現的問題,以及一些用于類似問題診斷的工具,例如ElectricFence與valgrind。


查看運行時數據
———————

???
???在你調試程序時,當程序被停住時,你可以使用print命令(簡寫命令為p),或是同義命令inspect來查看當前程序的運行數據。print命令的格式是:
???
??? print<expr>
??? print/<f><expr>
???????<expr>是表達式,是你所調試的程序的語言的表達式(GDB可以調試多種編程語言),<f>是輸出的格式,比如,如果要把表達式按16進制的格式輸出,那么就是/x。
???????
???
一、表達式

???print和許多GDB的命令一樣,可以接受一個表達式,GDB會根據當前的程序運行的數據來計算這個表達式,既然是表達式,那么就可以是當前程序運行中的const常量、變量、函數等內容。可惜的是GDB不能使用你在程序中所定義的宏。
???
???表達式的語法應該是當前所調試的語言的語法,由于C/C++是一種大眾型的語言,所以,本文中的例子都是關于C/C++的。(而關于用GDB調試其它語言的章節,我將在后面介紹)
???
???在表達式中,有幾種GDB所支持的操作符,它們可以用在任何一種語言中。
???
??? @
???????是一個和數組有關的操作符,在后面會有更詳細的說明。
???????
??? ::
???????指定一個在文件或是一個函數中的變量。
???????
???{<type>}<addr>
???????表示一個指向內存地址<addr>的類型為type的一個對象。
???????
???????
二、程序變量

???在GDB中,你可以隨時查看以下三種變量的值:
???????1、全局變量(所有文件可見的)
???????2、靜態全局變量(當前文件可見的)
???????3、局部變量(當前Scope可見的)
???????
???如果你的局部變量和全局變量發生沖突(也就是重名),一般情況下是局部變量會隱藏全局變量,也就是說,如果一個全局變量和一個函數中的局部變量同名時,如果當前停止點在函數中,用print顯示出的變量的值會是函數中的局部變量的值。如果此時你想查看全局變量的值時,你可以使用“::”操作符:
???
???????file::variable
???function::variable
???可以通過這種形式指定你所想查看的變量,是哪個文件中的或是哪個函數中的。例如,查看文件f2.c中的全局變量x的值:
???
??? gdb) p'f2.c'::x
???
???當然,“::”操作符會和C++中的發生沖突,GDB能自動識別“::”是否C++的操作符,所以你不必擔心在調試C++程序時會出現異常。
???
???另外,需要注意的是,如果你的程序編譯時開啟了優化選項,那么在用GDB調試被優化過的程序時,可能會發生某些變量不能訪問,或是取值錯誤碼的情況。這個是很正常的,因為優化程序會刪改你的程序,整理你程序的語句順序,剔除一些無意義的變量等,所以在GDB調試這種程序時,運行時的指令和你所編寫指令就有不一樣,也就會出現你所想象不到的結果。對付這種情況時,需要在編譯程序時關閉編譯優化。一般來說,幾乎所有的編譯器都支持編譯優化的開關,例如,GNU的C/C++編譯器GCC,你可以使用“-gstabs”選項來解決這個問題。關于編譯器的參數,還請查看編譯器的使用說明文檔。
???

三、數組

???有時候,你需要查看一段連續的內存空間的值。比如數組的一段,或是動態分配的數據的大小。你可以使用GDB的“@”操作符,“@”的左邊是第一個內存的地址的值,“@”的右邊則你你想查看內存的長度。例如,你的程序中有這樣的語句:
????
???????int *array = (int *) malloc (len * sizeof (int));
???????
???于是,在GDB調試過程中,你可以以如下命令顯示出這個動態數組的取值:

???????p *array@len

???@的左邊是數組的首地址的值,也就是變量array所指向的內容,右邊則是數據的長度,其保存在變量len中,其輸出結果,大約是下面這個樣子的:
???
???????(gdb) p
*array@len
???????$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,34, 36, 38, 40}

???如果是靜態數組的話,可以直接用print數組名,就可以顯示數組中所有數據的內容了。

???
四、輸出格式

???一般來說,GDB會根據變量的類型輸出變量的值。但你也可以自定義GDB的輸出的格式。例如,你想輸出一個整數的十六進制,或是二進制來查看這個整型變量的中的位的情況。要做到這樣,你可以使用GDB的數據顯示格式:
???
???x? 按十六進制格式顯示變量。
???d? 按十進制格式顯示變量。
???u? 按十六進制格式顯示無符號整型。
???o? 按八進制格式顯示變量。
???t? 按二進制格式顯示變量。
???a? 按十六進制格式顯示變量。
???c? 按字符格式顯示變量。
???f? 按浮點數格式顯示變量。

???????(gdb) p i
???????$21 =101???
???????
???????(gdb) p/a i
???????$22 = 0x65
???????
???????(gdb) p/c i
???????$23 = 101 'e'
???????
???????(gdb) p/f i
???????$24 = 1.41531145e-43
???????
???????(gdb) p/x i
???????$25 = 0x65
???????
???????(gdb) p/t i
???????$26 = 1100101


五、查看內存

???你可以使用examine命令(簡寫是x)來查看內存地址中的值。x命令的語法如下所示:
???
???x/<n/f/u><addr>
???
???n、f、u是可選的參數。
???
??? n是一個正整數,表示顯示內存的長度,也就是說從當前地址向后顯示幾個地址的內容。
??? f表示顯示的格式,參見上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。
??? u表示從當前地址往后請求的字節數,如果不指定的話,GDB默認是4個bytes。u參數可以用下面的字符來代替,b表示單字節,h表示雙字節,w表示四字節,g表示八字節。當我們指定了字節長度后,GDB會從指內存定的內存地址開始,讀寫指定字節,并把其當作一個值取出來。
???
???<addr>表示一個內存地址。

???n/f/u三個參數可以一起使用。例如:
???
??? 命令:x/3uh0x54320 表示,從內存地址0x54320讀取內容,h表示以雙字節為一個單位,3表示三個單位,u表示按十六進制顯示。
???
???
六、自動顯示

???你可以設置一些自動顯示的變量,當程序停住時,或是在你單步跟蹤時,這些變量會自動顯示。相關的GDB命令是display。
???
??? display<expr>
???display/<fmt><expr>
???display/<fmt><addr>
???
???expr是一個表達式,fmt表示顯示的格式,addr表示內存地址,當你用display設定好了一個或多個表達式后,只要你的程序被停下來,GDB會自動顯示你所設置的這些表達式的值。
???
???格式i和s同樣被display支持,一個非常有用的命令是:
???
???????display/i $pc
???
???$pc是GDB的環境變量,表示著指令的地址,/i則表示輸出格式為機器指令碼,也就是匯編。于是當程序停下后,就會出現源代碼和機器指令碼相對應的情形,這是一個很有意思的功能。
???
???下面是一些和display相關的GDB命令:
???
??? undisplay<dnums...>
??? deletedisplay <dnums...>
???刪除自動顯示,dnums意為所設置好了的自動顯式的編號。如果要同時刪除幾個,編號可以用空格分隔,如果要刪除一個范圍內的編號,可以用減號表示(如:2-5)
???
??? disabledisplay <dnums...>
??? enabledisplay <dnums...>
???disable和enalbe不刪除自動顯示的設置,而只是讓其失效和恢復。
???
??? infodisplay
???查看display設置的自動顯示的信息。GDB會打出一張表格,向你報告當然調試中設置了多少個自動顯示設置,其中包括,設置的編號,表達式,是否enable。


七、設置顯示選項

???GDB中關于顯示的選項比較多,這里我只例舉大多數常用的選項。

???set print address
??? set printaddress on
???????打開地址輸出,當程序顯示函數信息時,GDB會顯出函數的參數地址。系統默認為打開的,如:
???????
???????(gdb) f
???????#0? set_quotes (lq=0x34c78"<<", rq=0x34c88">>")
???????????at input.c:530
???????530????????if (lquote != def_lquote)


???set print address off
???????關閉函數的參數地址顯示,如:
???????
???????(gdb) set print addr off
???????(gdb) f
???????#0? set_quotes(lq="<<",rq=">>") at input.c:530
???????530????????if (lquote != def_lquote)

???show print address
???????查看當前地址顯示選項是否打開。
???????
??? set printarray
??? set printarray on
???????打開數組顯示,打開后當數組顯示時,每個元素占一行,如果不打開的話,每個元素則以逗號分隔。這個選項默認是關閉的。與之相關的兩個命令如下,我就不再多說了。
???????
??? set printarray off
??? show printarray

???set print elements<number-of-elements>
???????這個選項主要是設置數組的,如果你的數組太大了,那么就可以指定一個<number-of-elements>來指定數據顯示的最大長度,當到達這個長度時,GDB就不再往下顯示了。如果設置為0,則表示不限制。
???????
??? show printelements
???????查看print elements的選項信息。
???????
??? set printnull-stop <on/off>
???????如果打開了這個選項,那么當顯示字符串時,遇到結束符則停止顯示。這個選項默認為off。
???????
??? set printpretty on
???????如果打開printf pretty這個選項,那么當GDB顯示結構體時會比較漂亮。如:

???????????$1 = {
?????????????next = 0x0,
?????????????flags = {
???????????????sweet = 1,
???????????????sour = 1
?????????????},
?????????????meat = 0x54 "Pork"
???????????}

???set print pretty off
???????關閉printf pretty這個選項,GDB顯示結構體時會如下顯示:
???????
???????????$1 = {next = 0x0, flags = {sweet = 1, sour = 1}, meat = 0x54"Pork"}
???????????
??? show printpretty
???????查看GDB是如何顯示結構體的。
???????
???
??? set printsevenbit-strings <on/off>
???????設置字符顯示,是否按“\nnn”的格式顯示,如果打開,則字符串或字符數據按\nnn顯示,如“\065”。
???
??? show printsevenbit-strings
???????查看字符顯示開關是否打開。
???????
??? set printunion <on/off>
???????設置顯示結構體時,是否顯式其內的聯合體數據。例如有以下數據結構:
???????
???????typedef enum {Tree, Bug} Species;
???????typedef enum {Big_tree, Acorn, Seedling} Tree_forms;
???????typedef enum {Caterpillar, Cocoon, Butterfly}
?????????????????????Bug_forms;
???????
???????struct thing {
?????????Species it;
?????????union {
???????????Tree_forms tree;
???????????Bug_forms bug;
?????????} form;
???????};
???????
???????struct thing foo = {Tree, {Acorn}};

???????當打開這個開關時,執行 p foo 命令后,會如下顯示:
???????????$1 = {it = Tree, form = {tree = Acorn, bug = Cocoon}}
???????
???????當關閉這個開關時,執行 p foo 命令后,會如下顯示:
???????????$1 = {it = Tree, form = {...}}

???show print union
???????查看聯合體數據的顯示方式
???????
??? set printobject <on/off>
???????在C++中,如果一個對象指針指向其派生類,如果打開這個選項,GDB會自動按照虛方法調用的規則顯示輸出,如果關閉這個選項的話,GDB就不管虛函數表了。這個選項默認是off。
???
??? show printobject
???????查看對象選項的設置。
???????
??? set printstatic-members <on/off>
???????這個選項表示,當顯示一個C++對象中的內容是,是否顯示其中的靜態數據成員。默認是on。
???
??? show printstatic-members
???????查看靜態數據成員選項設置。
???????
??? set printvtbl <on/off>
???????當此選項打開時,GDB將用比較規整的格式來顯示虛函數表時。其默認是關閉的。
???????
??? show printvtbl
???????查看虛函數顯示格式的選項。
???????
???????
八、歷史記錄

???當你用GDB的print查看程序運行時的數據時,你每一個print都會被GDB記錄下來。GDB會以$1, $2, $3.....這樣的方式為你每一個print命令編上號。于是,你可以使用這個編號訪問以前的表達式,如$1。這個功能所帶來的好處是,如果你先前輸入了一個比較長的表達式,如果你還想查看這個表達式的值,你可以使用歷史記錄來訪問,省去了重復輸入。
???
???
九、GDB環境變量

???你可以在GDB的調試環境中定義自己的變量,用來保存一些調試程序中的運行數據。要定義一個GDB的變量很簡單只需。使用GDB的set命令。GDB的環境變量和UNIX一樣,也是以$起頭。如:
???
??? set $foo =*object_ptr
???
???使用環境變量時,GDB會在你第一次使用時創建這個變量,而在以后的使用中,則直接對其賦值。環境變量沒有類型,你可以給環境變量定義任一的類型。包括結構體和數組。
???
??? showconvenience
???????該命令查看當前所設置的所有的環境變量。
???????
???這是一個比較強大的功能,環境變量和程序變量的交互使用,將使得程序調試更為靈活便捷。例如:
???
???????set $i = 0
???????print bar[$i++]->contents
???
???于是,當你就不必,print bar[0]->contents, printbar[1]->contents地輸入命令了。輸入這樣的命令后,只用敲回車,重復執行上一條語句,環境變量會自動累加,從而完成逐個輸出的功能。
???
???
十、查看寄存器

???要查看寄存器的值,很簡單,可以使用如下命令:
???
??? inforegisters
???????查看寄存器的情況。(除了浮點寄存器)
???
??? infoall-registers
???????查看所有寄存器的情況。(包括浮點寄存器)
???
??? inforegisters <regname ...>
???????查看所指定的寄存器的情況。
???????
???寄存器中放置了程序運行時的數據,比如程序當前運行的指令地址(ip),程序的當前堆棧地址(sp)等等。你同樣可以使用print命令來訪問寄存器的情況,只需要在寄存器名字前加一個$符號就可以了。如:p$eip。


改變程序的執行
———————

???一旦使用GDB掛上被調試程序,當程序運行起來后,你可以根據自己的調試思路來動態地在GDB中更改當前被調試程序的運行線路或是其變量的值,這個強大的功能能夠讓你更好的調試你的程序,比如,你可以在程序的一次運行中走遍程序的所有分支。
???
???
一、修改變量值

???修改被調試程序運行時的變量值,在GDB中很容易實現,使用GDB的print命令即可完成。如:
???
???????(gdb) print x=4
???
???x=4這個表達式是C/C++的語法,意為把變量x的值修改為4,如果你當前調試的語言是Pascal,那么你可以使用Pascal的語法:x:=4。
???
???在某些時候,很有可能你的變量和GDB中的參數沖突,如:
???
???????(gdb) whatis width
???????type = double
???????(gdb) p width
???????$4 = 13
???????(gdb) set width=47
???????Invalid syntax in expression.

???因為,set width是GDB的命令,所以,出現了“Invalid syntax inexpression”的設置錯誤,此時,你可以使用setvar命令來告訴GDB,width不是你GDB的參數,而是程序的變量名,如:
???
???????(gdb) set var width=47
???????
???另外,還可能有些情況,GDB并不報告這種錯誤,所以保險起見,在你改變程序變量取值時,最好都使用setvar格式的GDB命令。
???

二、跳轉執行

???一般來說,被調試程序會按照程序代碼的運行順序依次執行。GDB提供了亂序執行的功能,也就是說,GDB可以修改程序的執行順序,可以讓程序執行隨意跳躍。這個功能可以由GDB的jump命令來完:
???
??? jump<linespec>
???指定下一條語句的運行點。<linespce>可以是文件的行號,可以是file:line格式,可以是+num這種偏移量格式。表式著下一條運行語句從哪里開始。
???
??? jump<address>
???這里的<address>是代碼行的內存地址。
???
???注意,jump命令不會改變當前的程序棧中的內容,所以,當你從一個函數跳到另一個函數時,當函數運行完返回時進行彈棧操作時必然會發生錯誤,可能結果還是非常奇怪的,甚至于產生程序CoreDump。所以最好是同一個函數中進行跳轉。
???
???熟悉匯編的人都知道,程序運行時,有一個寄存器用于保存當前代碼所在的內存地址。所以,jump命令也就是改變了這個寄存器中的值。于是,你可以使用“set$pc”來更改跳轉執行的地址。如:
???
??? set $pc =0x485


三、產生信號量

???使用singal命令,可以產生一個信號量給被調試的程序。如:中斷信號Ctrl+C。這非常方便于程序的調試,可以在程序運行的任意位置設置斷點,并在該斷點用GDB產生一個信號量,這種精確地在某處產生信號非常有利程序的調試。
???
??? 語法是:signal<singal>,UNIX的系統信號量通常從1到15。所以<singal>取值也在這個范圍。
???
???single命令和shell的kill命令不同,系統的kill命令發信號給被調試程序時,是由GDB截獲的,而single命令所發出一信號則是直接發給被調試程序的。
???

四、強制函數返回

???如果你的調試斷點在某個函數中,并還有語句沒有執行完。你可以使用return命令強制函數忽略還沒有執行的語句并返回。
???
??? return
??? return<expression>
???使用return命令取消當前函數的執行,并立即返回,如果指定了<expression>,那么該表達式的值會被認作函數的返回值。
???
???
五、強制調用函數

???call <expr>
???表達式中可以一是函數,以此達到強制調用函數的目的。并顯示函數的返回值,如果函數返回值是void,那么就不顯示。
???
???另一個相似的命令也可以完成這一功能——print,print后面可以跟表達式,所以也可以用他來調用函數,print和call的不同是,如果函數返回void,call則不顯示,print則顯示函數返回值,并把該值存入歷史數據中。

?

在不同語言中使用GDB
——————————

GDB支持下列語言:C, C++, Fortran, PASCAL,Java, Chill, assembly, 和Modula-2。一般說來,GDB會根據你所調試的程序來確定當然的調試語言,比如:發現文件名后綴為“.c”的,GDB會認為是C程序。文件名后綴為“.C, .cc, .cp, .cpp, .cxx, .c++”的,GDB會認為是C++程序。而后綴是“.f,.F”的,GDB會認為是Fortran程序,還有,后綴為如果是“.s, .S”的會認為是匯編語言。

也就是說,GDB會根據你所調試的程序的語言,來設置自己的語言環境,并讓GDB的命令跟著語言環境的改變而改變。比如一些GDB命令需要用到表達式或變量時,這些表達式或變量的語法,完全是根據當前的語言環境而改變的。例如C/C++中對指針的語法是*p,而在Modula-2中則是p^。并且,如果你當前的程序是由幾種不同語言一同編譯成的,那到在調試過程中,GDB也能根據不同的語言自動地切換語言環境。這種跟著語言環境而改變的功能,真是體貼開發人員的一種設計。


下面是幾個相關于GDB語言環境的命令:

???show language
???????查看當前的語言環境。如果GDB不能識為你所調試的編程語言,那么,C語言被認為是默認的環境。
???????
??? infoframe
???????查看當前函數的程序語言。
???????
??? infosource
???????查看當前文件的程序語言。
???
如果GDB沒有檢測出當前的程序語言,那么你也可以手動設置當前的程序語言。使用setlanguage命令即可做到。

???當set language命令后什么也不跟的話,你可以查看GDB所支持的語言種類:
???
???????(gdb) set language
???????The currently understood settings are:
???????
???????local or auto???Automatic setting based on source file
???????c???????????????Use the C language
???????c++?????????????Use the C++ language
???????asm?????????????Use the Asm language
???????chill???????????Use the Chill language
???????fortran?????????Use the Fortran language
???????java????????????Use the Java language
???????modula-2????????Use the Modula-2 language
???????pascal??????????Use the Pascal language
???????scheme??????????Use the Scheme language
???????
??? 于是你可以在setlanguage后跟上被列出來的程序語言名,來設置當前的語言環境。



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/447180.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/447180.shtml
英文地址,請注明出處:http://en.pswp.cn/news/447180.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在pycharm中使用conda虛擬環境(conda虛擬環境是已經創建好的),解決python安裝包文件很費勁的問題

查看conda的虛擬環境 使用PyCharm連接conda創建的虛擬環境&#xff0c;需要一個前提就是虛擬環境必須存在&#xff0c;使用conda env list命令查看虛擬環境列表打開PyCharm軟件 打開pycharm&#xff0c;選擇File->setting->Project:****->Project Interperter&#…

YUV測試序列下載地址

1、http://www.tkn.tu-berlin.de/research/vid/&#xff08;此網站已經移走&#xff0c;正在尋找。&#xff09; CIF&#xff1a; Akiyo、Bridge (far) 、Bridge (close) 、Bus 、Container 、Coastguard 、Flower 、Football 、Foreman 、Hall 、Highway 、Mobile & Calen…

flask 安裝flask_resultful

命令如下 conda install -c https://conda.anaconda.org/conda-forge flask-restful

完成一個H.265/HEVC碼流分析工具

經過大約一個月左右的業余時間&#xff0c;終于初步完成一個H.265/HEVC碼流分析工具。時間包括平時的周末、晚上&#xff0c;以及調休的集中時間。當然&#xff0c;中秋回家過節不寫代碼。截至今天&#xff0c;經過多種H.265序列測試&#xff0c;也有各種工具對比&#xff0c;基…

Linux中創建用戶并且配置sudo權限,百分百簡單有效

配置步驟如下&#xff1a; 登陸或切換到root用戶下&#xff1b;因為只有root用戶才具有權限進行用戶的添加&#xff0c;使用命令sudo su&#xff0c;切換為管理員用戶添加用戶 adduser -m youuser&#xff0c;并且在home路徑下創建同名的用戶文件夾passwd youuser&#xff0c;…

Java 計算兩個日期之間的相差天數

代碼如下 package com.data.dp;import org.apache.commons.lang3.StringUtils;import java.text.ParseException; import java.text.SimpleDateFormat;public class testDate {public static void main(String[] args) throws ParseException {SimpleDateFormat df new Simpl…

【HEVC】目前H265分析軟件推薦

2014-11-14liuwri本帖最后由 zxdaemon 于 2012-12-25 04:06 PM 編輯 1&#xff0c;Codecvisa&#xff1a; http://codecian.com/國產軟件&#xff0c;從最早的H264visa發展至今&#xff0c;感覺從剛開始的玩票&#xff0c;發展到今天專業級別的商業軟件&#xff0c;值得支持。軟…

將IPFS 配置服務器,電腦連接遠程服務器,環境配置

先前安裝配置的步驟省略&#xff0c;僅僅涉及后期的環境修改步驟 1&#xff0c;找到配置文件config&#xff0c;現需要使用命令ipfs init&#xff0c;從而生成.ipfs文件 ipfs init cd ~/.ipfs2&#xff0c;使用命令ls查看當前文件夾下面所包含的文件&#xff0c;正常情況小會…

Golomb及指數哥倫布編碼原理介紹及實現

文章來源&#xff1a; https://www.cnblogs.com/wangguchangqing/p/6297792.html &#xff0c; 寫的不錯&#xff0c;轉發出來。 2017年的第一篇博文。 本文主要有以下三部分內容&#xff1a; 介紹了Golomb編碼&#xff0c;及其兩個變種&#xff1a;Golomb-Rice和Exp-Golo…

解決ipfs 出現Error: can‘t publish while offline: pass `--allow-offline` to override的問題

原因 出現這個問題的原因是因為&#xff0c;ipfs未與公網上的節點相互連接&#xff0c;因此此時處于離線狀態 使用場景 部署自己的博客&#xff0c;后期的改動&#xff0c;累計追加在同一個地址&#xff0c;這個地址是唯一的&#xff0c;也就是創建ipfs生成的ID號 步驟 1&…

HEVC 編解碼資源

資料 overview 等 csvt系列詳見HHI主頁&#xff0c;適合&#xff1a;入門 Encoder Description 適合入門 Recommendation H.265 pdf 2016-12-22 標準文檔&#xff0c;適合&#xff1a;提高&#xff1b;解碼 High Efficiency Video Coding (HEVC): Algorithms and Architectures…

安裝ipfs-http-client出現constants@0.1.2 install: node build.js > index.browser.js錯誤的解決辦法

錯誤截圖 原先的命令 npm install --save-dev ipfs-http-client 修改后的命令 npm install --save-dev ipfs-http-client --unsafe-permtrue --allow-root

安裝ipfs-api的命令變了,最新版的命令如下

原先使用的命令安裝ipfs-api npm install --save-dev ipfs-api 現在使用的命令安裝ipfs-api npm install --save-dev ipfs-http-client 官方文檔 參考教程 官方文檔

執行truffle unbox react報錯,出現Error: connect ECONNREFUSED 0.0.0.0:443問題的解決辦法

前提&#xff1a;我是用的是MAC系統&#xff0c;不知道使用windows系統是否也可以 react-box 項目構建 localhost:ReactDapp liyuechun$ truffle unbox react-boxStarting unbox... ? Preparing to download box ? Downloading Unbox failed! RequestError: Error: connect…

區塊鏈相關問題 理解

本博客針對區塊鏈的部分問題進行詳解&#xff0c;希望幫助大家對于區塊鏈有一個更好的理解 1&#xff0c;如果散布虛假交易&#xff0c;怎么整&#xff1f;&#xff08;誰欠我XXX比特幣&#xff09; 1&#xff0c;可以發起虛假交易&#xff0c;但是很難被全網的節點接收并寫入…

區塊鏈的相關知識點

1&#xff0c;密碼學原理 密碼學原理 1&#xff0c;collision resistance 哈希函數 &#xff0c;目前還是很安全的&#xff0c;沒有找到任何不同的內容其哈希是一致的情形。哈希碰撞&#xff08;這個在數學上面是沒法證明的&#xff0c;都是日常的實踐表明&#xff0c;無法找…

使用MAC 編譯合約的時候報錯:

編譯報錯的顯示內容如下&#xff1a; Error: EACCES: permission denied, mkdir /Users/chy/Documents/ipfslearn/ipfs_eth_img/client/src/contracts 最簡單的解決方法 原因分析&#xff1a;執行的權限不夠 解決辦法&#xff1a;升級權限 使用命令 chmod 777 項目的文件夾使…

最新,使用truffle框架之后,安裝ipfs的api包文件的命令變化

原因 項目文件路徑變化了&#xff0c;不是先前直接在項目文件夾里面直接執行安裝ipfs的命令了&#xff0c;需要切換到client文件夾下面執行 路徑不對&#xff0c;會出現錯誤&#xff0c;錯誤提示如下 切換到正確的路徑之后&#xff0c;使用命令 npm install --save ipfs-http-…

通過node 完成簡單的web3 API調用測試

使用命令查看當前web3的版本 npm list web3輸入命令 node&#xff0c;進入終端 引入web3&#xff0c;使用命令 var web3 require(web3) 查看引入web3的版本 web3.version

LInux學習筆記(四)-----實操排雷

參考書籍&#xff1a;linux就該這么學 4 Vim 編輯器與 Shell 命令腳本 4.1 Vim 文本編輯器(在 Vim 中&#xff0c;無法直接從輸入模式切換到末行模式) 4.1.1 配置 Yum 軟件倉庫 注&#xff1a;書中好像有點問題&#xff0c;第一個 . 改成 /&#xff08;我是Ubuntu發現原來不能用…