本文主要介绍gdb的基础使用。若需了解一些技巧,请访问此篇博客:点这里

本篇教程适用于Windows,macOS及Linux,但由于Windows的自带终端很难用,所以体验可能不太好。Windows 10建议安装Windows Terminal以取得最佳体验。

1. 前言

你是否为C/C++下的调试而苦恼?你是否苦于Dev-C++调试烦人的问题(如调不了STL、结构体数组要一层一层展开)?那么,gdb很可能是你的最佳选择。

gdb是一个命令行下的、功能强大的调试器。看到命令行下,是不是有点害怕?没关系,本文最后会介绍一些图形前端,但建议先学习一些基础命令。

示例代码:(example.cpp,以下调试命令均以此代码为准)

博主太懒了,只写了个求阶乘

#include <iostream>
#include <cstdio>
using namespace std;
int f(int x){
int ans=1;
for(int i=1;i<=x;i++) ans*=i;
return ans;
} int main(){
int a;
scanf("%d",&a);
printf("%d\n",f(a));
return 0;
}

系统环境:Linux Mint 19.3 64位。

2. 调试

2.1 启动gdb,载入文件,打印源代码,退出gdb

首先,在编译选项里加上 -g ,以生成调试用的符号表。建议不要同时开 -O2 等优化选项,否则可能会有奇奇怪怪的问题。

打开终端,输入 gdb [可执行文件名] ,载入程序(注意,是可执行文件名(比如1.exe),不是你的源文件名)。比如这样:

> g++ example.cpp -o example -g
[编译,无提示]
> gdb ./example

然后,你可能会见到如下的界面:

GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://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 ./example...done.
(gdb)

这样,你就进入gdb的命令行环境里了。

其中,第一行是版本信息,倒数第二行表示正载入符号表,最后一行 (gdb) 则是gdb的提示符

请注意,若你倒数第二行有 (no debugging symbols found) 字样,请确保在编译选项里加上 -g 选项。

当然,如果你直接输入 gdb 启动,不加文件名,也可以。只是,你要使用 file 命令手动载入可执行文件。

以后出现的所有命令,都是在gdb的环境,而非系统shell的环境执行的。

命令:file(简写fil)

格式:file 可执行文件名

作用:载入当前目录下的对应名称的可执行文件。

例子:

(gdb) file example
Load new symbol table from "example"? (y or n) y
Reading symbols from example...done.

命令:list(简写为l)

格式:list [行号]

作用:打印给定行号周围10行的源代码。若不提供行号,则接续打印上次的源代码。

这里提到了一个简写的概念。什么是简写呢?简写是为了简化命令的。比如,打一个 list 还是有些麻烦的。这时,我们可以输入它的简写 l 。你可以认为一个命令与它的简写是完全等价的。以后若提到简写,不再解释。

例子:

(gdb) l 7
2 #include <cstdio>
3 using namespace std;
4 int f(int x){
5 int ans=1;
6 for(int i=1;i<=x;i++) ans*=i;
7 return ans;
8 }
9
10 int main(){
11 int a;
(gdb) l
12 scanf("%d",&a);
13 printf("%d\n",f(a));
14 return 0;
15 }
(gdb)

最后,用 quit 命令退出gdb。

命令:quit(简写为q)

格式:quit(无参数)

作用:退出gdb。

2.2 设置断点

要调试程序,我们必须让它在某个地方停下来。否则,让它一直执行下去,那和普通的执行程序有什么区别呢?

因此,我们需要用 break 来设置断点。此后,程序将会在设定的断点处停下来。

命令:break(简写为b)

格式:break 函数名|行号

作用:在给定函数名或行数处设置断点。

例子:

(gdb) break main //main函数处设置断点
Breakpoint 1 at 0x874: file example.cpp, line 10.
(gdb) break 11 //在第11行处设置断点
Breakpoint 2 at 0x883: file example.cpp, line 11.

当然可以用它的简写:

(gdb) b main
Breakpoint 1 at 0x874: file example.cpp, line 10.
(gdb) b 11
Breakpoint 2 at 0x883: file example.cpp, line 11.

2.3 运行

命令:run(简写为r)

格式:run(无参数)

作用:从头运行程序。

命令:continue(简写为c)

格式:continue(无参数)

作用:从当前位置继续运行程序,直到遇到下一个断点或程序运行完毕。

命令:until(简写为u)

格式:until 行号(无参数)

作用:从当前位置继续运行程序,直到指定行号处才停下来。

当然,有时候,你可能发现运行上述命令后gdb会停住。这有两种情况:

  1. 你的程序用了标准输入,gdb在等待输入。
  2. 数据规模太大或程序效率太低,以至于运行到断点的时间较长。

例子:

(gdb) r
Starting program: /home/acceptedzhs/example Breakpoint 1, main () at example.cpp:10
10 int main(){
(gdb) c
Continuing. Breakpoint 2, main () at example.cpp:12
12 scanf("%d",&a);
(gdb)

2.4 单步执行

很多时候,我们要一步一步地执行程序。无疑,反复地 breakcontinue 十分麻烦。gdb有两个命令 nextstep,可实现单步执行。

命令:next(简写为n)

格式:next(无参数)

作用:单步执行。若当前行有函数调用,则把这个函数作为一个整体执行(即不进入函数内部)。

命令:step(简写为s)

格式:step(无参数)

作用:单步执行。若当前行有函数调用,则进入该函数内部

但是,又有人想偷懒了。反复敲 ns 依然很麻烦。怎么办呢?

gdb有个特性:若什么都不输,直接按回车,则会执行上一次执行的命令

所以,只要开始敲个 ns,然后一直敲回车就行了。

举个例子好了:

(gdb) b 13
Breakpoint 1 at 0x89b: file example.cpp, line 13.
(gdb) r
Starting program: /home/acceptedzhs/example
10 Breakpoint 1, main () at example.cpp:13
13 printf("%d\n",f(a));
(gdb) n
3628800
14 return 0;
(gdb) [回车] //看到没,执行了上次的命令,即next
15 }
(gdb)
Starting program: /home/acceptedzhs/example
10 Breakpoint 1, main () at example.cpp:13
13 printf("%d\n",f(a));
(gdb) s
f (x=10) at example.cpp:5 //step命令,进入了f函数内部
5 int ans=1;
(gdb)

2.5 输出变量/函数值

有时,我们想要打印某些变量或函数的值,看它是否符合期望。这又怎么办呢?

命令:print(简写为p)

格式:print 变量名

作用:打印一次变量名/函数调用对应的值。

命令:display(简写为disp)

格式:display 变量名

作用:设置在每一次停下来时(如到断点时,单步执行等)都打印该变量名/函数调用对应的值。

例子:

(gdb) b 13
Breakpoint 1 at 0x89b: file example.cpp, line 13.
(gdb) r
Starting program: /home/acceptedzhs/example
9 Breakpoint 1, main () at example.cpp:13
13 printf("%d\n",f(a));
(gdb) p a
$1 = 9
(gdb) n
362880 //输出9!
14 return 0; //这只会显示一次,下一步就不会再打印该变量值了
(gdb) p f(2) //当然,调用函数也可以
$2 = 2
(gdb)
(gdb) b f
Breakpoint 1 at 0x841: file example.cpp, line 5.
(gdb) r
Starting program: /home/acceptedzhs/example
9 Breakpoint 1, f (x=9) at example.cpp:5
5 int ans=1;
(gdb) disp ans
1: ans = 0
(gdb) n
6 for(int i=1;i<=x;i++) ans*=i;
1: ans = 1
(gdb)
7 return ans;
1: ans = 362880 //每次停下来时,该变量都会显示
(gdb)
8 }
1: ans = 362880
(gdb)

有时,你可能会见到 <optimized out> 的提示。此时,请检查编译时是否开了优化(如 -O2 )。

2.6 查看某些信息

命令:info(简写为i)

格式:info 类型

作用:打印对应类型的信息。

其中,类型可以是breakpoints(断点,简写b)、locals(局部变量,简写lo)、display(被设为总是显示的变量,简写disp)等。具体可以通过 help info 查看。

比如:

(gdb) b 13
Breakpoint 1 at 0x89b: file example.cpp, line 13.
(gdb) r
Starting program: /home/acceptedzhs/example
10 //程序的标准输入 Breakpoint 1, main () at example.cpp:13
13 printf("%d\n",f(a));
(gdb) i lo
a = 10
(gdb) i b
Num Type Disp Enb Address What
1 breakpoint keep y 0x000055555555489b in main() at example.cpp:13
breakpoint already hit 1 time
(gdb) disp a
1: a = 10
(gdb) i display
Auto-display expressions now in effect:
Num Enb Expression
1: y a
(gdb)

我们发现输出了很多信息。其中Num是编号。编号有什么用呢?我们待会儿就要见到。

2.7 删除/禁用/启用某些东西

命令:disable(简写为dis)

格式:disable 类型 [编号]

作用:临时禁用某些类型的对应编号的东西,待会儿讲。

命令:delete(简写为d)

格式:delete 类型 [编号]

作用:删除某些类型的对应编号的东西。

命令:enable(简写为en)

格式:enable 类型 [编号]

作用:启用某些类型的对应编号的东西。

其中,类型就是讲述 info 命令时中的类型,编号就是 info 命令输出的一堆东西中的Num那一栏。

注意:delete可能用不了类型的简写。

举个例子:

(gdb) b 12
Breakpoint 1 at 0x883: file example.cpp, line 12.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000555555554883 in main() at example.cpp:12
(gdb) dis b 1 //禁用1号断点
(gdb) r
Starting program: /home/acceptedzhs/example
10
3628800 //不经过该断点了
[Inferior 1 (process 2695) exited normally]
(gdb) en b 1 //启用该断点
(gdb) r
Starting program: /home/acceptedzhs/example Breakpoint 1, main () at example.cpp:12
12 scanf("%d",&a); //又经过该断点了
(gdb) d breakpoints 1 //删除
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/acceptedzhs/example
10
3628800 //又不经过断点了
[Inferior 1 (process 3516) exited normally]
(gdb)

2.8 获取帮助

有时,我们可能忘记某个命令的用法。这该怎么办呢?

命令:help(简写为h)

格式:help 待查询的命令(待查询的命令可以用简写)

作用:显示待查询的命令的帮助。

例子:

(gdb) h b
Set breakpoint at specified location.
break [PROBE_MODIFIER] [LOCATION] [thread THREADNUM] [if CONDITION]
PROBE_MODIFIER shall be present if the command is to be placed in a
probe point. Accepted values are `-probe' (for a generic, automatically
guessed probe type), `-probe-stap' (for a SystemTap probe) or
`-probe-dtrace' (for a DTrace probe).
LOCATION may be a linespec, address, or explicit location as described
below. With no LOCATION, uses current execution address of the selected
stack frame. This is useful for breaking on return to a stack frame.
...(省略若干行)...

3. 命令一览表

命令 简写 作用
file fil 载入可执行文件
list l 打印源代码
quit q 退出gdb
break b 设置断点
run r 从头运行程序
continue c 从当前位置继续运行程序
until u 从当前位置继续运行,直到指定行号
next n 单步执行
step s 单步执行
print p 打印一次值
display disp 设置某个变量/函数总是显示
info i 打印相关类型的信息
disable dis 临时禁用某些东西
delete d 删除某些东西
enable en 启用某些东西
help h 获取帮助

4. 图形界面?

gdb作为一个命令行调试器,对于某些人来说可能望而生畏。

所以,很多人为其开发了图形前端,以方便大家使用。

这里,我推荐nemiver、ddd、gdbgui。(貌似不支持win)

如果你是个Vim爱好者,vim-vebugger也不错。

对上面不满意?可以试试gdb自带的伪图形界面,只要启动gdb时加上 -tui 选项即可。

5. 结语

以上便是全部内容了。

蒟蒻写博客不易,恳请大佬点个赞!

较详细的gdb入门教程的更多相关文章

  1. 超强、超详细Redis数据库入门教程

    这篇文章主要介绍了超强.超详细Redis入门教程,本文详细介绍了Redis数据库各个方面的知识,需要的朋友可以参考下 [本教程目录] 1.redis是什么2.redis的作者何许人也3.谁在使用red ...

  2. 超强、超详细Redis数据库入门教程(转载)

    这篇文章主要介绍了超强.超详细Redis入门教程,本文详细介绍了Redis数据库各个方面的知识,需要的朋友可以参考下   [本教程目录] 1.redis是什么 2.redis的作者何许人也 3.谁在使 ...

  3. 超详细Redis数据库入门教程

    [本教程目录] 1.redis是什么2.redis的作者何许人也3.谁在使用redis4.学会安装redis5.学会启动redis6.使用redis客户端7.redis数据结构 – 简介8.redis ...

  4. 有shi以来最详细的正则表达式入门教程

    本篇文章文字内容较多,但是要学习正则就必须耐心读下去,正则表达式是正则表达式其实并没有想像中的那么困难,但是想要熟练的掌握它,还是需要下功夫勤加练习的.这里讲一些正则表达式的语法和学习方法,大家还要多 ...

  5. 一份详细的asyncio入门教程

    asyncio模块提供了使用协程构建并发应用的工具.它使用一种单线程单进程的的方式实现并发,应用的各个部分彼此合作, 可以显示的切换任务,一般会在程序阻塞I/O操作的时候发生上下文切换如等待读写文件, ...

  6. gulp详细入门教程

    本文链接:http://www.ydcss.com/archives/18 gulp详细入门教程 简介: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器:她不仅能对网站资源进行优 ...

  7. ant使用指南详细入门教程

    这篇文章主要介绍了ant使用指南详细入门教程,本文详细的讲解了安装.验证安装.使用方法.使用实例.ant命令等内容,需要的朋友可以参考下 一.概述 ant 是一个将软件编译.测试.部署等步骤联系在一起 ...

  8. gulp详细入门教程(转载)

    本文转载自: gulp详细入门教程

  9. <转载>ant使用指南详细入门教程 http://www.jb51.net/article/67041.htm

    这篇文章主要介绍了ant使用指南详细入门教程,本文详细的讲解了安装.验证安装.使用方法.使用实例.ant命令等内容,需要的朋友可以参考下 一.概述 ant 是一个将软件编译.测试.部署等步骤联系在一起 ...

随机推荐

  1. makefile从入门到入门

    makefile文件是用来帮助编译和管理C++项目代码的,需要配合make命令使用.makefile里也可以执行shell操作,具备一部分.sh脚本的功能. makefile格式 makefile内容 ...

  2. 015 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 09 Unicode编码

    015 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 09 Unicode编码 本文知识点:Unicode编码以及字符如何表示? ASCII码是美国提出的标准信息 ...

  3. devops-持续集成管理之SonarQube

    1. devops-持续集成管理之SonarQube  1) 代码质量七宗罪 编码规范:是否遵守了编码规范,遵循了最佳实践. 潜在的BUG:可能在最坏情况下出现问题的代码,以及存在安全漏洞的代码. 文 ...

  4. 多测师讲解python_os模块_高级讲师肖sir

    #os.path.isfile()#:判断当前是否为文件,返回布尔值是文件则True否者Falsea_path='F:\cms搭建.rar' #lesson包b_path=r'D:\bao\kk '# ...

  5. day32 Pyhton 异常处理

    一.内容回顾 反射的另外两个内置函数 setattr delattr a.b=c 与 setattr(a,'b',c)相对 del a.b 与 delattr(a,'b') 两个内置函数 A,B(A) ...

  6. react中 受控组件和 非受控组件 浅析

    一 受控组件 顾名思义,受控 也就是能够被控制,简而言之也就是 该组件ui的显示或者内部state逻辑的变化依赖外部的 props的传入. 二 非受控组件 顾名思义,非受控,也就是内部的视图变化,st ...

  7. 安装ipython

    安装ipython,首先系统上已安装python 在这里,我们已安装python3 在windows下: pip intsall ipython 在linux下: sudo apt install p ...

  8. Elasticsearch修改字段类型 (_reindex)

    1.设置索引t2为想要的数据类型 2.将t1 reindex到t2 3.数据reindex完成删除t1 4.设置索引t1为想要的数据类型 5.将t2 reindex到t1 如果 _reindex 超时 ...

  9. Tensorflow学习笔记No.7

    tf.data与自定义训练综合实例 使用tf.data自定义猫狗数据集,并使用自定义训练实现猫狗数据集的分类. 1.使用tf.data创建自定义数据集 我们使用kaggle上的猫狗数据以及tf.dat ...

  10. spring boot:用rocketmq消息订阅实现删除购物车商品功能(spring boot 2.3.3)

    一,为什么要使用消息队列实现删除购物车商品功能? 消息队列主要用来处理不需要立刻返回结果的业务, 常见的例子: 用户在下单后,要清除原购物车中的商品, 这个处理过程不需要马上实现也不需要返回结果给用户 ...