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