DB是GNU開源組織釋出的一個強大的UNIX下的程式調試工具,GDB主要可幫助工程師完成下面4個方面的功能:
- 啟動程式,可以按照工程師自定義的要求随心所欲的運作程式。
- 讓被調試的程式在工程師指定的斷點處停住,斷點可以是條件表達式。
- 當程式被停住時,可以檢查此時程式中所發生的事,并追索上文。
- 動态地改變程式的執行環境。
不管是調試Linux核心空間的驅動還是調試使用者空間的應用程式,掌握gdb的用法都是必須。而且,調試核心和調試應用程式時使用的gdb指令是完全相同的,下面以代碼清單22.2的應用程式為例示範gdb調試器的用法。
[cpp]
view plain
copy
- 1 int add(int a, int b)
- 2 {
- 3 return a + b;
- 4 }
- 5
- 6 main()
- 7 {
- 8 int sum[10] =
- 9 {
- 10 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
- 11 } ;
- 12 int i;
- 13
- 14 int array1[10] =
- 15 {
- 16 48, 56, 77, 33, 33, 11, 226, 544, 78, 90
- 17 };
- 18 int array2[10] =
- 19 {
- 20 85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4
- 21 };
- 22
- 23 for (i = 0; i < 10; i++)
- 24 {
- 25 sum[i] = add(array1[i], array2[i]);
- 26 }
- 27 }
使用指令gcc –g gdb_example.c –o gdb_example編譯上述程式,得到包含調試資訊的二進制檔案example,執行gdb gdb_example指令進入調試狀态:
[cpp] view plain copy
- [[email protected] driver_study]# gdb gdb_example
- 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)
1、list指令
在gdb中運作list指令(縮寫l)可以列出代碼,list的具體形式包括:
- list <linenum> ,顯示程式第linenum行周圍的源程式,如:
[cpp] view plain copy
- (gdb) list 15
- 10
- 11 int array1[10] =
- 12 {
- 13 48, 56, 77, 33, 33, 11, 226, 544, 78, 90
- 14 };
- 15 int array2[10] =
- 16 {
- 17 85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4
- 18 };
- 19
- list <function> ,顯示函數名為function的函數的源程式,如:
[cpp] view plain copy
- (gdb) list main
- 2 {
- 3 return a + b;
- 4 }
- 5
- 6 main()
- 7 {
- 8 int sum[10];
- 9 int i;
- 10
- 11 int array1[10] =
- list,顯示目前行後面的源程式。
- list - ,顯示目前行前面的源程式。
下面示範了使用gdb中的run(縮寫r)、break(縮寫b)、next(縮寫n)指令控制程式的運作,并使用print(縮寫p)指令列印程式中的變量sum的過程:
(gdb) break add
Breakpoint 1 at 0x80482f7: file gdb_example.c, line 3.
(gdb) run
Starting program: /driver_study/gdb_example
Breakpoint 1, add (a=48, b=85) at gdb_example.c:3
warning: Source file is more recent than executable.
3 return a + b;
(gdb) next
4 }
(gdb) next
main () at gdb_example.c:23
23 for (i = 0; i < 10; i++)
(gdb) next
25 sum[i] = add(array1[i], array2[i]);
(gdb) print sum
$1 = {133, 0, 0, 0, 0, 0, 0, 0, 0, 0}
2、run指令
在gdb中,運作程式使用run指令。在程式運作前,我們可以設定如下4方面的工作環境:
- 程式運作參數
set args 可指定運作時參數,如: set args 10 20 30 40 50;show args 指令可以檢視設定好的運作參數。
- 運作環境
path <dir> 可設定程式的運作路徑;how paths可檢視程式的運作路徑;set environment varname [=value]用于設定環境變量,如set env USER=baohua;
show environment [varname]則用于檢視環境變量。
- 工作目錄
cd <dir> 相當于shell的cd指令;pwd 顯示目前所在的目錄。
- 程式的輸入輸出
info terminal 用于顯示程式用到的終端的模式;gdb中也可以使用重定向控制程式輸出,如run > outfile;
tty指令可以指定輸入輸出的終端裝置,如:tty /dev/ttyS1。
3、break指令
在gdb中用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>
“...”可以是上述的break <linenum>、break +offset / break –offset中的參數,condition表示條件,在條件成立時停住。比如在循環體中,可以設定break if i=100,表示當i為100時停住程式。
檢視斷點時,可使用info指令,如info breakpoints [n]、info break [n](n表示斷點号)。
4、單步指令
在調試過程中,next指令用于單步執行,類似VC++中的step over。next的單步不會進入函數的内部,與next對應的step(縮寫s)指令則在單步執行一個函數時,會進入其内部,類似VC++中的step into。下面示範了step指令的執行情況,在23行的add()函數調用處執行step會進入其内部的“return a+b;”語句:
[cpp] view plain copy
- (gdb) break 25
- Breakpoint 1 at 0x8048362: file gdb_example.c, line 25.
- (gdb) run
- Starting program: /driver_study/gdb_example
- Breakpoint 1, main () at gdb_example.c:25
- 25 sum[i] = add(array1[i], array2[i]);
- (gdb) step
- add (a=48, b=85) at gdb_example.c:3
- 3 return a + b;
單步執行的更複雜用法包括:
- step <count>
單步跟蹤,如果有函數調用,則進入該函數(進入函數的前提是,此函數被編譯有debug資訊)。step後面不加count表示一條條地執行,加表示執行後面的count條指令,然後再停住。
- next <count>
單步跟蹤,如果有函數調用,它不會進入該函數。同樣地,next後面不加count表示一條條地執行,加表示執行後面的count條指令,然後再停住。
- set step-mode
set step-mode on用于打開step-mode模式,這樣,在進行單步跟蹤時,程式不會因為沒有debug資訊而不停住,這個參數的設定可便于檢視機器碼。set step-mod off用于關閉step-mode模式。
- finish
運作程式,直到目前函數完成傳回,并列印函數傳回時的堆棧位址和傳回值及參數值等資訊。
- until (縮寫u)
一直在循環體内執行單步,退不出來是一件令人煩惱的事情,until指令可以運作程式直到退出循環體。
- stepi(縮寫si)和nexti(縮寫ni)
stepi和nexti用于單步跟蹤一條機器指令,一條程式代碼有可能由數條機器指令完成,stepi和nexti可以單步執行機器指令。 另外,運作“display/i $pc”指令後,單步跟蹤會在打出程式代碼的同時打出機器指令,即彙編代碼。
5、continue指令
當程式被停住後,可以使用continue指令(縮寫c,fg指令同continue指令)恢複程式的運作直到程式結束,或到達下一個斷點,指令格式為:
[cpp] view plain copy
- continue [ignore-count]
- c [ignore-count]
- fg [ignore-count]
ignore-count表示忽略其後多少次斷點。 假設我們設定了函數斷點 add(),并 watch i,則在continue過程中,每次遇到 add()函數或i發生變化,程式就會停住,如:
[cpp] view plain copy
- (gdb) continue
- Continuing.
- Hardware watchpoint 3: i
- Old value = 2
- New value = 3
- 0x0804838d in main () at gdb_example.c:23
- 23 for (i = 0; i < 10; i++)
- (gdb) continue
- Continuing.
- Breakpoint 1, main () at gdb_example.c:25
- 25 sum[i] = add(array1[i], array2[i]);
- (gdb) continue
- Continuing.
- Hardware watchpoint 3: i
- Old value = 3
- New value = 4
- 0x0804838d in main () at gdb_example.c:23
- 23 for (i = 0; i < 10; i++)
6、print指令
在調試程式時,當程式被停住時,可以使用print指令(縮寫為p),或是同義指令inspect來檢視目前程式的運作資料。print指令的格式是:
[cpp] view plain copy
- print <expr>
- print /<f> <expr>
<expr>是表達式,是被調試的程式中的表達式, <f>是輸出的格式,比如,如果要把表達式按16進制的格式輸出,那麼就是 /x。在表達式中,有幾種GDB所支援的操作符,它們可以用在任何一種語言中, “@”是一個和數組有關的操作符, “::”指定一個在檔案或是函數中的變量, “{<type>} <addr>”表示一個指向記憶體位址 <addr>的類型為type的一個對象。
下面示範了檢視sum[]數組的值的過程:
[cpp] view plain copy
- (gdb) print sum
- $2 = {133, 155, 0, 0, 0, 0, 0, 0, 0, 0}
- (gdb) next
- Breakpoint 1, main () at gdb_example.c:25
- 25 sum[i] = add(array1[i], array2[i]);
- (gdb) next
- 23 for (i = 0; i < 10; i++)
- (gdb) print sum
- $3 = {133, 155, 143, 0, 0, 0, 0, 0, 0, 0}
當需要檢視一段連續記憶體空間的值的時間,可以使用GDB的 “@”操作符, “@”的左邊是第一個記憶體位址, “@”的右邊則是想檢視記憶體的長度。例如如下動态申請的記憶體:
int *array = (int *) malloc (len * sizeof (int));
在GDB調試過程中這樣顯示出這個動态數組的值:
p *[email protected]
print的輸出格式包括:
- x 按十六進制格式顯示變量。
- d 按十進制格式顯示變量。
- u 按十六進制格式顯示無符号整型。
- o 按八進制格式顯示變量。
- t 按二進制格式顯示變量。
- a 按十六進制格式顯示變量。
- c 按字元格式顯示變量。
- f 按浮點數格式顯示變量。
我們可用display指令設定一些自動顯示的變量,當程式停住時,或是單步跟蹤時,這些變量會自動顯示。 如果要修改變量,如x的值,可使用如下指令:
print x=4
當用GDB的print檢視程式運作時的資料時,每一個print都會被GDB記錄下來。GDB會以$1,$2,$3 …這樣的方式為每一個print指令編号。我們可以使用這個編号通路以前的表達式,如$1。
7、watch指令
watch一般來觀察某個表達式(變量也是一種表達式)的值是否有變化了,如果有變化,馬上停住程式。我們有下面的幾種方法來設定觀察點: watch <expr>:為表達式(變量)expr設定一個觀察點。一量表達式值有變化時,馬上停住程式。rwatch <expr>:當表達式(變量)expr被讀時,停住程式。awatch <expr>:當表達式(變量)的值被讀或被寫時,停住程式。info watchpoints:列出目前所設定了的所有觀察點。 下面示範了觀察i并在連續運作next時一旦發現i變化,i值就會顯示出來的過程:
[cpp] view plain copy
- (gdb) watch i
- Hardware watchpoint 3: i
- (gdb) next
- 23 for (i = 0; i < 10; i++)
- (gdb) next
- Hardware watchpoint 3: i
- Old value = 0
- New value = 1
- 0x0804838d in main () at gdb_example.c:23
- 23 for (i = 0; i < 10; i++)
- (gdb) next
- Breakpoint 1, main () at gdb_example.c:25
- 25 sum[i] = add(array1[i], array2[i]);
- (gdb) next
- 23 for (i = 0; i < 10; i++)
- (gdb) next
- Hardware watchpoint 3: i
- Old value = 1
- New value = 2
- 0x0804838d in main () at gdb_example.c:23
- 23 for (i = 0; i < 10; i++)
8、examine指令
我們可以使用examine指令(縮寫為x)來檢視記憶體位址中的值。examine指令的文法如下所示:
x/<n/f/u> <addr>
<addr>表示一個記憶體位址。“x/”後的n、f、u都是可選的參數,n 是一個正整數,表示顯示記憶體的長度,也就是說從目前位址向後顯示幾個位址的内容;f 表示顯示的格式,如果位址所指的是字元串,那麼格式可以是s,如果位址是指令位址,那麼格式可以是i;u 表示從目前位址往後請求的位元組數,如果不指定的話,GDB預設是4位元組。u參數可以被一些字元代替:b表示單位元組,h表示雙位元組,w表示四位元組,g表示八位元組。當我們指定了位元組長度後,GDB會從指定的記憶體位址開始,讀寫指定位元組,并把其當作一個值取出來。n、f、u這3個參數可以一起使用,例如指令“x/3uh 0x54320”表示從記憶體位址0x54320開始以雙位元組為1個機關(h)、16進制方式(u)顯示3個機關(3)的記憶體。 ==
譬如下面的例子:
[cpp] view plain copy
- main()
- {
- char *c = "hello world";
- printf("%s\n", c);
- }
我們在 [cpp] view plain copy
- char *c = "hello world";
下一行設定斷點後: [cpp] view plain copy
- (gdb) l
- 1 main()
- 2 {
- 3 char *c = "hello world";
- 4 printf("%s\n", c);
- 5 }
- (gdb) b 4
- Breakpoint 1 at 0x100000f17: file main.c, line 4.
- (gdb) r
- Starting program: /Users/songbarry/main
- Reading symbols for shared libraries +. done
- Breakpoint 1, main () at main.c:4
- 4 printf("%s\n", c);
可以通過多種方式看C指向的字元串:
方法1:
[cpp] view plain copy
- (gdb) p c
- $1 = 0x100000f2e "hello world"
方法2: [cpp] view plain copy
- (gdb) x/s 0x100000f2e
- 0x100000f2e: "hello world"
方法3: [cpp] view plain copy
- (gdb) p (char *)0x100000f2e
- $3 = 0x100000f2e "hello world"
将第一個字元改為大寫: [cpp] view plain copy
- (gdb) p *(char *)0x100000f2e='H'
- $4 = 72 'H'
再看看C: [cpp] view plain copy
- (gdb) p c
- $5 = 0x100000f2e "Hello world"
9、set指令
修改寄存器:
[cpp] view plain copy
- (gdb) set $v0 = 0x004000000
- (gdb) set $epc = 0xbfc00000
修改記憶體:
[cpp] view plain copy
- (gdb) set {unsigned int}0x8048a51=0x0
譬如對于第8節的例子: [cpp] view plain copy
- (gdb) set {unsigned int}0x100000f2e=0x0
- (gdb) x/10cb 0x100000f2e
- 0x100000f2e: 0 '\0' 0 '\0' 0 '\0' 0 '\0' 111 'o' 32 ' ' 119 'w' 111 'o'
- 0x100000f36: 114 'r' 108 'l'
- (gdb) p c
- $10 = 0x100000f2e ""
10、jump指令
一般來說,被調試程式會按照程式代碼的運作順序依次執行,但是GDB也提供了亂序執行的功能,也就是說,GDB可以修改程式的執行順序,進而讓程式随意跳躍。這個功能可以由GDB的jump指令:jump <linespec> 來指定下一條語句的運作點。<linespec>可以是檔案的行号,可以是file:line格式,也可以是+num這種偏移量格式,表示下一條運作語句從哪裡開始。jump <address> 這裡的<address>是代碼行的記憶體位址。 注意,jump指令不會改變目前的程式棧中的内容,是以,如果使用jump從一個函數跳轉到另一個函數,當跳轉到的函數運作完傳回,進行出棧操作時必然會發生錯誤,這可能導緻意想不到的結果,是以最好隻用jump在同一個函數中進行跳轉。
11、signal指令
使用singal指令,可以産生一個信号量給被調試的程式,如中斷信号“Ctrl+C”。這非常友善于程式的調試,可以在程式運作的任意位置設定斷點,并在該斷點用GDB産生一個信号量,這種精确地在某處産生信号的方法非常有利于程式的調試。 signal指令的文法是:signal <signal>,UNIX的系統信号量通常從1到15,是以<signal>取值也在這個範圍。
12、return指令
如果在函數中設定了調試斷點,在斷點後還有語句沒有執行完,這時候我們可以使用return指令強制函數忽略還沒有執行的語句并傳回。
[cpp] view plain copy
- return
- return <expression>
上述return指令用于取消目前函數的執行,并立即傳回,如果指定了 <expression>,那麼該表達式的值會被作為函數的傳回值。
13、call指令
call指令用于強制調用某函數: call <expr> 表達式中可以一是函數,以此達到強制調用函數的目的,它會顯示函數的傳回值(如果函數傳回值不是void)。 其實,前面介紹的print指令也可以完成強制調用函數的功能。
14、info指令
info指令可以在調試時用來檢視寄存器、斷點、觀察點和信号等資訊。要檢視寄存器的值,可以使用如下指令: info registers (檢視除了浮點寄存器以外的寄存器)info all-registers (檢視所有寄存器,包括浮點寄存器)info registers <regname ...> (檢視所指定的寄存器) 要檢視斷點資訊,可以使用如下指令:info break 列出目前所設定的所有觀察點,使用如下指令:info watchpoints 檢視有哪些信号正在被GDB檢測,使用如下指令:info signals info handle 也可以使用info line指令來檢視源代碼在記憶體中的位址。info threads可以看多線程。info line後面可以跟行号、函數名、檔案名:行号、檔案名:函數名等多種形式,例如下面的指令會列印出所指定的源碼在運作時的記憶體位址:
[cpp] view plain copy
- info line tst.c:func
15、set scheduler-locking off|on|step
off 不鎖定任何線程,也就是所有線程都執行,這是預設值。
on 隻有目前被調試程式會執行。
step 在單步的時候,除了next過一個函數的情況以外,隻有目前線程會執行。
與多線程調試相關的指令還包括:
thread ID
切換目前調試的線程為指定ID的線程。
break thread_test.c:123 thread all
在所有線程中相應的行上設定斷點
thread apply ID1 ID2 command
讓一個或者多個線程執行GDB指令command。
thread apply all command
讓所有被調試線程執行GDB指令command。
16、disassemble
disassemble指令用于反彙編,它可被用來檢視目前執行時的源代碼的機器碼,其實際上隻是把目前記憶體中的指令dump出來。下面的示例用于檢視函數func的彙編代碼:
[cpp] view plain copy
- (gdb) disassemble func
- Dump of assembler code for function func:
- 0x8048450 <func>: push %ebp
- 0x8048451 <func+1>: mov %esp,%ebp
- 0x8048453 <func+3>: sub $0x18,%esp
- 0x8048456 <func+6>: movl $0x0,0xfffffffc(%ebp)
- ...
- End of assembler dump.