Linux gdb調試器用法全面解析


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

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

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

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命令進入調試狀態:

[root@localhost 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行周圍的源程序,如:

(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的函數的源程序,如:

(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命令,如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;”語句:

(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命令)恢復程序的運行直到程序結束,或到達下一個斷點,命令格式為:

continue [ignore-count]
c [ignore-count]
fg [ignore-count]

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

(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命令的格式是:

 print <expr>
 print /<f> <expr>

<expr>是表達式,是被調試的程序中的表達式,

<f>是輸出的格式,比如,如果要把表達式按16進制的格式輸出,那么就是/x。在表達式中,有幾種GDB所支持的操作符,它們可以用在任何一種語言中,“@”是一個和數組有關的操作符,“::”指定一個在文件或是函數中的變量,“{<type>} <addr>”表示一個指向內存地址<addr>的類型為type的一個對象。

下面演示了查看sum[]數組的值的過程:

    (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 *array@len

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值就會顯示出來的過程:

(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)的內存。 ==

譬如下面的例子:

main()
{
        char *c = "hello world";
        printf("%s\n", c);
}

我們在

char *c = "hello world";

下一行設置斷點后:

(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:

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

方法2:

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

方法3:

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

將第一個字符改為大寫:

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

再看看C:

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

9、set命令

修改寄存器:

(gdb) set $v0 = 0x004000000
(gdb) set $epc = 0xbfc00000 

修改內存:

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

譬如對於第8節的例子:

(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命令強制函數忽略還沒有執行的語句並返回。

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 列出當前所設置的所有觀察點,

  info signals info handle 查看有哪些信號正在被GDB檢測,

  info line命令來查看源代碼在內存中的地址。

  info threads可以看多線程。

  info line后面可以跟行號、函數名、文件名:行號、文件名:函數名等多種形式,例如下面的命令會打印出所指定的源碼在運行時的內存地址:

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的匯編代碼:

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

 本文轉載自:http://blog.csdn.net/21cnbao/article/details/7385161


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM