使用 GDB 偵錯工具
開啟 C 程式的調試功能
編譯器, 我們可以使用 gcc -S main.c 這樣來開啟調試並且這樣也能見到二進位的彙編. 編譯器時使用 -g 更加方便不但有二進位彙編,還有代碼本身 (注, 這時我們想看二進位結構,可以使用 objdump 加 -dS 參數).
測試範例代碼
#include
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 來增加代碼到二進位檔案中來. 代碼是在指定路徑,所以代碼檔案不存在並不行。
啟動調試:
# 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
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 就可以了。
(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 ,然後就會進入這個函數的內部。
(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)命令可以查看函數調用的棧幀
(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 命令來查看函數內部的局部變數的值。
(gdb) i locals
i = 0
sum = 0
這時想查看 main 函數內的局部變數也可以需要使用 fram 1(簡寫 f) 來轉到 1 號棧幀。接下來基本上都是使用 p 來顯示變數的內容
(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 就可以退出當前函數來執行其它的了。
在使用過程中想修改變數的話,可以象下面這樣操作
(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 就行。
(gdb) display sum
1: sum = 1634469985
(gdb) n
9 sum = sum*10 + input[1] - Ɔ'
1: sum = 1634469985
(gdb) n
8 for (i = 0; input[i] != '\0' i++)
1: sum = -835169374
這樣每就到了 sum 都會列印出來給我們顯示.不想顯示時可以使用 undisplay 來取消跟蹤顯示。
每次這樣顯示很累, 我們可以直接使用 break 命令來在指定行上打外斷點,如
(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] - Ɔ'
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] - Ɔ'
1: sum = -1254259506
break 可以指定行,也可以指定函數名。這個時候,在內部需要使用 continue 命令來連續運行,而非單步運行, 程式達到斷點就會自動停止下來。這樣可以自動停止在下一次迴圈內。
一次調試可以設定多個斷點, 可以使用 info 來查看設定好的斷點
(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 來刪除指定的斷點。
(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 命令重新啟動程式,然後我們可以這樣。
(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 顯示跟蹤號 取消跟蹤顯示
除了斷點外, 我們有時可能想知道, 當程式訪問有些儲存單元時中斷了, 我們不知道是哪個地方修改了這個儲存單元, 這時我們可以使用觀察點。
(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 命令看看是行引起的
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 行出錯。 有時我們調試, 查出段錯誤的時候,並沒有詳細內容, 段錯誤發生在 } 括弧結束的位置, 這可是常見問題,如果一個函數局部變數發生訪問越界, 有可能並不立刻產生段錯誤, 而是在函數返回的時候生產段錯誤。
如果對於數組,我們想顯示整組的資訊可以象下如下操作。
(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