首页 > 一文快速学懂常用工具——GDB(下)
头像
蒋豆芽
编辑于 2021-07-26 22:40
+ 关注

一文快速学懂常用工具——GDB(下)

img
  • 本专栏适合于软件开发的学生或人士,有一定的编程基础。
  • 本专栏针对面试题答案进行了优化,尽量做到好记、言简意赅。这才是一份面试题总结的正确打开方式。这样才方便背诵
  • 如专栏内容有错漏,欢迎在评论区指出或私聊我更改,一起学习,共同进步。
  • 相信大家都有着高尚的灵魂,请尊重我的知识产权,未经允许严禁各类机构和个人转载、传阅本专栏的内容。
img

更多内容请访问豆芽的博客https://blog.nowcoder.net/jiangwenbo


一. GDB 指令入门

经过前面的铺垫,终于到讲指令了呀,家人们,撒花撒花~ ~ ~

为了能够使用 GDB 调试我们的执行程序,需要重新编译一下,加上参数 -g

$ gcc -g douya.c -o douya

参数 -g 会为我们的调试文件加上必要的行信息。这样我们就可以查看。

我们进入 GDB 调试:

$ gdb douya

GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-w64-mingw32".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from douya...
(gdb)

我们通过查看命令 list 可以查看程序内容:

(gdb) list

1       #include <stdio.h>
2
3       int main(){
4               int i = 0;
5               int temp = 1;
6
7               for (i; i <= 10; i++){
8                       temp *= i;
9                       printf("hello douya!\n");
10              }
(gdb)

可以看出,GDB 为我们的调试文件加入了必要的行信息。

我们运行程序, 命令 rrun 就是运行程序:

(gdb) r

hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
hello douya!
[Inferior 1 (process 8012) exited normally]
(gdb)

然后我们通过命令 qquit 退出调试。

(gdb) quit

好了,我们已经成功完成了 GDB 调试一个程序,大家已经 GDB 入门了,好了,今天我们的教程就到这里了,再见!


算了,开玩笑的啦。摊手,我们继续吧。


二. GDB 指令学习

1. 设置断点

我们进入调试,然后在程序第4行设下【断点】,使用指令 bbreak

$ gdb douya
(gdb) b 4

Breakpoint 1 at 0x40159e: file douya.c, line 4.

也可以为某个函数打下断点,如:

(gdb) b main

我们 r 运行至断点

(gdb) r

Breakpoint 1, main () at douya.c:4
4               int i = 0;

可以看到,程序成功运行至第4行了,并且打印出了相关信息。

接下来我们想单步运行程序,通过指令 nnext 实现:

(gdb) n
5               int temp = 1;

现在我们不想单步了,想运行到下一个断点,那我们先在第 12 行设置一个新断点:

(gdb) b 12
Breakpoint 2 at 0x4015d1: file douya.c, line 12.

然后运行 ccontinue

(gdb) continue
Continuing.

Breakpoint 2, main () at douya.c:12
12              return 0;

好的,我们成功做到了,各位家人们!太棒了!

我们可以通过 info 命令查看断点信息:

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040159e in main at douya.c:4
        breakpoint already hit 1 time
2       breakpoint     keep y   0x00000000004015d1 in main at douya.c:12
        breakpoint already hit 1 time

我们也可以通过条件断点,比如我想在 i=3 的时候让程序停在第8行:

(gdb) b 8 if i=3
Breakpoint 4 at 0x4015ae: file douya.c, line 8.

(gdb) r
Breakpoint 1, main () at douya.c:4
4               int i = 0;
(gdb) c
Continuing.

Breakpoint 4, main () at douya.c:8
8                       temp *= i;

可以看到现在程序停在了第8行。

我们可以通过指令 cleardelete 来删除断点。

(gdb) info breakpoint
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040159e in main at douya.c:4
        breakpoint already hit 1 time
4       breakpoint     keep y   0x00000000004015ae in main at douya.c:8
        stop only if i=3
5       breakpoint     keep y   0x00000000004015d1 in main at douya.c:12

(gdb) clear 12
Deleted breakpoint 5

clear 12删除了第12行的断点。

(gdb) delete

(gdb) info b
No breakpoints or watchpoints.

delete删除了所有的断点。

我们也可以禁止使能断点,分别对应指令disableenable

(gdb) b 4   // 设置断点
Breakpoint 6 at 0x40159e: file douya.c, line 4.
(gdb) b 8   // 设置断点
Breakpoint 7 at 0x4015ae: file douya.c, line 8.
(gdb) b 12   // 设置断点
Breakpoint 8 at 0x4015d1: file douya.c, line 12.
(gdb) info b     // 查看断点
Num     Type           Disp Enb Address            What
6       breakpoint     keep y   0x000000000040159e in main at douya.c:4
7       breakpoint     keep y   0x00000000004015ae in main at douya.c:8
8       breakpoint     keep y   0x00000000004015d1 in main at douya.c:12
(gdb) disable 7   // 禁止断点
(gdb) info b
Num     Type           Disp Enb Address            What
6       breakpoint     keep y   0x000000000040159e in main at douya.c:4
7       breakpoint     keep n   0x00000000004015ae in main at douya.c:8
8       breakpoint     keep y   0x00000000004015d1 in main at douya.c:12

上面命令显示设置4、8、12行3个断点,然后禁止第7号断点,可以看到断点的属性【Enb】变为了 n

然后我们通过命令 qquit 退出调试。

(gdb) quit

2. 打印信息

我们可以通过 pprint 来打印变量信息,比如刚才我们通过条件断点已经断点到第8行了,但是是否满足 i=3 的条件呢?我们来打印一下变量 i 的值

(gdb) p i
$1 = 3

可以看出,满足条件。

display 和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。

也就是说,使用 1 次 print 命令只能查看 1 次某个变量或表达式的值,而同样使用 1 次 display 命令,每次程序暂停执行时都会自动打印出目标变量或表达式的值。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸

现在我们要监测变量 i , 通过指令 watch

(gdb) delete  // 删除所有断点
Delete all breakpoints? (y or n) [answered Y; input not from terminal]
(gdb) b 8    // 第8行设置断点
Breakpoint 15 at 0x4015ae: file douya.c, line 8.
(gdb) r     // 运行至断点

Breakpoint 15, main () at douya.c:8
8                       temp *= i;
(gdb) watch i  // 监控 i
Hardware watchpoint 16: i
(gdb) info watchpoint  // 查看监控点
Num     Type           Disp Enb Address            What
16      hw watchpoint  keep y                      i
(gdb) c         // 继续运行
Continuing.

Hardware watchpoint 16: i

Old value = 0    // 发现 i 发生了变化并打印了出来
New value = 1
0x00000000004015cb in main () at douya.c:7
7               for (i; i <= 10; i++){

通过上面内容可以看出,指令 watch 可以实现变量的监控

然后我们通过命令 qquit 退出调试。

(gdb) quit

3. step 和 util

现在我们为源文件增加代码:

#include <stdio.h>

void func(){
    int i = 0;
    printf("enter this func");
}

int main(){
    int i = 0;
    int temp = 1;

    func();

    for (i; i <= 10; i++){
        temp *= i;
        printf("hello douya!\n");
    }

    return 0;
}

编译调试:

$ gcc -g douya.c -o douya  // 编译
$ gdb douya   // 调试
(gdb) list   // 打印信息
1       #include <stdio.h>
2
3       void func(){
4               int i = 0;
5               printf("enter this func");
6       }
7
8       int main(){
9               int i = 0;
10              int temp = 1;

(gdb) b 10  // 第10行设置断点
Breakpoint 1 at 0x4015ca: file douya.c, line 10.
(gdb) r    // 运行程序至断点

Breakpoint 1, main () at douya.c:10
10              int temp = 1;
(gdb) n      // 单步运行至第12行函数
12              func();
(gdb) s     // 进入函数
func () at douya.c:4
4               int i = 0;

可以看到,我们可以通过指令 sstep 进入函数体。

我们顺便通过指令 btbacktrace 查看堆栈。

(gdb) backtrace
#0  func () at douya.c:4
#1  0x00000000004015d6 in main () at douya.c:12

然后我们通过命令 qquit 退出调试。

接下来命令 uuntil 帮助我们运行到程序指定的位置。

$ gdb douya   // 调试
(gdb) list   // 打印信息
1       #include <stdio.h>
2
3       void func(){
4               int i = 0;
5               printf("enter this func");
6       }
7
8       int main(){
9               int i = 0;
10              int temp = 1;
11
12              func();
13
14              for (i; i <= 10; i++){
15                      temp *= i;
16                      printf("hello douya!\n");
17              }
18
19              return 0;
20      }

(gdb) b 9     // 第9行断点
Breakpoint 1 at 0x4015c3: file douya.c, line 9.

(gdb) r       // 运行

Breakpoint 1, main () at douya.c:9
9               int i = 0;

(gdb) until 19      // 运行至第19行
main () at douya.c:19
19              return 0;

可以看到,程序按照我们的要求,运行到了指定的第19行


豆芽试了一下午啊,家人们, Windows + git 整不了 core 和 多线程啊。家人们,准备上 Linux 操作系统了,整活啊!始终脱离不了 Linux 的魔爪了。


三. GDB 调试 core 文件

调试 core 文件是必须掌握的能力。

1. 为什么调试 core 文件

通常情况下,core文件会包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成第一个文件,许多的程序出错的时候都会产生一个core文件,通过工具分析这个文件,我们可以定位到程序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。

2. 如何调试 core 文件

等待添加内容。


四. GDB 调试多线程

等待添加内容。


五. 常见面试题

  1. 说说 GDB ⭐⭐⭐

    GDB 全称“GNU symbolic debugger”,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。实际场景中,GDB 更常用来调试 C 和 C++ 程序

更多模拟面试

全部评论

(4) 回帖
加载中...
话题 回帖

推荐话题

相关热帖

近期精华帖

热门推荐