gdb 笔记(09)— 常用命令示例说明(run、continue、next、step、until、finish、return、jump、print、backtrace、frame、thread等)

1. next

next 命令(简写为 n)是让 GDB 调到下一条命令去执行,这里的下一条命令不一定是代码的下一行,而是根据程序逻辑跳转到相应的位置。举个例子:

int a = 0;
if (a == 9)
{
    print("a is equal to 9.\n");
}

int b = 10;
print("b = %d.\n", b);

如果当前 GDB 中断在上述代码第 2 行,此时输入 next 命令 GDB 将调到第 7 行,因为这里的 if 条件并不满足。

这里有一个小技巧,在 GDB 命令行界面如果直接按下回车键,默认是将最近一条命令重新执行一遍,因此,当使用 next 命令单步调试时,不必反复输入 n 命令,直接回车就可以了。

next 命令调试遇到函数调用直接跳过,不进入函数体内部。

2. step

step 命令(简写为 s)就是“单步步入”(step into),顾名思义,就是遇到函数调用,进入函数内部。

还有一个需要注意的地方,就是当函数的参数也是函数调用时,我们使用 step 命令会依次进入各个函数,那么顺序是什么呢?举个例子,看下面这段代码:

1  int fun1(int a, int b)
2  {
3     int c = a + b;
4     c += 2;
5     return c;
6  }
7
8  int func2(int p, int q)
9  {
10    int t = q * p;
11       return t * t;
12 }
13
14 int func3(int m, int n)
15 {
16    return m + n;
17 }
18
19 int main()
20 {
21    int c;
22    c = func3(func1(1, 2),  func2(8, 9));
23    printf("c=%d.\n", c);
24    return 0;
25 }

上述代码,程序入口是 main() 函数,在第 22 行 func3 使用 func1func2 的返回值作为自己的参数,在第 22 行输入 step 命令,会先进入哪个函数呢?这里就需要补充一个知识点了—— 函数调用方式,我们常用的函数调用方式有 _cdecl_stdcallC++ 非静态成员函数的调用方式是 _thiscall 。在这些调用方式中,函数参数的传递本质上是函数参数的入栈过程,而这三种调用方式参数的入栈顺序都是从右往左的,因此,这段代码中并没有显式标明函数的调用方式,采用默认 _cdecl 方式。

当我们在第 22 行代码处输入 step 先进入的是 func2() ,当从 func2() 返回时再次输入 step 命令会接着进入 func1() ,当从 func1 返回时,此时两个参数已经计算出来了,这时候会最终进入 func3() 。理解这一点,在遇到这样的代码时,才能根据需要进入我们想要的函数中去调试。

3. finish 和 return

实际调试时,我们在某个函数中调试一段时间后,不需要再一步步执行到函数返回处,希望直接执行完当前函数并回到上一层调用处,就可以使用 finish 命令。与 finish 命令类似的还有 return 命令,return 命令的作用是结束执行当前函数,还可以指定该函数的返回值。

这里需要注意一下二者的区别:

  • finish 命令会执行函数到正常退出该函数;
  • return 命令是立即结束执行当前函数并返回,也就是说,如果当前函数还有剩余的代码未执行完毕,也不会执行了;

我们用一个例子来验证一下:

1  #include <stdio.h>
2
3  int func()
4  {
5     int a = 9;
6     printf("a=%d.\n", a);
7
8     int b = 8;
9     printf("b=%d.\n", b);
10    return a + b;
11 }
12
13 int main()
14 {
15    int c = func();
16    printf("c=%d.\n");
17
18    return 0;
19 }

main() 函数处加一个断点,然后运行程序,在第 15 行使用 step 命令进入 func() 函数,接着单步到代码第 8 行,直接输入 return 命令,这样 func() 函数剩余的代码就不会继续执行了,因此

printf("b=%d.\n", b); 

这一行就没有输出。同时由于我们没有在 return 命令中指定这个函数的返回值,因而最终在 main() 函数中得到的变量 c 的值是一个脏数据。这也就验证了我们上面说的:return 命令在当前位置立即结束当前函数的执行,并返回到上一层调用。

(gdb) b main
Breakpoint 1 at 0x40057d: file test.c, line 15.
(gdb) r
Starting program: /root/testreturn/test

Breakpoint 1, main () at test.c:15
15          int c = func();
Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.el7_4.2.x86_64
(gdb) s
func () at test.c:5
5           int a = 9;
(gdb) n
6           printf("a=%d.\n", a);
(gdb) n
a=9.
8           int b = 8;
(gdb) return
Make func return now? (y or n) y
#0  0x0000000000400587 in main () at test.c:15
15          int c = func();
(gdb) n
16          printf("c=%d.\n");
(gdb) n
c=-134250496.
18          return 0;
(gdb)

再次用 return 命令指定一个值试一下,这样得到变量 c 的值应该就是我们指定的值。

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/testreturn/test

Breakpoint 1, main () at test.c:15
15          int c = func();
(gdb) s
func () at test.c:5
5           int a = 9;
(gdb) n
6           printf("a=%d.\n", a);
(gdb) n
a=9.
8           int b = 8;
(gdb) return 9999
Make func return now? (y or n) y
#0  0x0000000000400587 in main () at test.c:15
15          int c = func();
(gdb) n
16          printf("c=%d.\n");
(gdb) n
c=-134250496.
18          return 0;
(gdb) p c
$1 = 9999
(gdb)

仔细观察上述代码应该会发现,虽然用 return 命令修改了函数的返回值,当使用 print 命令打印 c 值的时候,c 值也确实被修改成了 9999 ,但是 GDB 本身认为的程序执行逻辑中,打印出来的 c 仍然是脏数据。这点在实际调试时需要注意一下。

我们再对比一下使用 finish 命令来结束函数执行的结果。

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/testreturn/test

Breakpoint 1, main () at test.c:15
15          int c = func();
(gdb) s
func () at test.c:5
5           int a = 9;
(gdb) n
6           printf("a=%d.\n", a);
(gdb) n
a=9.
8           int b = 8;
(gdb) finish
Run till exit from #0  func () at test.c:8
b=8.
0x0000000000400587 in main () at test.c:15
15          int c = func();
Value returned is $3 = 17
(gdb) n
16          printf("c=%d.\n");
(gdb) n
c=-134250496.
18          return 0;
(gdb)

结果和我们预期的一样,finish 正常结束函数,剩余的代码也会被正常执行。

4. until

实际调试时,还有一个 until 命令(简写为 u)可以指定程序运行到某一行停下来,想直接跳到第 1839 行,可以直接输入 u 1839,这样就能快速执行完中间的代码。当然,也可以先在第 1839 行加一个断点,然后使用 continue 命令运行到这一行,但是使用 until 命令会更简便。


(gdb) n
1815        signal(SIGHUP, SIG_IGN);
(gdb) u 1839
initServer () at server.c:1839
1839        createSharedObjects();
(gdb)

5. jump

jump 命令基本用法是:

jump <location>

location 可以是程序的行号或者函数的地址,jump 会让程序执行流跳转到指定位置执行,当然其行为也是不可控制的,例如您跳过了某个对象的初始化代码,直接执行操作该对象的代码,那么可能会导致程序崩溃或其他意外行为。jump 命令可以简写成 j,但是不可以简写成 jmp,其使用有一个注意事项,即如果 jump 跳转到的位置后续没有断点,那么 GDB 会执行完跳转处的代码会继续执行。举个例子:

1 int somefunc()
2 {
3   //代码A
4   //代码B
5   //代码C
6   //代码D
7   //代码E
8   //代码F
9 }

假设我们的断点初始位置在行号 3 处(代码 A),这个时候我们使用 jump 6,那么程序会跳过代码 B 和 C 的执行,执行完代码 D( 跳转点),程序并不会停在代码 6 处,而是继续执行后续代码,因此如果我们想查看执行跳转处的代码后的结果,需要在行号 6、7 或 8 处设置断点。

jump 命令除了跳过一些代码的执行外,还有一个妙用就是可以执行一些我们想要执行的代码,而这些代码在正常的逻辑下可能并不会执行(当然可能也因此会产生一些意外的结果,这需要读者自行斟酌使用)。举个例子,假设现在有如下代码:

1  #include <stdio.h>
2  int main()
3  {
4    int a = 0;
5    if (a != 0)
6    {
7      printf("if condition\n");
8    }
9    else
10   {
11     printf("else condition\n");
12   }
13
14   return 0;
15 }

我们在行号 4 、14 处设置一个断点,当触发行号 4 处的断点后,正常情况下程序执行流会走 else 分支,我们可以使用 jump 7 强行让程序执行 if 分支,接着 GDB 会因触发行号 14 处的断点而停下来,此时我们接着执行 jump 11,程序会将 else 分支中的代码重新执行一遍。整个操作过程如下:

[root@localhost testcore]# gdb test
Reading symbols from /root/testcore/test...done.
(gdb) b main
Breakpoint 1 at 0x400545: file main.cpp, line 4.
(gdb) b 14
Breakpoint 2 at 0x400568: file main.cpp, line 14.
(gdb) r
Starting program: /root/testcore/test

Breakpoint 1, main () at main.cpp:4
4       int a = 0;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) jump 7
Continuing at 0x400552.
if condition

Breakpoint 2, main () at main.cpp:14
14       return 0;
(gdb) jump 11
Continuing at 0x40055e.
else condition

Breakpoint 2, main () at main.cpp:14
14       return 0;
(gdb) c
Continuing.
[Inferior 1 (process 13349) exited normally]
(gdb)

gdb 的命令成百上千,下表中只列出了一些在调试过程中比较常用的命令。

启动停止源代码断点管理执行栈查看信息查看变量查看变量

  • 10
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值