GDB学习总结
GDB十分鐘教程
http://blog.csdn.net/liigo/article/details/582231本文寫給主要工作在Windows操作系統下而又需要開發一些跨平臺軟件的程序員朋友,以及程序愛好者。
GDB是一個由GNU開源組織發布的、UNIX/LINUX操作系統下的、基于命令行的、功能強大的程序調試工具
。
GDB中的命令固然很多,但我們只需掌握其中十個左右的命令,就大致可以完成日常的基本的程序調試工
作。
?命令 解釋 示例
file <文件名> 加載被調試的可執行程序文件。
因為一般都在被調試程序所在目錄下執行GDB,因而文本名不需要帶路徑。 (gdb) file gdb-
sample
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
廢話不多說,下面開始實踐。
先給出一個示例用的小程序,C語言代碼,簡單的不能再簡單了:
//此程序僅作為“GDB十分鐘教程”的示例代碼, by liigo
//Email: liigo@sina.com
//blog: http://blog.csdn.net/liigo
//WebSite: www.liigo.com?
#include <stdio.h>
int nGlobalVar = 0;
int tempFunction(int a, int b)
{
? ? printf("tempFunction is called, a = %d, b = %d /n", a, b);
? ? return (a + b);
}
int main()
{
? ? int n;
? ? n = 1;
? ? n++;
? ? n--;
? ? nGlobalVar += 100;
? ? nGlobalVar -= 12;
? ? printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);
? ? n = tempFunction(1, 2);
? ? printf("n = %d", n);
? ? return 0;
}
請將此代碼復制出來并保存到文件 gdb-sample.c 中,然后切換到此文件所在目錄,用GCC編譯之:
gcc gdb-sample.c -o gdb-sample -g
在上面的命令行中,使用 -o 參數指定了編譯生成的可執行文件名為 gdb-sample,使用參數 -g 表示將
源代碼信息編譯到可執行文件中。如果不使用參數 -g,會給后面的GDB調試造成不便。當然,如果我們
沒有程序的源代碼,自然也無從使用 -g 參數,調試/跟蹤時也只能是匯編代碼級別的調試/跟蹤。
下面“gdb”命令啟動GDB,將首先顯示GDB說明,不管它:
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 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 certain conditions.
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-redhat-linux-gnu".
(gdb)?
上面最后一行“(gdb) ”為GDB內部命令引導符,等待用戶輸入GDB命令。
下面使用“file”命令載入被調試程序 gdb-sample(這里的 gdb-sample 即前面 GCC 編譯輸出的可執
行文件):
(gdb) file gdb-sample
Reading symbols from gdb-sample...done.
上面最后一行提示已經加載成功。
下面使用“r”命令執行(Run)被調試文件,因為尚未設置任何斷點,將直接執行到程序結束:
(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
n = 1, nGlobalVar = 88
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.
下面使用“b”命令在 main 函數開頭設置一個斷點(Breakpoint):
(gdb) b main
Breakpoint 1 at 0x804835c: file gdb-sample.c, line 19.
上面最后一行提示已經成功設置斷點,并給出了該斷點信息:在源文件 gdb-sample.c 第19行處設置斷
點;這是本程序的第一個斷點(序號為1);斷點處的代碼地址為 0x804835c(此值可能僅在本次調試過
程中有效)?;剡^頭去看源代碼,第19行中的代碼為“n = 1”,恰好是 main 函數中的第一個可執行語
句(前面的“int n;”為變量定義語句,并非可執行語句)。
再次使用“r”命令執行(Run)被調試程序:
(gdb) r
Starting program: /home/liigo/temp/gdb-sample
Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
程序中斷在gdb-sample.c第19行處,即main函數是第一個可執行語句處。
上面最后一行信息為:下一條將要執行的源代碼為“n = 1;”,它是源代碼文件gdb-sample.c中的第19
行。
下面使用“s”命令(Step)執行下一行代碼(即第19行“n = 1;”):
(gdb) s
20 n++;
上面的信息表示已經執行完“n = 1;”,并顯示下一條要執行的代碼為第20行的“n++;”。
既然已經執行了“n = 1;”,即給變量 n 賦值為 1,那我們用“p”命令(Print)看一下變量 n 的值
是不是 1 :
(gdb) p n
$1 = 1
果然是 1。($1大致是表示這是第一次使用“p”命令——再次執行“p n”將顯示“$2 = 1”——此信
息應該沒有什么用處。)
下面我們分別在第26行、tempFunction 函數開頭各設置一個斷點(分別使用命令“b 26”“b?
tempFunction”):
(gdb) b 26
Breakpoint 2 at 0x804837b: file gdb-sample.c, line 26.
(gdb) b tempFunction
Breakpoint 3 at 0x804832e: file gdb-sample.c, line 12.
使用“c”命令繼續(Continue)執行被調試程序,程序將中斷在第二個斷點(26行),此時全局變量?
nGlobalVar 的值應該是 88;再一次執行“c”命令,程序將中斷于第三個斷點(12行,tempFunction?
函數開頭處),此時tempFunction 函數的兩個參數 a、b 的值應分別是 1 和 2:
(gdb) c
Continuing.
Breakpoint 2, main () at gdb-sample.c:26
26 printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar);
(gdb) p nGlobalVar
$2 = 88
(gdb) c
Continuing.
n = 1, nGlobalVar = 88
Breakpoint 3, tempFunction (a=1, b=2) at gdb-sample.c:12
12 printf("tempFunction is called, a = %d, b = %d /n", a, b);
(gdb) p a
$3 = 1
(gdb) p b
$4 = 2
上面反饋的信息一切都在我們預料之中,哈哈~~~
再一次執行“c”命令(Continue),因為后面再也沒有其它斷點,程序將一直執行到結束:
(gdb) c
Continuing.
tempFunction is called, a = 1, b = 2
n = 3
Program exited normally.
有時候需要看到編譯器生成的匯編代碼,以進行匯編級的調試或跟蹤,又該如何操作呢?
這就要用到display命令“display /i $pc”了(此命令前面已有詳細解釋):
(gdb) display /i $pc
(gdb)?
此后程序再中斷時,就可以顯示出匯編代碼了:
(gdb) r
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
Breakpoint 1, main () at gdb-sample.c:19
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)
看到了匯編代碼,“n = 1;”對應的匯編代碼是“movl $0x1,0xfffffffc(%ebp)”。
并且以后程序每次中斷都將顯示下一條匯編指定(“si”命令用于執行一條匯編代碼——區別于“s”執
行一行C代碼):
(gdb) si
20 n++;
1: x/i $pc 0x8048363 <main+23>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x08048366 20 n++;
1: x/i $pc 0x8048366 <main+26>: incl (%eax)
(gdb) si
21 n--;
1: x/i $pc 0x8048368 <main+28>: lea 0xfffffffc(%ebp),%eax
(gdb) si
0x0804836b 21 n--;
1: x/i $pc 0x804836b <main+31>: decl (%eax)
(gdb) si
23 nGlobalVar += 100;
1: x/i $pc 0x804836d <main+33>: addl $0x64,0x80494fc
接下來我們試一下命令“b *<函數名稱>”。
為了更簡明,有必要先刪除目前所有斷點(使用“d”命令——Delete breakpoint):
(gdb) d
Delete all breakpoints? (y or n) y
(gdb)
當被詢問是否刪除所有斷點時,輸入“y”并按回車鍵即可。
下面使用命令“b *main”在 main 函數的 prolog 代碼處設置斷點(prolog、epilog,分別表示編譯器
在每個函數的開頭和結尾自行插入的代碼):
(gdb) b *main
Breakpoint 4 at 0x804834c: file gdb-sample.c, line 17.
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/liigo/temp/test_jmp/test_jmp/gdb-sample
Breakpoint 4, main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834c <main>: push %ebp
(gdb) si
0x0804834d 17 {
1: x/i $pc 0x804834d <main+1>: mov %esp,%ebp
(gdb) si
0x0804834f in main () at gdb-sample.c:17
17 {
1: x/i $pc 0x804834f <main+3>: sub $0x8,%esp
(gdb) si
0x08048352 17 {
1: x/i $pc 0x8048352 <main+6>: and $0xfffffff0,%esp
(gdb) si
0x08048355 17 {
1: x/i $pc 0x8048355 <main+9>: mov $0x0,%eax
(gdb) si
0x0804835a 17 {
1: x/i $pc 0x804835a <main+14>: sub %eax,%esp
(gdb) si
19 n = 1;
1: x/i $pc 0x804835c <main+16>: movl $0x1,0xfffffffc(%ebp)
此時可以使用“i r”命令顯示寄存器中的當前值———“i r”即“Infomation Register”:
(gdb) i r
eax 0xbffff6a4 -1073744220
ecx 0x42015554 1107383636
edx 0x40016bc8 1073834952
ebx 0x42130a14 1108544020
esp 0xbffff6a0 0xbffff6a0
ebp 0xbffff6a8 0xbffff6a8
esi 0x40015360 1073828704
edi 0x80483f0 134513648
eip 0x8048366 0x8048366
eflags 0x386 902
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x33 51
當然也可以顯示任意一個指定的寄存器值:
(gdb) i r eax
eax 0xbffff6a4 -1073744220
最后一個要介紹的命令是“q”,退出(Quit)GDB調試環境:
(gdb) q
The program is running. Exit anyway? (y or n) y
?
版權所有:liigo.com
轉載請事先征得作者liigo同意。
liigo@sina.com
www.liigo.com
http://blog.csdn.net/liigo/
QQ: 175199125
========
gdb
UNIX及UNIX-like下的調試工具?;蛟S,各位比較喜歡那種圖形界面方式的,像VC、BCB等IDE的調試,但如果你是在 UNIX平臺下做軟件,你會發現GDB這個調試工具有比VC、BCB的圖形化調試器更強大的功能。
所謂“寸有所長,尺有所短”就是這個道理。
外文名 GDB 發布組織GNU開源組織 類 ? ?型 強大的UNIX下的程序調試工具 功 ? ?能 動態的改變你程
序的執行環境等
目錄
1 功能
2 版本發布
3 文件清單
4 執行程序
5 顯示數據
6 斷點
7 斷點管理
8 變量檢查賦值
9 單步執行
10 函數調用
11 機器語言工具
12 信號
13 GDB使用
功能
一般來說,GDB主要幫助你完成下面四個方面的功能:
1、啟動你的程序,可以按照你的自定義的要求隨心所欲的運行程序。
2、可讓被調試的程序在你所指定的調置的斷點處停住。(斷點可以是條件表達式)
3、當程序被停住時,可以檢查此時你的程序中所發生的事。
4、你可以改變你的程序,將一個BUG產生的影響修正從而測試其他BUG。
《GDB使用手冊》
版本發布
2009年12月29日,程序調試工具 GDB 7.0.1 發布,新版本修正了7.0版本的一些嚴重的堆棧溢出bug,這
些bug可能導致 GDB 調試進程中斷,修正了在 FreeBSD 和 IRⅨ 系統下無法編譯的問題,增加了對?
Thumb2調試的支持,還有其他一些小bug的修復。
2010年03月19日,GDB 7.1 發布,
詳細改進內容:多程序調試的支持;
位置獨立的可執行文件(派)調試的支持;
新的目標(包括一個模擬器):Xilinx MicroBlaze和瑞薩RX;
Python支持增強;
c++支持擴展;
新tracepoint功能;
過程記錄的改進;
遠程協議擴展。
2010年09月06日 ,GDB 7.2 發布,
該版本改進記錄:
⒈ 支持D語言
⒉ C++ 改進,支持參數依賴查找ADL,靜態常量類成員和改進了用戶自定義操作符的支持
⒊ Python 調試的改進,包括斷點、符號、符號表、程序空間、線程等可通過命令行進行操作
⒋ Furthermore,enhancements were made for tracepoints and for GDBserver.在跟蹤點和GDB程序上
有了改善。
⒌ 支持 ARM Symbian 平臺
⒍ 其他方面的改進和bug修復。
2011年08月26日,GDB 7.3a 發布,
變化:
1。GDB可以理解線程的名字。
2。這個命令”線程名稱”(指定一個名稱)和“線程找到[REGEXP]”(匹配名稱、目標ID,或者額外的
信息)被添加。
3。Python腳本支持是大大增強。
4。在c++的支持,異常處理是提高,模板參數放在范圍在一個實例化時調試。
5。線程調試的核心轉儲在GNU / Linux成為可能。
6。最初支持C語言版本的OpenCL。
7。許多其他改進。
文件清單
List
(gdb) list line1,line2
查看源代碼
list lineNum 在lineNum的前后源代碼顯示出來
list + 列出當前行的后面代碼行
list - 列出當前行的前面代碼行
list function
set listsize count
設置顯示代碼的行數
show listsize
顯示打印代碼的行數
list first,last
顯示從first到last的源代碼行
執行程序
要想運行準備調試的程序,可使用run命令,在它后面可以跟隨發給該程序的任何參數,包括標準輸入和
標準輸出說明符(<;和>;)和shell通配符(*、?、[、])在內。如果你使用不帶參數的run命令,
gdb就再次使用你給予前一條run命令的參數,這是很有用的。利用set args 命令就可以修改發送給程序
的參數,而使用show args 命令就可以查看其缺省參數的列表。
(gdb) file a.out //加載被調試的可執行程序文件。
(gdb)set args –b –x
(gdb) show args
(gdb)r //執行程序
backtrace命令為堆棧提供向后跟蹤功能。
Backtrace 命令產生一張列表,包含著從最近的過程開始的所有有效過程和調用這些過程的參數。
顯示數據
利用print 命令可以檢查各個變量的值。
(gdb) print p (p為變量名)
print 是gdb的一個功能很強的命令,利用它可以顯示被調試的語言中任何有效的表達式。表達式除了包
含你程序中的變量外,還可以包含以下內容:
對程序中函數的調用
(gdb) print find_entry(1,0)
數據結構和其他復雜對象
(gdb) print *table_start
={e=reference=’\000’,location=0x0,next=0x0}
值的歷史成分
(gdb)print (為歷史記錄變量,在以后可以直接引用的值)
人為數組
人為數組提供了一種去顯示存儲器塊(數組節或動態分配的存儲區)內容的方法。早期的調試程序沒有
很好的方法將任意的指針換成一個數組。就像對待參數一樣,讓我們查看內存中在變量h后面的10個整數
,一個動態數組的語法如下所示:
base@length
因此,要想顯示在h后面的10個元素,可以使用h@10:
(gdb)print h@10
=(-1,345,23,-234,0,0,0,98,345,10)
whatis命令可以顯示某個變量的類型
(gdb) whatis p
type = int *
斷點
break命令(可以簡寫為b)可以用來在調試的程序中設置斷點,該命令有如下四種形式:
break line-number 使程序恰好在執行給定行之前停止。
break function-name 使程序恰好在進入指定的函數之前停止。
break line-or-function if condition 如果condition(條件)是真,程序到達指定行或函數時停止。
break routine-name 在指定例程的入口處設置斷點
如果該程序是由很多原文件構成的,你可以在各個原文件中設置斷點,而不是在當前的原文件中設置斷
點,其方法如下:
(gdb) break filename:line-number
(gdb) break filename:function-name
要想設置一個條件斷點,可以利用break if命令,如下所示:
(gdb) break line-or-function if expr
例:
(gdb) break 46 if testsize==100
從斷點繼續運行:continue 命令
斷點管理
1.顯示當前gdb的斷點信息:
(gdb) info break
他會以如下的形式顯示所有的斷點信息:
Num Type Disp Enb Address What
1 breakpoint keep y 0x000028bc in init_random at qsort2.c:155
2 breakpoint keep y 0x0000291c in init_organ at qsort2.c:168
刪除指定的某個斷點:
(gdb) delete breakpoint 1
該命令將會刪除編號為1的斷點,如果不帶編號參數,將刪除所有的斷點
(gdb) delete breakpoint
禁止使用某個斷點
(gdb) disable breakpoint 1
該命令將禁止斷點1,同時斷點信息的 (Enb)域將變為 n
允許使用某個斷點
(gdb) enable breakpoint 1
該命令將允許斷點1,同時斷點信息的 (Enb)域將變為 y
清除源文件中某一代碼行上的所有斷點
(gdb)clear number
注:number 為源文件的某個代碼行的行號
變量檢查賦值
whatis:識別數組或變量的類型
ptype:比whatis的功能更強,他可以提供一個結構的定義
set variable = value:將值賦予變量
print variable = value or p variable = value : 除了顯示一個變量的值外,還可以用來賦值
單步執行
next 不進入的單步執行
step 進入的單步執行如果已經進入了某函數,而想退出該函數返回到它的調用函數中,可使用命令
finish
函數調用
call name 調用和執行一個函數
(gdb) call gen_and_sork(1234,1,0)
(gdb) call printf(“abcd”)
=4
finish 結束執行當前函數,顯示其返回值(如果有的話)
機器語言工具
有一組專用的gdb變量可以用來檢查和修改計算機的通用寄存器,gdb提供了目 前每一臺計算機中實際使
用的4個寄存器的標準名字:
$pc :程序計數器
$fp :幀指針(當前堆棧幀)
$sp :棧指針
$ps :處理器狀態
信號
gdb通??梢圆蹲降桨l送給它的大多數信號,通過捕捉信號,它就可決定對于正在運行的進程要做些什么
工作。例如,按CTRL-C將中斷信號發送給gdb,通常就會終止gdb。但是你或許不想中斷gdb,真正的目的
是要中斷gdb正在運行的程序,因此,gdb要抓住該信號并停止它正在運行的程序,這樣就可以執行某些
調試操作。
Handle命令可控制信號的處理,他有兩個參數,一個是信號名,另一個是接受到信號時該作什么。幾種
可能的參數是:
nostop 接收到信號時,不要將它發送給程序,也不要停止程序。
stop 接受到信號時停止程序的執行,從而允許程序調試;顯示一條表示已接受到信號的消息(禁止使用
消息除外)
print 接受到信號時顯示一條消息
noprint 接受到信號時不要顯示消息(而且隱含著不停止程序運行)
pass 將信號發送給程序,從而允許你的程序去處理它、停止運行或采取別的動作。
nopass 停止程序運行,但不要將信號發送給程序。
例如,假定你截獲SIGPIPE信號,以防止正在調試的程序接受到該信號,而且只要該信號一到達,就要求
該程序停止,并通知你。要完成這一任務,可利用如下命令:
(gdb) handle SIGPIPE stop print
請注意,UNⅨ的信號名總是采用大寫字母!你可以用信號編號替代信號名如果你的程序要執行任何信號
處理操作,就需要能夠測試其信號處理程序,為此,就需要一種能將信號發送給程序的簡便方法,這就
是signal命令的任務。該命令的參數是一個數字或者一個名字,如SIGINT。假定你的程序已將一個專用
的SIGINT(鍵盤輸入,或CTRL-C;信號2)信號處理程序設置成采取某個清理動作,要想測試該信號處理
程序,你可以設置一個斷點并使用如下命令:
(gdb) signal 2
continuing with signal SIGINT⑵
該程序繼續執行,但是立即傳輸該信號,而且處理程序開始運行。
GDB使用
GDB是一個強大的命令行調試工具。大家知道命令行的強大就是在于,其可以形成
執行序列,形成腳本。UNⅨ下的軟件全是命令行的,這給程序開發提代供了極大的
便利,命令行軟件的優勢在于,它們可以非常容易的集成在一起,使用幾個簡單的已
有工具的命令,就可以做出一個非常強大的功能。
于是UNⅨ下的軟件比Windows下的軟件更能有機地結合,各自發揮各自的長處,組合
成更為強勁的功能。而Windows下的圖形軟件基本上是各自為營,互相不能調用,很
不利于各種軟件的相互集成。在這里并不是要和Windows做個什么比較,所謂“寸有
所長,尺有所短”,圖形化工具還有時不如命令行的地方。
========
Linux學習--gdb調試
http://www.cnblogs.com/hankers/archive/2012/12/07/2806836.html一.gdb常用命令:
命令 描述
backtrace(或bt) 查看各級函數調用及參數
finish 連續運行到當前函數返回為止,然后停下來等待命令
frame(或f) 幀編號 選擇棧幀
info(或i) locals 查看當前棧幀局部變量的值
list(或l) 列出源代碼,接著上次的位置往下列,每次列10行
list 行號 列出從第幾行開始的源代碼
list 函數名 列出某個函數的源代碼
next(或n) 執行下一行語句
print(或p) 打印表達式的值,通過表達式可以修改變量的值或者調用函數
quit(或q) 退出gdb調試環境
set var 修改變量的值
start 開始執行程序,停在main函數第一行語句前面等待命令
step(或s) 執行下一行語句,如果有函數調用則進入到函數中
二.gdb學習小例:
#include <stdio.h>
int add_range(int low, int high)
{
int i, sum;
for (i = low; i <= high; i++)
sum = sum + i;
return sum;
}
int main(void)
{
int result[100];
result[0] = add_range(1, 10);
result[1] = add_range(1, 100);
printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
return 0;
}
?
add_range函數從low加到high,在main函數中首先從1加到10,把結果保存下來,然后從1加到100,再把
結果保存下來,最后打印的兩個結果是:
result[0]=55
result[1]=5105
第一個結果正確[20],第二個結果顯然不正確,在小學我們就聽說過高斯小時候的故事,從1加到100應
該是5050。一段代碼,第一次運行結果是對的,第二次運行卻不對,這是很常見的一類錯誤現象,這種
情況不應該懷疑代碼而應該懷疑數據,因為第一次和第二次運行的都是同一段代碼,如果代碼是錯的,
那為什么第一次的結果能對呢?然而第一次和第二次運行時相關的數據卻有可能不同,錯誤的數據會導
致錯誤的結果。在動手調試之前,讀者先試試只看代碼能不能看出錯誤原因,只要前面幾章學得扎實就
應該能看出來。
在編譯時要加上-g選項,生成的可執行文件才能用gdb進行源碼級調試:
$ gcc -g main.c -o main
$ gdb main
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. ?Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb)?
-g選項的作用是在可執行文件中加入源代碼的信息,比如可執行文件中第幾條機器指令對應源代碼的第
幾行,但并不是把整個源文件嵌入到可執行文件中,所以在調試時必須保證gdb能找到源文件。gdb提供
一個類似Shell的命令行環境,上面的(gdb)就是提示符,在這個提示符下輸入help可以查看命令的類別
:
(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 the program
user-defined -- User-defined commands
Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
也可以進一步查看某一類別中有哪些命令,例如查看files類別下有哪些命令可用:
(gdb) help files
Specifying and examining files.
List of commands:
add-shared-symbol-files -- Load the symbols from shared objects in the dynamic linker's?
link map
add-symbol-file -- Load symbols from FILE
add-symbol-file-from-memory -- Load the symbols out of memory from a dynamically loaded?
object file
cd -- Set working directory to DIR for debugger and program being debugged
core-file -- Use FILE as core dump for examining memory and registers
directory -- Add directory DIR to beginning of search path for source files
edit -- Edit specified file or function
exec-file -- Use FILE as program for getting contents of pure memory
file -- Use FILE as program to be debugged
forward-search -- Search for regular expression (see regex(3)) from last line listed
generate-core-file -- Save a core file with the current state of the debugged process
list -- List specified function or line
...
現在試試用list命令從第一行開始列出源代碼:
(gdb) list 1
1 #include <stdio.h>
2
3 int add_range(int low, int high)
4 {
5 int i, sum;
6 for (i = low; i <= high; i++)
7 sum = sum + i;
8 return sum;
9 }
10
一次只列10行,如果要從第11行開始繼續列源代碼可以輸入
(gdb) list
也可以什么都不輸直接敲回車,gdb提供了一個很方便的功能,在提示符下直接敲回車表示重復上一條命
令。
(gdb) (直接回車)
11 int main(void)
12 {
13 int result[100];
14 result[0] = add_range(1, 10);
15 result[1] = add_range(1, 100);
16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
17 return 0;
18
gdb的很多常用命令有簡寫形式,例如list命令可以寫成l,要列一個函數的源代碼也可以用函數名做參
數:
(gdb) l add_range
1 #include <stdio.h>
2
3 int add_range(int low, int high)
4 {
5 int i, sum;
6 for (i = low; i <= high; i++)
7 sum = sum + i;
8 return sum;
9 }
10
現在退出gdb的環境:
(gdb) quit
我們做一個實驗,把源代碼改名或移到別處再用gdb調試,這樣就列不出源代碼了:
$ mv main.c mian.c
$ gdb main
...
(gdb) l
5 main.c: No such file or directory.
in main.c
可見gcc的-g選項并不是把源代碼嵌入到可執行文件中的,在調試時也需要源文件。現在把源代碼恢復原
樣,我們繼續調試。首先用start命令開始執行程序:
$ gdb main
...
(gdb) start
Breakpoint 1 at 0x80483ad: file main.c, line 14.
Starting program: /home/akaedu/main?
main () at main.c:14
14 result[0] = add_range(1, 10);
(gdb)
gdb停在main函數中變量定義之后的第一條語句處等待我們發命令,gdb列出的這條語句是即將執行的下
一條語句。我們可以用next命令(簡寫為n)控制這些語句一條一條地執行:
(gdb) n
15 result[1] = add_range(1, 100);
(gdb) (直接回車)
16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
(gdb) (直接回車)
result[0]=55
result[1]=5105
17 return 0;
用n命令依次執行兩行賦值語句和一行打印語句,在執行打印語句時結果立刻打出來了,然后停在return
語句之前等待我們發命令。雖然我們完全控制了程序的執行,但仍然看不出哪里錯了,因為錯誤不在
main函數中而在add_range函數中,現在用start命令重新來過,這次用step命令(簡寫為s)鉆進
add_range函數中去跟蹤執行:
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Breakpoint 2 at 0x80483ad: file main.c, line 14.
Starting program: /home/akaedu/main?
main () at main.c:14
14 result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at main.c:6
6 for (i = low; i <= high; i++)
這次停在了add_range函數中變量定義之后的第一條語句處。在函數中有幾種查看狀態的辦法,
backtrace命令(簡寫為bt)可以查看函數調用的棧幀:
(gdb) bt
#0 ?add_range (low=1, high=10) at main.c:6
#1 ?0x080483c1 in main () at main.c:14
可見當前的add_range函數是被main函數調用的,main傳進來的參數是low=1, high=10。main函數的棧幀
編號為1,add_range的棧幀編號為0。現在可以用info命令(簡寫為i)查看add_range函數局部變量的值
:
(gdb) i locals
i = 0
sum = 0
如果想查看main函數當前局部變量的值也可以做到,先用frame命令(簡寫為f)選擇1號棧幀然后再查看
局部變量:
(gdb) f 1
#1 ?0x080483c1 in main () at main.c:14
14 result[0] = add_range(1, 10);
(gdb) i locals?
result = {0, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480,?
...
? -1208623680}
注意到result數組中有很多元素具有雜亂無章的值,我們知道未經初始化的局部變量具有不確定的值。
到目前為止一切正常。用s或n往下走幾步,然后用print命令(簡寫為p)打印出變量sum的值:
(gdb) s
7 sum = sum + i;
(gdb) (直接回車)
6 for (i = low; i <= high; i++)
(gdb) (直接回車)
7 sum = sum + i;
(gdb) (直接回車)
6 for (i = low; i <= high; i++)
(gdb) p sum
$1 = 3
第一次循環i是1,第二次循環i是2,加起來是3,沒錯。這里的$1表示gdb保存著這些中間結果,$后面的
編號會自動增長,在命令中可以用$1、$2、$3等編號代替相應的值。由于我們本來就知道第一次調用的
結果是正確的,再往下跟也沒意義了,可以用finish命令讓程序一直運行到從當前函數返回為止:
(gdb) finish
Run till exit from #0 ?add_range (low=1, high=10) at main.c:6
0x080483c1 in main () at main.c:14
14 result[0] = add_range(1, 10);
Value returned is $2 = 55
返回值是55,當前正準備執行賦值操作,用s命令賦值,然后查看result數組:
(gdb) s
15 result[1] = add_range(1, 100);
(gdb) p result
$3 = {55, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480,?
...
? -1208623680}
第一個值55確實賦給了result數組的第0個元素。下面用s命令進入第二次add_range調用,進入之后首先
查看參數和局部變量:
(gdb) s
add_range (low=1, high=100) at main.c:6
6 for (i = low; i <= high; i++)
(gdb) bt
#0 ?add_range (low=1, high=100) at main.c:6
#1 ?0x080483db in main () at main.c:15
(gdb) i locals?
i = 11
sum = 55
由于局部變量i和sum沒初始化,所以具有不確定的值,又由于兩次調用是挨著的,i和sum正好取了上次
調用時的值,原來這跟例 3.7 “驗證局部變量存儲空間的分配和釋放”是一樣的道理,只不過我這次舉
的例子設法讓局部變量sum在第一次調用時初值為0了。i的初值不是0倒沒關系,在for循環中會賦值為0
的,但sum如果初值不是0,累加得到的結果就錯了。好了,我們已經找到錯誤原因,可以退出gdb修改源
代碼了。如果我們不想浪費這次調試機會,可以在gdb中馬上把sum的初值改為0繼續運行,看看這一處改
了之后還有沒有別的Bug:
(gdb) set var sum=0
(gdb) finish
Run till exit from #0 ?add_range (low=1, high=100) at main.c:6
0x080483db in main () at main.c:15
15 result[1] = add_range(1, 100);
Value returned is $4 = 5050
(gdb) n
16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]);
(gdb) (直接回車)
result[0]=55
result[1]=5050
17 return 0;
這樣結果就對了。修改變量的值除了用set命令之外也可以用print命令,因為print命令后面跟的是表達
式,而我們知道賦值和函數調用也都是表達式,所以也可以用print命令修改變量的值或者調用函數:
(gdb) p result[2]=33
$5 = 33
(gdb) p printf("result[2]=%d\n", result[2])
result[2]=33
$6 = 13
========
總結
- 上一篇: 图解在Matlab中初步操作一下3D图形
- 下一篇: 图解notepad++插件使用