天天看點

Linux, gdb 調試指令1、list指令2、run指令3、break指令4、單步指令5、continue指令6、print指令7、watch指令8、examine指令9、set指令10、jump指令11、signal指令12、return指令13、call指令14、info指令15、set scheduler-locking off|on|step16、disassemble

DB是GNU開源組織釋出的一個強大的UNIX下的程式調試工具,GDB主要可幫助工程師完成下面4個方面的功能:

  • 啟動程式,可以按照工程師自定義的要求随心所欲的運作程式。
  • 讓被調試的程式在工程師指定的斷點處停住,斷點可以是條件表達式。
  • 當程式被停住時,可以檢查此時程式中所發生的事,并追索上文。
  • 動态地改變程式的執行環境。

不管是調試Linux核心空間的驅動還是調試使用者空間的應用程式,掌握gdb的用法都是必須。而且,調試核心和調試應用程式時使用的gdb指令是完全相同的,下面以代碼清單22.2的應用程式為例示範gdb調試器的用法。

[cpp] 
    view plain
    copy
   
  
          
  1. 1  int add(int a, int b)  
  2. 2  {  
  3. 3    return a + b;  
  4. 4  }  
  5. 5    
  6. 6  main()  
  7. 7  {  
  8. 8    int sum[10] =   
  9. 9    {  
  10. 10     0, 0, 0, 0, 0, 0, 0, 0, 0, 0       
  11. 11   }  ;  
  12. 12   int i;  
  13. 13     
  14. 14   int array1[10] =  
  15. 15   {  
  16. 16     48, 56, 77, 33, 33, 11, 226, 544, 78, 90  
  17. 17   };  
  18. 18   int array2[10] =  
  19. 19   {  
  20. 20     85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4  
  21. 21   };  
  22. 22   
  23. 23   for (i = 0; i < 10; i++)  
  24. 24   {  
  25. 25     sum[i] = add(array1[i], array2[i]);  
  26. 26   }  
  27. 27 }  

使用指令gcc –g gdb_example.c –o gdb_example編譯上述程式,得到包含調試資訊的二進制檔案example,執行gdb gdb_example指令進入調試狀态:

[cpp] view plain copy

  1. [[email protected] driver_study]# gdb gdb_example  
  2. GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)  
  3. Copyright 2003 Free Software Foundation, Inc.  
  4. GDB is free software, covered by the GNU General Public License, and you are  
  5. welcome to change it and/or distribute copies of it under certain conditions.  
  6. Type "show copying" to see the conditions.  
  7. There is absolutely no warranty for GDB.  Type "show warranty" for details.  
  8. This GDB was configured as "i386-redhat-linux-gnu"...  
  9. (gdb)  

1、list指令

在gdb中運作list指令(縮寫l)可以列出代碼,list的具體形式包括:

  • list <linenum> ,顯示程式第linenum行周圍的源程式,如:

[cpp] view plain copy

  1. (gdb) list 15  
  2. 10          
  3. 11        int array1[10] =  
  4. 12        {  
  5. 13          48, 56, 77, 33, 33, 11, 226, 544, 78, 90  
  6. 14        };  
  7. 15        int array2[10] =  
  8. 16        {  
  9. 17          85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4  
  10. 18        };  
  11. 19  
  • list <function> ,顯示函數名為function的函數的源程式,如:

[cpp] view plain copy

  1. (gdb) list main  
  2. 2       {  
  3. 3         return a + b;  
  4. 4       }  
  5. 5  
  6. 6       main()  
  7. 7       {  
  8. 8         int sum[10];  
  9. 9         int i;  
  10. 10          
  11. 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

  1. (gdb) break 25  
  2. Breakpoint 1 at 0x8048362: file gdb_example.c, line 25.  
  3. (gdb) run  
  4. Starting program: /driver_study/gdb_example   
  5. Breakpoint 1, main () at gdb_example.c:25  
  6. 25          sum[i] = add(array1[i], array2[i]);  
  7. (gdb) step  
  8. add (a=48, b=85) at gdb_example.c:3  
  9. 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

  1. continue [ignore-count]  
  2. c [ignore-count]  
  3. fg [ignore-count]  

ignore-count表示忽略其後多少次斷點。 假設我們設定了函數斷點 add(),并 watch i,則在continue過程中,每次遇到 add()函數或i發生變化,程式就會停住,如:

[cpp] view plain copy

  1. (gdb) continue  
  2. Continuing.  
  3. Hardware watchpoint 3: i  
  4. Old value = 2  
  5. New value = 3  
  6. 0x0804838d in main () at gdb_example.c:23  
  7. 23        for (i = 0; i < 10; i++)  
  8. (gdb) continue  
  9. Continuing.  
  10. Breakpoint 1, main () at gdb_example.c:25  
  11. 25          sum[i] = add(array1[i], array2[i]);  
  12. (gdb) continue  
  13. Continuing.  
  14. Hardware watchpoint 3: i  
  15. Old value = 3  
  16. New value = 4  
  17. 0x0804838d in main () at gdb_example.c:23  
  18. 23        for (i = 0; i < 10; i++)  

6、print指令

在調試程式時,當程式被停住時,可以使用print指令(縮寫為p),或是同義指令inspect來檢視目前程式的運作資料。print指令的格式是:

[cpp] view plain copy

  1. print <expr>  
  2. print /<f> <expr>  

<expr>是表達式,是被調試的程式中的表達式, <f>是輸出的格式,比如,如果要把表達式按16進制的格式輸出,那麼就是 /x。在表達式中,有幾種GDB所支援的操作符,它們可以用在任何一種語言中, “@”是一個和數組有關的操作符, “::”指定一個在檔案或是函數中的變量, “{<type>} <addr>”表示一個指向記憶體位址 <addr>的類型為type的一個對象。

下面示範了檢視sum[]數組的值的過程:

[cpp] view plain copy

  1. (gdb) print sum  
  2. $2 = {133, 155, 0, 0, 0, 0, 0, 0, 0, 0}  
  3. (gdb) next  
  4. Breakpoint 1, main () at gdb_example.c:25  
  5. 25          sum[i] = add(array1[i], array2[i]);  
  6. (gdb) next  
  7. 23        for (i = 0; i < 10; i++)  
  8. (gdb) print sum  
  9. $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

  1. (gdb) watch i  
  2. Hardware watchpoint 3: i  
  3. (gdb) next  
  4. 23        for (i = 0; i < 10; i++)  
  5. (gdb) next  
  6. Hardware watchpoint 3: i  
  7. Old value = 0  
  8. New value = 1  
  9. 0x0804838d in main () at gdb_example.c:23  
  10. 23        for (i = 0; i < 10; i++)  
  11. (gdb) next  
  12. Breakpoint 1, main () at gdb_example.c:25  
  13. 25          sum[i] = add(array1[i], array2[i]);  
  14. (gdb) next  
  15. 23        for (i = 0; i < 10; i++)  
  16. (gdb) next  
  17. Hardware watchpoint 3: i  
  18. Old value = 1  
  19. New value = 2  
  20. 0x0804838d in main () at gdb_example.c:23  
  21. 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

  1. main()  
  2. {  
  3.         char *c = "hello world";  
  4.         printf("%s\n", c);  
  5. }  

我們在 [cpp] view plain copy

  1. char *c = "hello world";  

下一行設定斷點後: [cpp] view plain copy

  1. (gdb) l  
  2. 1    main()  
  3. 2    {  
  4. 3        char *c = "hello world";  
  5. 4        printf("%s\n", c);  
  6. 5    }  
  7. (gdb) b 4  
  8. Breakpoint 1 at 0x100000f17: file main.c, line 4.  
  9. (gdb) r  
  10. Starting program: /Users/songbarry/main  
  11. Reading symbols for shared libraries +. done  
  12. Breakpoint 1, main () at main.c:4  
  13. 4        printf("%s\n", c);  

可以通過多種方式看C指向的字元串:

方法1:

[cpp] view plain copy

  1. (gdb) p c  
  2. $1 = 0x100000f2e "hello world"  

方法2: [cpp] view plain copy

  1. (gdb) x/s 0x100000f2e  
  2. 0x100000f2e:     "hello world"  

方法3: [cpp] view plain copy

  1. (gdb) p (char *)0x100000f2e  
  2. $3 = 0x100000f2e "hello world"  

将第一個字元改為大寫: [cpp] view plain copy

  1. (gdb) p *(char *)0x100000f2e='H'  
  2. $4 = 72 'H'  

再看看C: [cpp] view plain copy

  1. (gdb) p c  
  2. $5 = 0x100000f2e "Hello world"  

9、set指令

修改寄存器:

[cpp] view plain copy

  1. (gdb) set $v0 = 0x004000000  
  2. (gdb) set $epc = 0xbfc00000   

修改記憶體:

[cpp] view plain copy

  1. (gdb) set {unsigned int}0x8048a51=0x0  

譬如對于第8節的例子: [cpp] view plain copy

  1. (gdb) set {unsigned int}0x100000f2e=0x0         
  2. (gdb) x/10cb 0x100000f2e  
  3. 0x100000f2e:    0 '\0'  0 '\0'  0 '\0'  0 '\0'  111 'o' 32 ' '  119 'w' 111 'o'  
  4. 0x100000f36:    114 'r' 108 'l'  
  5. (gdb) p c  
  6. $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

  1. return  
  2. 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

  1. 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

  1. (gdb) disassemble func  
  2. Dump of assembler code for function func:  
  3. 0x8048450 <func>:       push   %ebp  
  4. 0x8048451 <func+1>:     mov    %esp,%ebp  
  5. 0x8048453 <func+3>:     sub    $0x18,%esp  
  6. 0x8048456 <func+6>:     movl   $0x0,0xfffffffc(%ebp)  
  7. ...  
  8. End of assembler dump.