mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-26 21:30:55 +08:00
262 lines
8.9 KiB
Markdown
262 lines
8.9 KiB
Markdown
[#]: subject: "7 pro tips for using the GDB step command"
|
||
[#]: via: "https://opensource.com/article/22/12/gdb-step-command"
|
||
[#]: author: "Alexandra https://opensource.com/users/ahajkova"
|
||
[#]: collector: "lkxed"
|
||
[#]: translator: "chai001125"
|
||
[#]: reviewer: "wxy"
|
||
[#]: publisher: "wxy"
|
||
[#]: url: "https://linux.cn/article-15362-1.html"
|
||
|
||
GDB 的 7 个单步调试命令
|
||
======
|
||
|
||
![][0]
|
||
|
||
> 即使是复杂的函数,也有几种方法可以单步调试,所以下次在排除代码故障时,可以尝试一下这些 GDB 技术。
|
||
|
||
**调试器** 是一个可以运行你的代码并检查问题的软件。[GNU Debugger][1](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;
|
||
}
|
||
```
|
||
|
||
如果你在没有 <ruby>调试符号<rt> debugging sysbols </rt></ruby> 的情况下进行编译(LCTT 译注:即在使用 `gcc` 编译程序时没有写 `-g` 选项),然后在 `bar` 上设置一个断点,然后尝试在这个函数内使用 `step` 来单步执行语句。GDB 会给出一个 <ruby>没有行号信息<rt> no line number information </rt></ruby> 的错误信息。
|
||
|
||
```
|
||
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` 通常很有用,这会在每一步之后**显示** <ruby>程序计数器<rt> program counter </rt></ruby> 的值和**相应的** <ruby>机器指令<rt> machine instruction </rt></ruby>:
|
||
|
||
```
|
||
(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 = %d\n", 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 = %d\n", 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 = %d\n", 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 = %d\n", 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 = %d\n", 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][a]
|
||
选题:[lkxed][b]
|
||
译者:[chai001125](https://github.com/chai001125)
|
||
校对:[wxy](https://github.com/wxy)
|
||
|
||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||
|
||
[a]: https://opensource.com/users/ahajkova
|
||
[b]: https://github.com/lkxed
|
||
[1]: https://opensource.com/article/21/3/debug-code-gdb
|
||
[0]: https://img.linux.net.cn/data/attachment/album/202212/19/093831nrjrmozx1mixmgii.jpg |