调试器是一种运行您的代码并检查其发现的任何问题的软件。 GNU 调试器 (GDB) 是最流行的调试器之一,在本文中,我将研究 GDB 的 step
命令和相关命令在几种常见用例中的应用。Step 是一个广泛使用的命令,但关于它的一些不太为人所知的事情可能会令人困惑。此外,还有一些方法可以在不实际使用 step
命令本身的情况下步入函数,例如使用不太为人所知的 advance
命令。
没有调试符号
考虑一个简单的示例程序
#include <stdio.h>
int num() {
return 2;
}
void bar(int i)
{
printf("i = %d\n", i);
}
int main()
{
bar(num());
return 0;
}
如果您首先在没有调试符号的情况下进行编译,在 bar
上设置断点,然后尝试步入其中。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 ()
Stepi
即使在没有行号信息的函数内部,仍然可以步入,但应该使用 stepi
命令。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
是程序计数器寄存器。
使用 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)
复杂的函数调用
在使用调试符号重新编译示例程序后,您可以使用其行号在 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());
现在,步入 bar()
(gdb) step
num () at exmp.c:4
4 return 2;
函数调用的参数需要在实际函数调用之前进行处理,因此 num()
预计会在 bar()
被调用之前执行。但是,如何按需步入 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);
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
上设置临时断点后,您只需要继续即可最终进入 bar
。
(gdb) continue
Continuing.
Temporary breakpoint 2, bar (i=2) at exmp.c:9
9 printf("i = %d\n", i);
禁用命令
或者,您可以在 bar
上设置一个普通断点,继续,然后在不再需要时禁用第二个断点。这样,您可以使用一个额外的命令实现与 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
Advance location
您可以使用的另一个选项是 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);
跳过函数
另一种步入 bar
,避免 num
的方法是使用 skip
命令
(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
。num
函数被标记为已启用跳过,标记为 y
(gdb) info skip
Num Enb Glob File RE Function
1 y n <none> n num
如果不再需要 skip
,可以禁用(并稍后重新启用)或完全删除。您可以添加另一个 skip
并禁用第一个,然后删除所有 skip
。要禁用某个 skip
,必须指定其编号;如果未指定,则每个 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 技术。
评论已关闭。