[轉]GDB 進行程序調試筆記

轉自:http://www.php-oa.com/2015/03/11/gdb.html

這是 2011 年參加亞嵌的內核開發時,C 語言中 GDB 調試部分的筆記。因混於其它筆記一起, 特單獨立放一文章, 並且照原來的筆記複習了一下。

使用 GDB 調試程序

打開 C 程序的調試功能

編譯程序, 我們可以使用 gcc -S main.c 這樣來打開調試並且這樣也能見到二進制的彙編. 編譯程序時使用 -g 更加方便不但有二進制彙編,還有代碼本身 (注, 這時我們想看二進制結構,可以使用 objdump 加 -dS 參數).
測試樣例代碼

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#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\n result[1]=%d\n", result[0], result[1]);
    return 0;
}

常用 GDB 的調試命令

計算機在分配變量的時候, 局部變量是存儲在棧中, 全局變量是存儲在全局量段。進入函數時, 變量會放到棧頂,退出時會從棧頂拿掉。它是從存儲器頂部開始向下增長的。
啓動 GDB 的時候, 一定要保證加了 -g 來增加代碼到二進制文件中來. 代碼是在指定路徑,所以代碼文件不存在並不行。
啓動調試:

01
02
03
04
05
06
07
08
09
10
11
12
# gcc 11.c -g -o 11
# gdb main
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-64.el6_5.2)
Copyright (C) 2010 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 "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
Reading symbols from /root/fukai/c/11...done.
幫助和顯示

gdb 提供了一個類似 shell 一樣的命令行環境, 可以在這個提示符下輸入 help 命令來查看可用的命令類別。我們可以進一步查看看 help 顯示的內容類別下有哪些類別, 比如 files 只需要輸入 help files 就可以了。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
(gdb) help files
Specifying and examining files.
 
List of commands:
 
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
load -- Dynamically load FILE into the running program
nosharedlibrary -- Unload all shared object library symbols
path -- Add directory DIR(s) to beginning of search path for object files
pwd -- Print working directory

進入後,就可以使用 list 1 來列出源代碼。一次只列出 10 行。如果要在顯示第 11 行開始, 需要在次輸入 list, 也可以什麼都不輸入直接回車。
列出指定函數代碼: l 函數名

GDB 基本命令

進入後, 輸入 start 來啓動程序. 啓動後我們可以使用 next (簡寫爲 n) 來控制代碼一條一條執行, 注意 n 並不會進入函數內部. 這時如果執行到一個指定的函數上, 想進入,可以使用 s ,然後就會進入這個函數的內部。

01
02
03
04
05
06
07
08
09
10
11
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 2 at 0x4004f8: file 11.c, line 13.
Starting program: /root/fukai/c/11
 
Temporary breakpoint 2, main () at 11.c:13
13          result[0] = add_range(1, 10);
(gdb) s
add_range (low=1, high=10) at 11.c:5
5           for (i = low; i <= high; i++) {

進入函數後狀態的查看

上面講到使用 step 進入當前的函數後, 下面是怎麼樣來查看看函數內部的內容。
命令 backtrace (簡寫 bt)命令可以查看函數調用的棧幀

1
2
3
(gdb) bt
#0  add_range (low=1, high=10) at 11.c:5
#1  0x0000000000400507 in main () at 11.c:13

這時可以見到 main 函數的棧幀是 1 , add_range 的棧幀是 0,我們可以使用 info 命令來查看函數內部的局部變量的值。

1
2
3
(gdb) i locals
i = 0
sum = 0

這時想查看 main 函數內的局部變量也可以需要使用 fram 1(簡寫 f) 來轉到 1 號棧幀。接下來基本上都是使用 p 來顯示變量的內容

1
2
3
4
5
6
7
(gdb) p sum
$5 = 6
(gdb) finish
Run till exit from #0  add_range (low=1, high=10) at 11.c:6
0x0000000000400507 in main () at 11.c:13
13          result[0] = add_range(1, 10);
Value returned is $6 = 55

如果要退出當前函數,就象上面一樣,執行 finish 就可以退出當前函數來執行其它的了。
在使用過程中想修改變量的話,可以象下面這樣操作

1
2
3
4
5
6
7
8
(gdb) set var sum=0
(gdb) p sum       
$8 = 0
(gdb) finish
Run till exit from #0  add_range (low=1, high=100) at 11.c:6
0x000000000040051c in main () at 11.c:14
14          result[1] = add_range(1, 100);
Value returned is $9 = 5050

 gdb 基本命令

brcktrace 或 bt  查看各級函數調用和參數
finish 連續運行到當前函數結束,然後等待下一個命令。
frame 或 f   幀編號 選擇指定棧幀
info 或 locals 查看當前棧幀局部變量
list 或 l 列出上次位置以下的 10 行源碼
list 行號 列出第幾行開始的 10 行源碼
list 函數名 列出指定函數源碼
next 或 n 執行下一語句
print 或 p 打印表達式, 通過表達式可以修改變量值或者調用函數
quit 或 q 退出 gdb 環境
set var 修改變量值
start 開始執行到 main 函數第一行語句
step 或 s 執行下一語句, 如果有函數就進入

高級調試

如果我們想每次查看指定的變量的變化, 我們不需要每次都使用 printf 來打印, 我們只需要使用 display 就行。

1
2
3
4
5
6
7
8
(gdb) display sum
1: sum = 1634469985
(gdb) n
9                   sum = sum*10 + input[1] - '0';
1: sum = 1634469985
(gdb) n
8               for (i = 0; input[i] != '\0'; i++)
1: sum = -835169374

這樣每就到了 sum 都會打印出來給我們顯示.不想顯示時可以使用 undisplay 來取消跟蹤顯示。
每次這樣顯示很累, 我們可以直接使用 break 命令來在指定行上打外斷點,如

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
(gdb) l
3       int main(void){
4           int sum = 0, i = 0;
5           char input[5];
6           while (1) {
7               scanf("%s", input);
8               for (i = 0; input[i] != '\0'; i++)
9                   sum = sum*10 + input[1] - '0';
10              printf("input=%d\n", sum);
11          }
12          return 0;
(gdb) b 9
Breakpoint 2 at 0x40056c: file 12.c, line 9.
(gdb) c
Continuing.
 
Breakpoint 2, main () at 12.c:9
9                   sum = sum*10 + input[1] - '0';
1: sum = -1254259506

break 可以指定行,也可以指定函數名。這個時候,在內部需要使用 continue 命令來連續運行,而非單步運行, 程序達到斷點就會自動停止下來。這樣可以自動停止在下一次循環內。
一次調試可以設置多個斷點, 可以使用 info 來查看設置好的斷點

1
2
3
4
(gdb) i break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x000000000040056c in main at 12.c:9
        breakpoint already hit 6 times

可以使用 delete break 2 來刪除指定的斷點。

1
2
3
4
5
(gdb) disable b 3
(gdb) i b
Num     Type           Disp Enb Address            What
3       breakpoint     keep n   0x000000000040059c in main at 12.c:10
4       breakpoint     keep y   0x0000000000400563 in main at 12.c:8

在這通過 disable b 3 來禁用指定 Num 的斷點,如上所顯示在 Enb 下禁用的會變成 n. 這時也可以使用 enable 3 來啓用斷點。
條件斷點。 我們可以根據一些條件來選擇指定條件啓用斷點, 如我們要 sum 不爲 0 時顯示。我們現在使用 run 命令重新啓動程序,然後我們可以這樣。

1
2
3
(gdb)break 9 if sum != 0         
 
Breakpoint 4, main () at 12.c:8

本節 gdb 命令

break 或 b 行號 在指定行設置斷點
break 函數名 在指定函數開頭設置斷點
break … if … 設置斷點條件
cotinue 或 c 從當前位置開始連續運行程序
delete breakpoints 斷點號 刪除斷點
display 變量名 跟蹤查看變量,每次到這都會顯示它的值
disable breakpoints 斷點號 禁用斷點
enable 斷點號 啓用斷點
info 或 i breakpoints 查看當前設置了哪些斷點
run 或 r 從頭開始連接運行程序
undisplay 顯示跟蹤號 取消跟蹤顯示

除了斷點外, 我們有時可能想知道, 當程序訪問有些存儲單元時中斷了, 我們不知道是哪個地方修改了這個存儲單元, 這時我們可以使用觀察點。

01
02
03
04
05
06
07
08
09
10
11
12
(gdb) watch input[5]
Hardware watchpoint 2: input[5]
(gdb) c
Continuing.
123
input=123
12312
Hardware watchpoint 2: input[5]
 
Old value = 127 '\177'
New value = 0 '\000'
0x00007ffff7aa1e09 in _IO_vfscanf_internal () from /lib64/libc.so.6

這樣有修改就會顯示。注意上面, 上面這個地方出現段錯誤, gdb 每次會在段錯誤時會停止下來。這時我們可以看看到底哪行引起的段錯誤, gdb 上面顯示在 _IO_vfscanf 的函數上出錯, 我們使用 bt 命令看看是行引起的

1
2
3
4
5
6
7
Old value = 127 '\177'
New value = 0 '\000'
0x00007ffff7aa1e09 in _IO_vfscanf_internal () from /lib64/libc.so.6
(gdb) bt
#0  0x00007ffff7aa1e09 in _IO_vfscanf_internal () from /lib64/libc.so.6
#1  0x00007ffff7aad44d in __isoc99_scanf () from /lib64/libc.so.6
#2  0x000000000040056a in main () at 13.c:10

上面顯示在 13.c 的第 10 行出錯。 有時我們調試, 查出段錯誤的時候,並沒有詳細內容, 段錯誤發生在 } 括號結束的位置, 這可是常見問題,如果一個函數局部變量發生訪問越界, 有可能並不立刻產生段錯誤, 而是在函數返回的時候生產段錯誤。
如果對於數組,我們想顯示整組的信息可以象下如下操作。

1
2
(gdb) x/7b input
0x7fffffffe5c0: 49      50      51      0       -1      127     0

這我們可以使用 x 命令來打印指定的存儲單元的內容, 7b 是打印格式, b 表示每個字節一組, 7 表示打印 7 組。 從 input 數組的第一個字節開始連續打印 7 組。

watch 設置觀察點
info 或 i watchpoints 查看所有的觀察點
x 從指定位置的存儲單元開始打印內容,給內容會部當成字節來看,並不區分是哪個變量。

反彙編:  disassemble 默認這個是反彙編當前的函數, 如果要查看其它的,需要指定函數名或地址.
顯示寄存器信息: info registers 注意在 gdb 中表示寄存器需要前面加 $ .如  p $esp 可以打印這個寄存器的值

查看當前程序棧的內容: x/10x $sp–>打印stack的前10個元素
查看當前程序棧的參數: info args—lists arguments to the function
查看當前寄存器的值:info registers(不包括浮點寄存器) info all-registers(包括浮點寄存器)
查看當前棧幀中的異常處理器:info catch(exception handlers)

彙編常識:

默認 At&T 的彙編是從左向右,有前綴的.
esp 指向棧頂
ebp 指向棧低
eip 程序的計數器, 下次程序運行地址
eax 通用的寄存器, 函數中的返回值就存在這個中



發佈了112 篇原創文章 · 獲贊 56 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章