较详细的gdb入门教程

前言

本文选自 较详细的gdb入门教程 - Zesty_Fox

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

你是否为 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 位。

调试

启动 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 [行号]
作用:打印给定行号周围 1010 行的源代码。若不提供行号,则接续打印上次的源代码。

这里提到了一个简写的概念。什么是简写呢?简写是为了简化命令的。比如,打一个 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。

设置断点

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

因此,我们需要用 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.

运行

命令: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) 

单步执行

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

命令:next(简写为n)
格式:next(无参数)
作用:单步执行。若当前行有函数调用,则把这个函数作为一个整体执行(即不进入函数内部)。

命令:step(简写为s)
格式:step(无参数)
作用:单步执行。若当前行有函数调用,则进入该函数内部

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

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

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

举个例子好了:

(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) 

输出变量/函数值

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

命令: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)

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

查看某些信息

命令: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 是编号。编号有什么用呢?我们待会儿就要见到。

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

命令: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) 

获取帮助

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

命令: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.
...(省略若干行)...

命令一览表

命令简写作用
filefil载入可执行文件
listl打印源代码
quitq退出gdb
breakb设置断点
runr从头运行程序
continuec从当前位置继续运行程序
untilu从当前位置继续运行,直到指定行号
nextn单步执行
steps单步执行
printp打印一次值
displaydisp设置某个变量/函数总是显示
infoi打印相关类型的信息
disabledis临时禁用某些东西
deleted删除某些东西
enableen启用某些东西
helph获取帮助

图形界面?

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

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

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux嵌入式开发是指在嵌入式系统中使用Linux操作系统进行开发。Linux操作系统具有开放性、可定制性、模块化等特点,使得它成为了嵌入式系统开发中最流行的操作系统之一。 本教程将以树莓派作为开发板,介绍Linux嵌入式开发的基础知识和实践。 准备工作: 1. 树莓派开发板 2. SD卡 3. 电脑 4. USB转串口工具 5. HDMI显示器和键盘(可选) 步骤1:安装Linux系统 1. 下载Raspberry Pi官方提供的系统镜像,可以从官网上下载:https://www.raspberrypi.org/downloads/ 2. 解压镜像文件,并将其写入SD卡中。可以使用Rufus等工具进行写入。 3. 将SD卡插入树莓派,连接电源和USB转串口工具。 步骤2:连接串口 1. 在电脑上安装串口调试工具,如SecureCRT、Putty等。 2. 打开串口调试工具,设置串口号和波特率,连接树莓派。 3. 在串口调试工具中输入用户名和密码登录树莓派。 步骤3:配置网络 1. 使用ifconfig命令查看树莓派的IP地址。 2. 在电脑上打开SSH客户端,如SecureCRT、Putty等。 3. 输入树莓派的IP地址和用户名、密码,即可在电脑上远程登录树莓派。 步骤4:开发环境搭建 1. 安装开发工具链,如gcc、g++等。 2. 安装调试工具,如gdb。 3. 安装版本控制工具,如git。 4. 安装构建工具,如make。 步骤5:开发应用程序 1. 创建一个简单的C程序,如Hello World。 2. 使用gcc编译程序,生成可执行文件。 3. 在树莓派上运行可执行文件,验证程序是否正常运行。 总结: 本教程介绍了Linux嵌入式开发的基础知识和实践。通过学习本教程,您可以了解Linux操作系统在嵌入式系统中的应用,学习如何使用树莓派进行开发,掌握基本的开发工具和技巧。希望本教程能够对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值