GDB调试命令详解

2023-11-06

GDB是什么

调试程序

程序中出现的语法错误可以借助编译器解决;但逻辑错误则只能靠自己解决。实际场景中解决逻辑错误最高效的方法,就是借助调试工具对程序进行调试。

所谓调试(Debug),就是让代码一步一步慢慢执行,跟踪程序的运行过程。比如,可以让程序停在某个地方,查看当前所有变量的值,或者内存中的数据;也可以让程序一次只执行一条或者几条语句,看看程序到底执行了哪些代码。

也就是说,通过调试程序,我们可以监控程序执行的每一个细节,包括变量的值、函数的调用过程、内存中数据、线程的调度等,从而发现隐藏的错误或者低效的代码。

GDB的作用

GDB 全称“GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada等。实际场景中,GDB 更常用来调试 CC++程序。

总的来说,借助 GDB调试器可以实现以下几个功能:

程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量;

可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果等),即支持断点调试;

程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误。

GDB安装

1、通过包管理器进行安装

$ yum -y install gdb

2、源码安装GDB

gdb源码包上面下载相应的版本进行安装即可。

3、查看GDB版本

输入gdb -v,即可查看当前安装的gdb的版本。

$ gdb -v
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-94.el7
Copyright (C) 2013 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-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.

如果显示出gdb的版本,也说明了安装成功。

GDB的用法

常用调试命令

GDB 的主要功能就是监控程序的执行流程。这也就意味着,只有当源程序文件编译为可执行文件并执行时,并且该文件中必须包含必要的调试信息(比如各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等),GDB才会派上用场。

所以在编译时需要使用 gcc/g++ -g 选项编译源文件,才可生成满足 GDB 要求的可执行文件

调试命令 (缩写) 作用
(gdb) break (b) 在源代码指定的某一行设置断点,其中xxx用于指定具体打断点位置
(gdb) run (r) 执行被调试的程序,其会自动在第一个断点处暂停执行。
(gdb) continue (c) 当程序在某一断点处停止后,用该指令可以继续执行,直至遇到断点或者程序结束。
(gdb) next (n) 令程序一行代码一行代码的执行。
(gdb) step(s) 如果有调用函数,进入调用的函数内部;否则,和 next 命令的功能一样。
(gdb) until (u)
(gdb) until (u) location
当你厌倦了在一个循环体内单步跟踪时,单纯使用 until 命令,可以运行程序直到退出循环体。
until n 命令中,n 为某一行代码的行号,该命令会使程序运行至第 n 行代码处停止。
(gdb) print (p) 打印指定变量的值,其中 xxx 指的就是某一变量名。
(gdb) list (l) 显示源程序代码的内容,包括各行代码所在的行号。
(gdb) finish(fi) 结束当前正在执行的函数,并在跳出函数后暂停程序的执行。
(gdb) return(return) 结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。
(gdb) jump(j) 使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。
(gdb) quit (q) 终止调试。

示例:

$ ls
main.cpp
$ g++ -g -o test main.cpp
$ ls
main.cpp  test
$ gdb test         <-- 启动gdb进行调试
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-94.el7
Copyright (C) 2013 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-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/zhuyong/project/linux/blog/gdb/test...done.
(gdb)

gdb启动时会默认打印一堆免责条款,通过添加 --silent(或者 -q--quiet)选项,可将这部分信息屏蔽掉。

下面先用个例子运行下上述调试命令

$ gdb test -q      <-- 启动gdb进行调试
Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done.
(gdb) l            <-- 显示带行号的源代码
1	#include <iostream>
2	using namespace std;
3
4	int main() {
5	    int sum = 0;
6	    int n = 1;
7	    while (n <= 100) {
8	        sum += n;
9	        n++;
10	    }
(gdb)              <-- 默认情况下,l 选项只显示 10 行源代码,如果查看后续代码,按 Enter 回车键即可
11	    cout << "sum = " << sum << endl;
12
13	    return 0;
14	}
15
(gdb) b 7          <-- 在第7行源代码处打断点
Breakpoint 1 at 0x4008d3: file main.cpp, line 7.
(gdb) r            <-- 运行程序,遇到断点停止
Starting program: /home/zhudi/project/linux/blog/gdb/test

Breakpoint 1, main () at main.cpp:7
7	    while (n <= 100) {
Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.x86_64 libgcc-4.8.5-39.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64
(gdb) print n      <-- 查看代码中变量 n 的值
$1 = 1             <-- 当前 n 的值为 1,$1 表示该变量 表示该变量所在存储区的名称
(gdb) b 13
Breakpoint 2 at 0x40090e: file main.cpp, line 13.
(gdb) n            <-- 单步执行程序
8	        sum += n;
(gdb) n            <-- 单步执行程序
9	        n++;
(gdb) c            <-- 继续执行程序
Continuing.
sum = 5050

Breakpoint 2, main () at main.cpp:13
13	    return 0;
(gdb) print sum     <-- 查看 sum 的值    
$2 = 5050           <-- 当前 sum 的值为 5050
(gdb) q             <-- 退出调试
A debugging session is active.

	Inferior 1 [process 8449] will be killed.

Quit anyway? (y or n) y     <-- 确认是否退出调试,y 为退出,n 为不退出

接下来分别介绍下各个命令的用法

GDB 断点调试

启动程序

根据不同场景的需要,GDB 调试器提供了多种方式来启动目标程序,其中最常用的就是run 指令,其次为 start 指令。也就是说,runstart 指令都可以用来在 GDB 调试器中启动程序,它们之间的区别是:

  • 默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run指令会执行程序至第一个断点处;

  • start 指令会执行程序至main()主函数的起始位置,即在main()函数的第一行语句处停止执行(该行代码尚未执行)。

break命令

break 命令(可以用b 代替)常用的语法格式有以下 2 种。

1(gdb) break location      // b location
2(gdb) break ... if cond   // b .. if cond
  1. 第一种格式中,location 用于指定打断点的具体位置,其表示方式有多种,如表 1 所示。
location的值 含义
linenum linenum 是一个整数,表示要打断点处代码的行号。要知道,程序中各行代码都有对应的行号,可通过执行 l(小写的 L)命令看到。
filename:linenum filename 表示源程序文件名;linenum 为整数,表示具体行数。整体的意思是在指令文件 filename 中的第 linenum 行打断点。
+ offset
- offset
offset 为整数(假设值为 2),+offset 表示以当前程序暂停位置(例如第 4 行)为准,向后数 offset 行处(第 6 行)打断点;-offset 表示以当前程序暂停位置为准,向前数 offset 行处(第 2 行)打断点
function function 表示程序中包含的函数的函数名,即 break 命令会在该函数内部的开头位置打断点,程序会执行到该函数第一行代码处暂停。
filename:function filename 表示远程文件名;function 表示程序中函数的函数名。整体的意思是在指定文件 filename 中 function 函数的开头位置打断点。
  1. 第二种格式中,… 可以是表 1 中所有参数的值,用于指定打断点的具体位置;cond 为某个表达式。整体的含义为:每次程序执行到 … 位置时都计算 cond 的值,如果为 True,则程序在该位置暂停;反之,程序继续执行。另外也可以用condition 为断点设置命中条件。
tbreak和rbreak命令

tbreak 命令可以看到是 break 命令的另一个版本,tbreakbreak 命令的用法和功能都非常相似,唯一的不同在于,使用 tbreak 命令打的断点仅会作用 1 次,即使程序暂停之后,该断点就会自动消失。

breaktbreak 命令不同,rbreak命令的作用对象是 CC++ 程序中的函数,它会在指定函数的开头位置打断点。语法格式

(gdb) tbreak regex

其中 regex 为一个正则表达式,程序中函数的函数名只要满足 regex条件,tbreak 命令就会其内部的开头位置打断点。值得一提的是,tbreak 命令打的断点和 break 命令打断点的效果是一样的,会一直存在,不会自动消失。

示例
$ gdb test -q
Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done.
(gdb) l
4	
5	void cb_one() {
6	    cout << "cb_one" << endl;
7	}
8	void cb_second() {
9	    cout << "cb_second" << endl;
10	}
11	
12	int main() {
13	    int sum = 0;
(gdb) 
14	    int n = 1;
15	    while (sum < 100) {
16	        sum += n;
17	        n++;
18	        cout << "sum = " << sum << endl;
19	        sleep(1);
20	        cb_one();
21	        cb_second();
22	    }
23	    cout << "sum = " << sum << endl;
(gdb) b 16                  <-- 在第16行打断点
Breakpoint 1 at 0x400959: file main.cpp, line 16.
(gdb) r                     <-- 启动程序
Starting program: /home/zhudi/project/linux/blog/gdb/test 

Breakpoint 1, main () at main.cpp:16
16	        sum += n;       <--16行暂停
(gdb) b +2                  <--在当前位置之后的2行处设置断点
Breakpoint 2 at 0x400963: file main.cpp, line 18.
(gdb) c
Continuing.

Breakpoint 2, main () at main.cpp:18
18	        cout << "sum = " << sum << endl;
(gdb) b 19 if sum>2         <-- 条件断点
Breakpoint 3 at 0x40098c: file main.cpp, line 19.
(gdb) c
Continuing.
sum = 1
cb_one
cb_second

Breakpoint 1, main () at main.cpp:16
16	        sum += n;
(gdb) c
Continuing.

Breakpoint 2, main () at main.cpp:18
18	        cout << "sum = " << sum << endl;
(gdb) c
Continuing.
sum = 3

Breakpoint 3, main () at main.cpp:19
19	        sleep(1);
(gdb) p sum
$1 = 3
(gdb) rbreak cb_*            <-- 匹配所有以cb_开头的函数
Breakpoint 4 at 0x400901: file main.cpp, line 6.
void cb_one();
Breakpoint 5 at 0x400923: file main.cpp, line 9.
void cb_second();
Breakpoint 6 at 0x400a17: file main.cpp, line 26.
(gdb) c
Continuing.

Breakpoint 4, cb_one () at main.cpp:6
6	    cout << "cb_one" << endl;     <-- 在cb_one函数的第一行暂停
(gdb) c
Continuing.
cb_one

Breakpoint 5, cb_second () at main.cpp:9
9	    cout << "cb_second" << endl;  <-- 在cb_second函数的第一行暂停
删除或禁用断点

删除断点

如果之前建立的断点不再需要或者暂时不需要,该如何删除或者禁用呢?常用的方式有 2 种:

  1. 使用 quit 命令退出调试,然后重新对目标程序启动调试,此方法会将消除上一次调试操作中建立的所有断点;
  2. 使用专门删除或禁用断点的命令,既可以删除某一个断点,也可以删除全部断点。

无论是普通断点、观察断点还是捕捉断点,都可以使用 clear 或者 delete 命令进行删除。

clear 命令可以删除指定位置处的所有断点,常用的语法格式如下所示:

(gdb) clear location

参数location 通常为某一行代码的行号或者某个具体的函数名。当 location 参数为某个函数的函数名时,表示删除位于该函数入口处的所有断点。

delete 命令(可以缩写为 d)通常用来删除所有断点,也可以删除指定编号的各类型断点,语法格式如下:

delete [breakpoints] [num]

其中,breakpoints 参数可有可无,num 参数为指定断点的编号,其可以是delete 删除某一个断点,而非全部。

如果不指定 num参数,则 delete 命令会删除当前程序中存在的所有断点。

禁用断点

禁用断点可以使用 disable 命令,语法格式如下:

disable [breakpoints] [num...]

breakpoints 参数可有可无;num...表示可以有多个参数,每个参数都为要禁用断点的编号。如果指定 num...disable 命令会禁用指定编号的断点;反之若不设定 num...,则 disable 会禁用当前程序中所有的断点。

对于禁用的断点,可以使用enable 命令激活,该命令的语法格式有多种,分别对应有不同的功能:

enable [breakpoints] [num...]                        激活用 num... 参数指定的多个断点,如果不设定 num...,表示激活所有禁用的断点
enable [breakpoints] once num…                 临时激活以 num... 为编号的多个断点,但断点只能使用 1 次,之后会自动回到禁用状态
enable [breakpoints] count num...      临时激活以 num... 为编号的多个断点,断点可以使用 count 次,之后进入禁用状态
enable [breakpoints] delete num…               激活 num.. 为编号的多个断点,但断点只能使用 1 次,之后会被永久删除。

其中,breakpoints 参数可有可无;num...表示可以提供多个断点的编号,enable命令可以同时激活多个断点。

观察断点监控变量值的变化

观察断点

要知道,GDB 调试器支持在程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点。其中 break 命令打的就是普通断点,而 watch 命令打的为观察断点。

使用 GDB 调试程序的过程中,借助观察断点可以监控程序中某个变量或者表达式的值,只要发生改变,程序就会停止执行。相比普通断点,观察断点不需要我们预测变量(表达式)值发生改变的具体位置

(gdb) watch cond

watch 命令功能相似的,还有 rwatchawatch 命令。其中:

  • rwatch 命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
  • awatch 命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。
示例
$ gdb test -q
Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done.
(gdb) start
Temporary breakpoint 1 at 0x400949: file main.cpp, line 13.
Starting program: /home/zhuyong/project/linux/blog/gdb/test 

Temporary breakpoint 1, main () at main.cpp:13
13	    int sum = 0;
(gdb) l
8	void cb_second() {
9	    cout << "cb_second" << endl;
10	}
11	
12	int main() {
13	    int sum = 0;
14	    int n = 1;
15	    while (sum < 100) {
16	        sum += n;
17	        n++;
(gdb) watch sum        <-- 设置观察断点
Hardware watchpoint 2: sum
(gdb) c
Continuing.
Hardware watchpoint 2: sum

Old value = 0
New value = 1
main () at main.cpp:17
17	        n++;       <-- sum值发生变化,程序暂停

查看变量或表达式的值

对于在调试期间查看某个变量或表达式的值,GDB 调试器提供有 2 种方法,即使用 print 命令或者 display命令。

print 命令

它的功能就是在 GDB 调试程序的过程中,输出或者修改指定变量或者表达式的值。

print 命令可以缩写为 p,最常用的语法格式如下所示:

(gdb) print num
(gdb) p num

其中,参数 num 用来代指要查看或者修改的目标变量或者表达式。

当程序中包含多个作用域不同但名称相同的变量或表达式时,可以借助::运算符明确指定要查看的目标变量或表达式。::运算符的语法格式如下:

(gdb) print file::variable
(gdb) print function::variable

其中 file用于指定具体的文件名,funciton 用于指定具体所在函数的函数名,variable表示要查看的目标变量或表达式。

另外,print也可以打印出类或者结构体变量的值。

#### display 命令

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

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

display 命令没有缩写形式,常用的语法格式如下 2 种:

(gdb) display expr
(gdb) display/fmt expr

注意,display 命令和 /fmt 之间不要留有空格。以 /x 为例,应写为 (gdb)display/x expr。

GDB单步调试

根据实际场景的需要,GDB 调试器共提供了 3 种可实现单步调试程序的方法,即使用 nextstepuntil 命令。换句话说,这 3 个命令都可以控制 GDB调试器每次仅执行 1 行代码,但除此之外,它们各自还有不同的功能。

next命令

next 是最常用来进行单步调试的命令,其最大的特点是当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next 指令都会一步执行完。也就是说,对于调用的函数来说,next 命令只会将其视作一行代码。

next 命令可以缩写为n 命令,使用方法也很简单,语法格式如下:

(gdb) next count
step命令

通常情况下,step 命令和next命令的功能相同,都是单步执行程序。不同之处在于,当step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行。

step 命令可以缩写为 s命令,用法和 next 命令相同,语法格式如下:

(gdb) step count
until命令

until 命令可以简写为 u 命令,有 2 种语法格式,如下所示:

1(gdb) until
2(gdb) until location

其中,参数 location为某一行代码的行号。

不带参数的 until命令,可以使 GDB调试器快速运行完当前的循环体,并运行至循环体外停止。注意,until 命令并非任何情况下都会发挥这个作用,只有当执行至循环体尾部(最后一行代码)时,until命令才会发生此作用;反之,until命令和 next 命令的功能一样,只是单步执行程序。

return命令

实际调试时,在某个函数中调试一段时间后,可能不需要再一步步执行到函数返回处,希望直接执行完当前函数,这时可以使用 finish命令。与finish 命令类似的还有 return 命令,它们都可以结束当前执行的函数。

finish命令

finish 命令和 return命令的区别是,finish命令会执行函数到正常退出;而 return 命令是立即结束执行当前函数并返回,也就是说,如果当前函数还有剩余的代码未执行完毕,也不会执行了。除此之外,return命令还有一个功能,即可以指定该函数的返回值。

jump命令

jump 命令的功能是直接跳到指定行继续执行程序,其语法格式为:

(gdb) jump location

其中,location 通常为某一行代码的行号。

也就是说,jump 命令可以略过某些代码,直接跳到 location处的代码继续执行程序。这意味着,如果你跳过了某个变量(对象)的初始化代码,直接执行操作该变量(对象)的代码,很可能会导致程序崩溃或出现其它 Bug。另外,如果 jump跳转到的位置后续没有断点,那么 GDB会直接执行自跳转处开始的后续代码。

GDB search 命令

调试文件时,某些时候可能会去找寻找某一行或者是某一部分的代码。可以使用 list 显示全部的源码,然后进行查看。当源文件的代码量较少时,我们可以使用这种方式搜索。如果源文件的代码量很大,使用这种方式寻找效率会很低。所以 GDB中提供了相关的源代码搜索的的search命令。

search 命令的语法格式为:

search <regexp>
reverse-search <regexp>

第一项命令格式表示从当前行的开始向前搜索,后一项表示从当前行开始向后搜索。其中regexp 就是正则表达式,正则表达式描述了一种字符串匹配的模式,可以用来检查一个串中是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串。很多的编程语言都支持使用正则表达式。

查看堆栈信息

backtrace 命令

backtrace 命令用于打印当前调试环境中所有栈帧的信息,常用的语法格式如下:

(gdb) backtrace [-full] [n]

其中,用 [ ] 括起来的参数为可选项,它们的含义分别为:

  • n:一个整数值,当为正整数时,表示打印最里层的 n 个栈帧的信息;n为负整数时,那么表示打印最外层n个栈帧的信息;

  • -full:打印栈帧信息的同时,打印出局部变量的值。

注意,当调试多线程程序时,该命令仅用于打印当前线程中所有栈帧的信息。如果想要打印所有线程的栈帧信息,应执行thread apply all backtrace命令。

frame 命令

frame命令的常用形式有 2 个:

  1. 根据栈帧编号或者栈帧地址,选定要查看的栈帧,语法格式如下:
(gdb) frame spec

该命令可以将 spec 参数指定的栈帧选定为当前栈帧。spec 参数的值,常用的指定方法有 3 种:

  1. 通过栈帧的编号指定。0 为当前被调用函数对应的栈帧号,最大编号的栈帧对应的函数通常就是 main() 主函数;
  2. 借助栈帧的地址指定。栈帧地址可以通过 info frame 命令(后续会讲)打印出的信息中看到;
  3. 通过函数的函数名指定。注意,如果是类似递归函数,其对应多个栈帧的话,通过此方法指定的是编号最小的那个栈帧。

除此之外,对于选定一个栈帧作为当前栈帧,GDB 调试器还提供有updown两个命令。其中,up命令的语法格式为:

(gdb) up n

其中 n为整数,默认值为 1。该命令表示在当前栈帧编号(假设为 m)的基础上,选定 m+n为编号的栈帧作为新的当前栈帧。

相对地,down 命令的语法格式为:

(gdb) down n

其中n为整数,默认值为 1。该命令表示在当前栈帧编号(假设为 m)的基础上,选定m-n 为编号的栈帧作为新的当前栈帧。

  1. 借助如下命令,我们可以查看当前栈帧中存储的信息:
(gdb) info frame

该命令会依次打印出当前栈帧的如下信息:

  • 当前栈帧的编号,以及栈帧的地址;
  • 当前栈帧对应函数的存储地址,以及该函数被调用时的代码存储的地址
  • 当前函数的调用者,对应的栈帧的地址;
  • 编写此栈帧所用的编程语言;
  • 函数参数的存储地址以及值;
  • 函数中局部变量的存储地址;
  • 栈帧中存储的寄存器变量,例如指令寄存器(64位环境中用 rip 表示,32为环境中用eip 表示)、堆栈基指针寄存器(64位环境用 rbp表示,32位环境用 ebp表示)等。

除此之外,还可以使用info args命令查看当前函数各个参数的值;使用info locals命令查看当前函数中各局部变量的值。

调试正在执行的程序

如果调试正在执行中的程序,首先需要找到正在运行程序的进程号PID,之后可以用下面三个命令进行调试,进入正常的调试流程。

1) gdb attach PID
2) gdb 文件名 PID
3) gdb -p PID
示例:
# ps -aux | grep test           <-- 找到正在运行程序的进程号PID
root     17997  0.0  0.0  12540  1064 pts/0    S+   10:19   0:00 ./test
root     18088  0.0  0.0 112812   972 pts/1    S+   10:20   0:00 grep --color=auto test
    
# gdb attach 17997 -q           <-- 用gdb进行调试
attach: No such file or directory.
Attaching to process 17997
Reading symbols from /root/project/blog/gdb/test...done.
Reading symbols from /lib64/libstdc++.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libstdc++.so.6
Reading symbols from /lib64/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libm.so.6
Reading symbols from /lib64/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib64/libgcc_s.so.1
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
0x00007f61ea02b840 in __nanosleep_nocancel () from /lib64/libc.so.6

注意,当 GDB 调试器成功连接到指定进程上时,程序执行会暂停。如上所示,程序暂停至第 6 行代码num++的位置,此时可以通过断点调试、逐步运行等方式监控程序的执行过程。例如:

(gdb) l                        
warning: Source file is more recent than executable.
1	#include <iostream>
2	#include <unistd.h>
3	using namespace std;
4	
5	int main() {
6	    int sum = 0;
7	    int n = 1;
8	    while (true) {
9	        sum += n;
10	        n++;
(gdb) 
11	        cout << "sum = " << sum << endl;
12	        sleep(1);
13	    }
14	    cout << "sum = " << sum << endl;
15	
16	    return 0;
17	}
18
    
    
(gdb) b 10
Breakpoint 1 at 0x400869: file main.cpp, line 10.
(gdb) c
Continuing.

Breakpoint 1, main () at main.cpp:10
10	        n++;
(gdb) p sum
$1 = 2145
(gdb) c
Continuing.

Breakpoint 1, main () at main.cpp:10
10	        n++;
(gdb) p sum
$2 = 2211
(gdb)

注意,当调试完成后,如果想令当前程序进行执行,消除调试操作对它的影响,需手动将 GDB 调试器与程序分离,分离过程分为 2 步:

  1. 执行 detach 指令,使GDB调试器和程序分离;

  2. 执行 quit(或q)指令,退出GDB调试。

调试执行异常崩溃的程序

Linux操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为core 文件,Linux 系统所具备的这种功能又称为核心转储(core dump)。幸运的是,GDBcore 文件的分析和调试提供有非常强大的功能支持,当程序发生异常崩溃时,通过GDB 调试产生的 core文件,往往可以更快速的解决问题。

这里就先不写如何设置core dump文件目录了,可以自行了解。

写个程序验证一下:

#include <stdio.h>

int main() {
    char *a = NULL;
    *a = 2;
    
    return 0;
}

编译运行

$ g++ -g -o test core.cpp 
$ ./test
Segmentation fault (core dumped)      <-- 发生段错误,并生成了 core 文件

可以根据生成时间查找core dump文件

ls /home/homework/coresave -hl | grep test
-rw-rw-rw- 1 root      root      400K Mar 13 15:08 core.test.27725.1615619332
-rw-rw-rw- 1 root      root      400K Mar 13 15:26 core.test.7791.1615620408
-rw-rw-rw- 1 root      root      540K Mar 11 10:29 core.test.1868.1615429740
-rw-rw-rw- 1 root      root      400K Mar 13 15:07 core.test.26880.1615619264
-rw-rw-rw- 1 root      root      404K Mar  3 19:42 core.test.28802.1614771771

gdb进行调试

$ gdb test /home/homework/coresave/core.test1.7791.1615620408 -q
Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done.

warning: core file may not match specified executable file.
[New LWP 7791]
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004005bd in main () at core.cpp:5
5	    *a = 2;

由此可见,程序崩溃了在第五行,定位到了出现问题的代码位置。

本文参考

GDB调试教程

本文作者:zhuyong
原文链接:https://zhuyongchn.github.io
关于博主:欢迎关注左侧公众号,获取更多干货。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GDB调试命令详解 的相关文章

  • Linux下的C#,Process.Start()异常“没有这样的文件或目录”

    我在使用 Process 类调用程序来启动程序时遇到问题 可执行文件的层次结构位于 bin 目录下 而当前工作目录需要位于 lib 目录下 project bin a out this is what I need to call lib
  • Nasm 打印到下一行

    我用 nasm Assembly 编写了以下程序 section text global start start Input variables mov edx inLen mov ecx inMsg mov ebx 1 mov eax 4
  • 在64位操作系统上以32位模式和64位模式编译ioctl函数的执行有什么不同?

    我有 64 位 Enterprise SuSE 11 我有一个应用程序 它打开 HIDRAW 设备并在其上操作 ioctl 函数以从该设备获取原始信息 如下所示 struct hidraw devinfo devinfo int fd op
  • Mono 和 WebRequest 速度 - 测试

    在 mono 4 6 2 linux 中 我注意到 wget 下载文件的速度与webclient DownloadString 所以我做了一个小测试来调查 为什么 wget 明显比 C 快 根据我自己的实验 使用 wget 下载 手动读取文
  • 是否可以找到哪个用户位于 localhost TCP 连接的另一端?

    这是一个编程问题 但它是 Linux Unix 特定的 如果我从本地主机获得 TCP 连接 是否有一种简单的方法可以告诉哪个用户在 C 程序内建立了连接而无需 shell 我知道这对于 Unix 域套接字来说并不太难 我已经知道远程 IP
  • 跟踪 pthread 调度

    我想做的是创建某种图表 详细说明 Linux 中 两个 线程的执行情况 我不需要查看线程的作用 只需查看它们何时被安排以及持续多长时间 基本上是一条时间线 在过去的几个小时里 我一直在互联网上搜索跟踪 pthread 调度的方法 不幸的是
  • 如何删除树莓派的相机预览

    我在我的 raspberryPi 上安装了 SimpleCv 并安装了用于使用相机板的驱动程序 uv4l 驱动程序 现在我想使用它 当我在 simpleCV shell Camera 0 getImage save foo jpg 上键入时
  • jpackage linux 创建的桌面文件不足

    我刚刚开始使用 jpackage 它是一个非常棒的工具 只要迈出一步 我的肩上的工作就减轻了很多 我对看起来硬编码且无法定制的东西越感到惊讶 JPackage 自动生成启动器 lib
  • 我在哪里可以学习如何使 C++ 程序与操作系统 (Linux) 交互

    我是一个 C 初学者 我想创建与操作系统交互的小程序 使用 Kubuntu Linux 到目前为止 我还没有找到任何教程或手册来让 C 与操作系统交互 在 PHP 中 我可以使用命令 exec 或反引号运算符来启动通常在控制台中执行的命令
  • 使用 sed 将 old-link-url 替换为 new-link-url

    我正在 bash 中编写一个脚本 将 old link url 替换为 new link url 我的问题是 sed 由于斜杠而无法替换 url 如果我只输入一些文字就可以了 my code sed e s old link new lin
  • 使用netcat将unix套接字传输到tcp套接字

    我正在尝试使用以下命令将 unix 套接字公开为 tcp 套接字 nc lkv 44444 nc Uv var run docker sock 当我尝试访问时localhost 44444 containers json从浏览器中 它不会加
  • 原生 Linux 应用程序可像 ResHacker 一样编辑 Win32 PE

    我想运行自动修改 dll服务 用户提交特定的 dll 我在服务器上修改它 然后用户可以下载 dll的修改版本 是否有任何本机 Linux 应用程序提供常见的 Win32 PE 修改功能 例如图标 字符串 加速器 对话等 至少提供命令行或脚本
  • /proc/PID 文件格式

    我想从中检索一些流程信息 proc目录 我的问题如下 中的文件是否有标准格式 proc PID 例如 有这个proc PID status文件与Name t ProcName在第一行 我可以在其他地方用空格代替这个文件吗 t或者类似的东西
  • 通过 SSH 将变量传递给远程脚本

    我正在通过 SSH 从本地服务器在远程服务器上运行脚本 首先使用 SCP 复制该脚本 然后在传递一些参数时调用该脚本 如下所示 scp path to script server example org another path ssh s
  • 如何在 Linux 中使用单行命令获取 Java 版本

    我想通过单个命令获取 Linux 中的 Java 版本 我是 awk 的新手 所以我正在尝试类似的事情 java version awk print 3 但这不会返回版本 我将如何获取1 6 0 21从下面的Java版本输出 java ve
  • 错误:NVIDIA-SMI 失败,因为无法与 NVIDIA 驱动程序通信

    NVIDIA SMI 抛出此错误 NVIDIA SMI 失败 因为无法与 NVIDIA 通信 司机 确保安装了最新的 NVIDIA 驱动程序并且 跑步 我清除了 NVIDIA 并按照提到的步骤重新安装了它here https askubun
  • Linux命令列出所有可用命令和别名

    是否有一个 Linux 命令可以列出该终端会话的所有可用命令和别名 就好像您输入 a 并按下 Tab 键一样 但针对的是字母表中的每个字母 或者运行 别名 但也返回命令 为什么 我想运行以下命令并查看命令是否可用 ListAllComman
  • CentOS目录结构是树形的吗?

    CentOS 上有相当于树的东西吗 如果你的 Centos 系统上没有安装 tree 无论如何我通常建议服务器设置使用最小安装磁盘 你应该在命令行中输入以下内容 yum install tree y 如果没有安装 那是因为您没有正确的存储库
  • “grep -q”的意义是什么

    我正在阅读 grep 手册页 并遇到了 q 选项 它告诉 grep 不向标准输出写入任何内容 如果发现任何匹配 即使检测到错误 也立即以零状态退出 我不明白为什么这可能是理想或有用的行为 在一个程序中 其原因似乎是从标准输入读取 处理 写入
  • 如何从linux命令行运行.exe可执行文件? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我在 Windows 中有一个 abc exe 可执行文件 我可以使用 DOS 命令提示来执行此应用程序 并为其提供一些运行时变量 我想从

随机推荐

  • 什么是Base64

    一 什么是Base64 百度百科中对Base64有一个很好的解释 Base64是网络上最常见的用于传输8Bit字节码的编码方式之一 Base64就是一种基于64个可打印字符来表示二进制数据的方法 什么是 可打印字符 呢 为什么要用它来传输8
  • logback mdc日志跟踪

    1 简介 MDC Mapped Diagnostic Context 映射调试上下文 是 log4j logback及log4j2 提供的一种方便在多线程条件下记录日志的功能 MDC 可以看成是一个与当前线程绑定的哈希表 可以往其中添加键值
  • 图象恢复——(逆滤波,维纳滤波)

    目的 对获取图像在频域用高斯函数进行退化并叠加白噪声 对退化图像进行逆滤波和维纳滤波恢复 比较原始图像和恢复图像 对利用逆滤波和维纳滤波恢复方法恢复图像进行比较 一 基本原理 图像复原是一种客观的操作 通过使用退化现象的先验知识重建或恢复一
  • Windows上Kafka运行环境安装

    1 安装JDK 1 1 安装文件 http www oracle com technetwork java javase downloads index html 下载JDK 1 2 安装完成后需要添加以下的环境变量 右键点击 我的电脑 g
  • Daily Scrum: 2012/11/12

    由于我们是从10月31日开始进行Daily Scrum的 所以我们的Daily Scrum时间段为10 31 11 12共10天 包括一天周六 成员 角色 今天工作 明天计划 王安然 PM Dev 完成了EnermyCraft抽象类 并进行
  • 毕业设计-基于深度学习的命名实体识别研究

    目录 目录 前言 课题背景和意义 实现技术思路 一 命名实体识别简单概述 二 基于深度学习的命名实体识别方法 实现结果 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精
  • 打包第三库那些事

    介绍 一般来说 写完一个第三方库需要打包出三个文件夹的文件 对应三种不同模块类型 outputpath dist umd module es es module lib commonjs module 三个模块类型 umd UMD Univ
  • Springboot使用Knife4j

    简述 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案 knife4j的前身是 swagger bootstrap ui 为了契合微服务的架构发展 由于原来 swagger bootstrap ui采用的
  • eclipse opengl java_eclipse安装openGL方法(完整版)

    学校上机使用在Windows上开发OpenGL 一般都会选择Visual Studio作为开发工具 不过我更喜欢Eclipse 在Windows上开发OpenGL所需的库一般会带有32这个后缀 跟Linux上的还不太一样 1 首先下载Ecl
  • 移入——归约技术

    归约 定义 我们可以将自底向上语法分析过程看成是建一个串w 归约 慰问发开始符号的过程 在归约中 一个与某产生式体相匹配的特定子串被替换为该产生式的头部的非终结符号 定义理解起来比较晦涩 我们来看个例子就知道了 已知有文法 E gt E T
  • SpringBoot slf4j的yaml日志配置不生效

    Spring boot工程中使用slf4j日志框架 发现日志配置总是不生效 我的yaml配置如下 logging level 全局日志级别 root info 具体到某个类的日志级别 打印所有访问请求日志 com xyz filter We
  • specialization of template.... in different namespace的解决

    代码来自DTL文档index htm struct Example tablename columnname int exampleInt DB EXAMPLE INT VALUE string exampleStr DB EXAMPLE
  • 理论总结

    作业1 2 1 Python程序是区分大小写的 2 解释程序对高级语言编写的程序是一边翻译 一边执行的 下次执行同样的程序时 还必须重新翻译 3 Python是一种用途广泛 解释型 面向对象的程序设计语言 4 程序设计语言包括机器语言 汇编
  • Into Clause VS Let Clause

    1 Into Clause 用来将select join 或者group的结果存储到一个临时变量中 目的 在之后的查询中需要使用此结果 例如 var developersGroupedByLanguage from d in develop
  • 面了一个测试工程师,明显感觉他背了很多面试题...

    最近有朋友去字节面试 面试前后进行了20天左右 包含4轮电话面试 1轮笔试 1轮主管视频面试 1轮hr视频面试 据他所说 80 的人都会栽在第一轮面试 要不是他面试前做足准备 估计都坚持不完后面几轮面试 其实 第一轮的电话面试除了一些常规的
  • LRU缓存淘汰算法

    概念理解 1 LRU是Least Recently Used的缩写 即最近最少使用页面置换算法 是为虚拟页式存储管理服务的 2 操作系统课程里有学过 在内存不够的场景下 淘汰就内容的策略 淘汰掉最不经常使用 LRU原理 可以用一个特殊的栈来
  • CentOS 7安装Gnome GUI 图形界面

    http www centoscn com image text config 2015 0528 5552 html
  • jwt在线解密工具分享

    前言 之前调用一个第三方api的时候 看到需要在Authorization填写bearer token 英文不好 看成了熊 bear 心里很疑惑 实际上 bearer 指的是持票人 Bearer Token用于授权访问资源 任何Bearer
  • splunk之获取数据(Ingesting Data)

    Ingesting Data 下载数据地址 http splk it f1data use uname in the Username field and 5p1unkbcup for the Password field
  • GDB调试命令详解

    GDB是什么 调试程序 程序中出现的语法错误可以借助编译器解决 但逻辑错误则只能靠自己解决 实际场景中解决逻辑错误最高效的方法 就是借助调试工具对程序进行调试 所谓调试 Debug 就是让代码一步一步慢慢执行 跟踪程序的运行过程 比如 可以