GDB 的 7 個單步調試命令
即使是複雜的函數,也有幾種方法可以單步調試,所以下次在排除代碼故障時,可以嘗試一下這些 GDB 技術。
調試器 是一個可以運行你的代碼並檢查問題的軟體。GNU Debugger(GBD)是最流行的調試器之一,在這篇文章中,我研究了 GDB 的 step
命令和其他幾種常見情況的相關命令。step
是一個被廣泛使用的命令,但它有一些人們不太了解的地方,可能會使得他們十分困惑。此外,還有一些方法可以在不使用 step
命令的情況下進入一個函數,比如使用不太知名的 advance
命令。
1、無調試符號
考慮以下這個簡單的示常式序:
#include <stdio.h>
int num() {
return 2;
}
void bar(int i) {
printf("i = %dn", i);
}
int main() {
bar(num());
return 0;
}
如果你在沒有 調試符號 的情況下進行編譯(LCTT 譯註:即在使用 gcc
編譯程序時沒有寫 -g
選項),然後在 bar
上設置一個斷點,然後嘗試在這個函數內使用 step
來單步執行語句。GDB 會給出一個 沒有行號信息 的錯誤信息。
gcc exmp.c -o exmp
gdb ./exmp
(gdb) b bar
Breakpoint 1 at 0x401135
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, 0x0000000000401135 in bar ()
(gdb) step
Single stepping until exit from function bar,
which has no line number information.
i = 2
0x0000000000401168 in main ()
2、stepi 命令
但是你仍然可以在沒有行號信息的函數內部單步執行語句,但要使用 stepi
命令來代替 step
。stepi
一次只執行一條指令。當使用 GDB 的 stepi
命令時,先做 display/i $pc
通常很有用,這會在每一步之後顯示 程序計數器 的值和相應的 機器指令 :
(gdb) b bar
Breakpoint 1 at 0x401135
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, 0x0000000000401135 in bar ()
(gdb) display/i $pc
1: x/i $pc
=> 0x401135 <bar+4>: sub $0x10,%rsp
在上述的 display
命令中,i
代表機器指令,$pc
表示程序計數器寄存器(即 PC 寄存器)。
使用 info registers
命令,來列印寄存器的內容,也是十分有用的。
(gdb) info registers
rax 0x2 2
rbx 0x7fffffffdbc8 140737488346056
rcx 0x403e18 4210200
(gdb) print $rax
$1 = 2
(gdb) stepi
0x0000000000401139 in bar ()
1: x/i $pc
=> 0x401139 <bar+8>: mov %edi,-0x4(%rbp)
3、複雜的函數調用
在帶調試符號的 -g
選項,重新編譯示常式序後,你可以使用行號在 main
中 bar
調用上設置斷點,然後再單步執行 bar
函數的語句:
gcc -g exmp.c -o exmp
gdb ./exmp
(gdb) b exmp.c:14
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
接下來,用 step
,來單步執行 bar()
函數的語句:
(gdb) step
num () at exmp.c:4
4 return 2;
函數調用的參數需要在實際的函數調用之前進行處理,bar()
函數的參數是 num()
函數,所以 num()
會在 bar()
被調用之前執行。但是,通過 GDB 調試,你怎麼才能如願以償地進入 bar()
函數呢?你可以使用 finish
命令,並再次使用 step
命令。
(gdb) finish
Run till exit from #0 num () at exmp.c:4
0x0000000000401161 in main () at exmp.c:14
14 bar(num());
Value returned is $1 = 2
(gdb) step
bar (i=2) at exmp.c:9
9 printf("i = %dn", i);
4、tbreak 命令
tbreak
命令會設置一個臨時斷點。如果你不想設置永久斷點,那麼這個命令是很有用的。舉個例子?,你想進入一個複雜的函數調用,例如 f(g(h()), i(j()), ...)
,在這種情況下,你需要一個很長的 step/finish/step
序列,才能到達 f
函數。如果你設置一個臨時斷點,然後再使用 continue
命令,這樣就不需要以上的序列了。為了證明這一點,你需要像以前一樣將斷點設置在 main
的 bar
調用上。然後在 bar
上設置臨時斷點。當到達該臨時斷點後,臨時斷點會被自動刪除。
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) tbreak bar
Temporary breakpoint 2 at 0x40113c: file exmp.c, line 9.
在調用 bar
的時候遇到斷點,並在 bar
上設置臨時斷點後,你只需要使用 continue
繼續運行直到 bar
結束。
(gdb) continue
Continuing.
Temporary breakpoint 2, bar (i=2) at exmp.c:9
9 printf("i = %dn", i);
5、disable 命令
類似地,你也可以在 bar
上設置一個正常的斷點,然後執行 continue
,然後在不再需要第二個斷點時,使用 disable
命令禁用這個斷點,這樣也能達到與 tbreak
相同的效果。
(gdb) b exmp.c:14
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) b bar
Breakpoint 2 at 0x40113c: file exmp.c, line 9.
(gdb) c
Continuing.
Breakpoint 2, bar (i=2) at exmp.c:9
9 printf("i = %dn", i);
(gdb) disable 2
正如你所看到的,info breakpoints
命令在 Enb
列下顯示為 n
,這意味著這個斷點已被禁用。但你也能在再次需要這個斷點時,再啟用它。
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000401157 in main at exmp.c:14
breakpoint already hit 1 time
2 breakpoint keep n 0x000000000040113c in bar at exmp.c:9
breakpoint already hit 1 time
(gdb) enable 2
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040116a in main at exmp.c:19
breakpoint already hit 1 time
2 breakpoint keep y 0x0000000000401158 in bar at exmp.c:14
breakpoint already hit 1 time
6、advance 命令運行程序到指定的位置
另一個進入函數內部的方法是 advance
命令。你可以簡單地用 advance bar
,來代替 tbreak bar ; continue
。這一命令會將程序繼續運行到指定的位置。
advance
命令的一個很棒的地方在於:如果程序並沒有到達你試圖進入的位置,那麼 GDB 將在當前函數運行完成後停止。因此,程序的執行會受到限制:
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) advance bar
bar (i=2) at exmp.c:9
9 printf("i = %dn", i);
7、skip 命令
進入 bar
函數的另一種方式是使用 skip num
命令:
(gdb) b exmp.c:14
Breakpoint 1 at 0x401157: file exmp.c, line 14.
(gdb) skip num
Function num will be skipped when stepping.
(gdb) r
Starting program: /home/ahajkova/exmp
Breakpoint 1, main () at exmp.c:14
14 bar(num());
(gdb) step
bar (i=2) at exmp.c:9
9 printf("i = %dn", i);
請使用 info skip
命令,來了解 GDB 跳過了哪些函數。num()
函數被標記為 y
,表示跳過了 num()
函數:
(gdb) info skip
Num Enb Glob File RE Function
1 y n <none> n num
如果不再需要 skip
,可以禁用(並稍後重新啟用)或完全刪除它。你可以添加另一個 skip
,並禁用第一個 skip
,然後全部刪除。要禁用某個 skip
,必須指定其編號(例如,skip disable 1
),如果沒有指定,則會禁用所有的 skip
。啟用或刪除 skip
的工作原理相同:
(gdb) skip bar
(gdb) skip disable 1
(gdb) info skip
Num Enb Glob File RE Function
1 n n <none> n num
2 y n <none> n bar
(gdb) skip delete
(gdb) info skip
Not skipping any files or functions.
GDB 的 step 命令
使用 GDB 的 step
命令是調試程序的一個有用工具。即使是複雜的函數,也有幾種方法可以單步調試這些函數,所以下次你在排除代碼問題的時候,可以嘗試一下這些 GDB 技術。
via: https://opensource.com/article/22/12/gdb-step-command
作者:Alexandra 選題:lkxed 譯者:chai001125 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive