C C++调试总结:IDE(visual studio和Dev C+,威力加强版

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

可以这样理解,使用 start 指令启动程序,完全等价于先在 main() 主函数起始位置设置一个断点,然后再使用 run 指令启动程序。另外,程序执行过程中使用 run 或者 start 指令,表示的是重新启动程序。

问一个问题,GDB 调试器启动后是否就可以直接使用 run 或者 start 指令了呢?答案当然是否定的。我们知道,启动 GDB 调试器的方式有多种,其中简单的方法就是直接使用 gdb 指令,例如:

[root@bogon demo]# gdb
GNU gdb (GDB) 8.0.1
Copyright © 2017 Free Software Foundation, Inc.
… <-- 省略部分输出信息
Type “apropos word” to search for commands related to “word”.
(gdb)

注意,使用此方式启动的 GDB 调试器,尚未指定要调试的目标程序,何谈使用 run 或者 start 指令呢?

不仅如此,在进行 run 或者 start 指令启动目标程序之前,还可能需要做一些必要的准备工作,大致包括以下几个方面:

  • 如果启动 GDB 调试器时未指定要调试的目标程序,或者由于各种原因 GDB 调试器并为找到所指定的目标程序,这种情况下就需要再次手动指定;
  • 有些 C 或者 C++ 程序的执行,需要接收一些参数(程序中用 argc 和 argv[] 接收);
  • 目标程序在执行过程中,可能需要临时设置 PATH 环境变量;
  • 默认情况下,GDB 调试器将启动时所在的目录作为工作目录,但很多情况下,该目录并不符合要求,需要在启动程序手动为 GDB 调试器指定工作目录。
  • 默认情况下,GDB 调试器启动程序后,会接收键盘临时输入的数据,并将执行结果会打印在屏幕上。但 GDB 调试器允许对执行程序的输入和输出进行重定向,使其从文件或其它终端接收输入,或者将执行结果输出到文件或其它终端。

假设使用 GDB 调试器调试如下程序:

//存储路径为 /tmp/demo/main.c
//其生成的可执行文件为 main.exe,位于同一路径下
#include<stdio.h>
int main(int argc,char* argv[])
{
FILE * fp;
if((fp = fopen(argv[1],“r”)) == NULL){
printf(“file open fail”);
}
else{
printf(“file open true”);
}
return 0;
}

要知道,命令行窗口打开时默认位于 ~ (表示当前用户的主目录)路径下,假设我们就位于此目录中使用 gdb 命令启动 GDB 调试器,则在执行 main.exe 之前,有以下几项操作要做:

  1. 首先,对于已启动的 GDB 调试器,我们可以先通过 l (小写的 L)指令验证其是否已找到指定的目标程序文件:

[root@bogon ~]# gdb -q <-- 使用 -q 选项,可以省略不必要的输出信息
(gdb) l
No symbol table is loaded. Use the “file” command.

可以看到,对于找不到目标程序文件的 GDB 调试器,l 指令的执行结果显示“无法加载符号表”。这种情况下,我们就必须手动为其指定要调试的目标程序,例如:

(gdb) file /tmp/demo/main.exe
Reading symbols from /tmp/demo/main.exe…
(gdb) l
1 #include<stdio.h>
2 int main(int argc,char* argv[])
3 {
4 FILE * fp;
5 if((fp = fopen(argv[1],“r”)) == NULL){
6 printf(“file open fail”);
7 }
8 else{
9 printf(“file open true”);
10 }
(gdb)
11 return 0;
12 }
(gdb)

可以看到,通过借助 file 命令,则无需重启 GDB 调试器也能指定要调试的目标程序文件。

除了 file 指令外,GDB 调试器还提供有其它的指定目标调试文件的指令,感兴趣的读者可千万 GDB 官网做详细了解,后续章节在用到时也会做详细的讲解。

  1. 通过分析 main.c 中程序的逻辑不难发现,要想其正确执行,必须在执行程序的同时给它传递一个目标文件的文件名。

总的来说,为 GDB 调试器指定的目标程序传递参数,常用的方法有 3 种:
1、启动 GDB 调试器时,可以在指定目标调试程序的同时,使用 --args 选项指定需要传递给该程序的数据。仍以 main.exe 程序为例:

[root@bogon demo]# gdb --args main.exe a.txt

整个指令的意思是:启动 GDB 调试器调试 main.exe 程序,并为其传递 “a.txt” 这个字符串(其会被 argv[] 字符串数组接收)。

2、GDB 调试器启动后,可以借助 set args 命令指定目标调试程序启动所需要的数据。仍以 main.exe 为例:

(gdb) set args a.txt

该命令表示将 “a.txt” 传递给将要调试的目标程序。

3、除此之外,还可以使用 run 或者 start 启动目标程序时,指定其所需要的数据。例如:

(gdb) run a.txt
(gdb) start a.txt

以上 2 条命令都可以将 “a.txt” 传递给要调试的程序。

  1. 要知道,对于调试 /tmp/demo/ 路径下的 main.exe 文件,将其作为 GDB 调试器的工作目录,一定程度上可以提高我们的调试效率。反之,如果 GDB 调试器的工作目录和目标调试文件不在同一目录,则很多时候需要额外指明要操作文件的存储路径(例如第 1) 种情况中用 file 指令指明调试文件时就必须指明其存储位置)。

默认情况下,GDB 调试器的工作目录为启动时所使用的目录。例如在 ~ 路径下启动的 GDB 调试器,其工作目录就为 ~(当前用户的 home 目录)。当然,GDB 调试器提供有修改工作目录的指令,即 cd 指令。例如,将 GDB 调试器的工作目录修改为 /tmp/demo,则执行指令为:

(gdb) cd /tmp/demo

由此,GDB 调试器的工作目录就变成了 /tmp/demo。

  1. 某些场景中,目标调试程序的执行还需要临时修改 PATH 环境变量,此时就可以借助 path 指令,例如:

(gdb) path /temp/demo
Executable and object file path: /temp/demo:/usr/local/sbin:/usr/local/bin…

注意,此修改方式只是临时的,退出 GDB 调试后会失效。

  1. 默认情况下,GDB 调试的程序会接收 set args 等方式指定的参数,同时会将输出结果打印到屏幕上。而通过对输入、输出重定向,可以令调试程序接收指定文件或者终端提供的数据,也可以将执行结果输出到文件或者某个终端上。

例如,将 main.exe 文件的执行结果输出到 a.txt 文件中,执行如下命令:

(gdb) run > a.txt

由此,在 GDB 调试的工作目录下就会生成一个 a.txt 文件,其中存储的即为 main.exe 的执行结果。

总的来说,只有将调试程序所需的运行环境搭建好后,才能使用 run 或者 start 命令开始调试。如下是一个完整的实例,演示了 GDB 调试 mian.exe 之前所做的准备工作:

[root@bogon demo]# pwd <–显示当前工作路径
/tmp/demo
[root@bogon demo]# ls <-- 显示当前路径下的文件
a.txt main.c main.exe
[root@bogon demo]# cd ~ <-- 进入 home 目录
[root@bogon ~]# gdb -q <-- 开启 GDB 调试器
(gdb) cd /tmp/demo <-- 修改 GDB 调试器的工作目录
Working directory /tmp/demo.
(gdb) file main.exe <-- 指定要调试的目标文件
Reading symbols from main.exe…
(gdb) set args a.txt <-- 指定传递的数据
(gdb) run <-- 运行程序
Starting program: /tmp/demo/main.exe a.txt
file open true[Inferior 1 (process 43065) exited normally]

GDB break(b):设置断点

默认情况下,程序不会进入调试模式,代码会瞬间从开头执行到末尾。要想观察程序运行的内部细节(例如某变量值的变化情况),可以借助 GDB 调试器在程序中的某个地方设置断点,这样当程序执行到这个地方时就会停下来。

所谓断点(BreakPoint),读者可以理解为障碍物,人遇到障碍物不能行走,程序遇到断点就暂停执行。

在 GDB 调试器中对 C、C++ 程序打断点,最常用的就是 break 命令,有些场景中还会用到 tbreak 或者 rbreak 命令,本节将对这 3 个命令的功能和用法做详细的讲解。

为了让大家更好地了解给程序打断点的作用,这里以一段完整的 C 语言程序为例:

#include<stdio.h>
int main(int argc,char* argv[])
{
int num = 1;
while(num<100)
{
num *= 2;
}
printf(“num=%d”,num);
return 0;
}

程序存储在/tmp/demo/main.c文件中,并已经生成了具备调试信息的 main.exe 可执行文件:

[root@bogon demo]# ls
main.c main.exe
[root@bogon demo]# gdb main.exe -q
Reading symbols from main.exe…
(gdb)

GDB break命令

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

1、(gdb) break location // b location
2、(gdb) break … if cond // b … if cond

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

如下演示了以上 2 种打断点方式的具体用法:

(gdb) l
1 #include<stdio.h>
2 int main(int argc,char* argv[])
3 {
4 int num = 1;
5 while(num<100)
6 {
7 num *= 2;
8 }
9 printf(“num=%d”,num);
10 return 0;
(gdb)
11 }
(gdb) b 4 <-- 程序第 4 行打断点
Breakpoint 1 at 0x1138: file main.c, line 4.
(gdb) r <-- 运行程序,至第 4 行暂停
Starting program: /home/ubuntu64/demo/main.exe

Breakpoint 1, main (argc=1, argv=0x7fffffffe078) at main.c:4
4 int num = 1;
(gdb) b +1 <-- 在第 4 行的基础上,在第 5 行代码处打断点
Breakpoint 2 at 0x55555555513f: file main.c, line 5.
(gdb) c <-- 继续执行程序,至第 5 行暂停
Continuing.

Breakpoint 2, main (argc=1, argv=0x7fffffffe078) at main.c:5
5 while(num<100)
(gdb) b 7 if num>10 <-- 如果 num>10 在第 7 行打断点
Breakpoint 3 at 0x555555555141: file main.c, line 7.
(gdb) c <-- 继续执行
Continuing.

Breakpoint 3, main (argc=1, argv=0x7fffffffe078) at main.c:7
7 num *= 2; <-- 程序在第 7 行暂停
(gdb) p num <-- p 命令查看 num 当前的值
$1 = 16 <-- num=16

有关 c 和 p 命令,后续章节会做详细讲解,这里会用即可,不必深究。

GDB tbreak命令

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

tbreak 命令的使用格式和 break 完全相同,有以下 2 种:

1、(gdb) tbreak location
2、(gdb) tbreak … if cond

其中,location、… 和 cond 的含义都和 break 命令中的参数含义相同,即表 1 也同样适用于 tbreak 命令。

仍以 main.exe 为例,如下演示了 tbreak 命令的用法:

(gdb) tbreak 7 if num>10
Temporary breakpoint 1 at 0x1165: file main.c, line 7.
(gdb) r
Starting program: /home/ubuntu64/demo/main.exe

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe088) at main.c:7
7 num *= 2;
(gdb) p num
$1 = 16
(gdb) c <-- 继续执行程序,则原使用 tbreak 在第 7 行打的断点将不再起作用
Continuing.
num=128[Inferior 1 (process 6534) exited normally]
(gdb)

可以看到,自num=16开始,后续循环过程中 num 的值始终大于 10,则num>10表达式的值永远为 True,理应在第 7 行暂停多次。但由于打断点采用的是 tbreak 命令,因此断点的作用只起 1 次。

GDB rbreak 命令

和 break 和 tbreak 命令不同,rbreak 命令的作用对象是 C、C++ 程序中的函数,它会在指定函数的开头位置打断点。

tbreak 命令的使用语法格式为:

(gdb) tbreak regex

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

这里我们对 main.c 源文件的程序做如下修改:

(gdb) l <-- 显示源码
1 #include<stdio.h>
2 void rb_one(){
3 printf(“rb_one\n”);
4 }
5 void rb_second(){
6 printf(“rb_second”);
7 }
8 int main(int argc,char* argv[])
9 {
10 rb_one();
(gdb)
11 rb_second();
12 return 0;
13 }
(gdb) rbreak rb_* <–匹配所有以 rb_ 开头的函数
Breakpoint 1 at 0x1169: file main.c, line 2.
void rb_one();
Breakpoint 2 at 0x1180: file main.c, line 5.
void rb_second();
(gdb) r
Starting program: /home/ubuntu64/demo/main.exe

Breakpoint 1, rb_one () at main.c:2
2 void rb_one(){
(gdb) c
Continuing.
rb_one

Breakpoint 2, rb_second () at main.c:5
5 void rb_second(){
(gdb) c
Continuing.
rb_second[Inferior 1 (process 7882) exited normally]
(gdb)

可以看到,通过执行rbreak rb_*指令,找到了程序中所有以 tb_* 开头的函数,并在这些函数内部的开头位置打上了断点(如上所示,分别为第 2 行和第 5 行)。

总结

在 GDB 调试器中,为程序的适当位置打断点,是最常用的调试程序的方法。不过,本节仅介绍了如何使用 break(tbreak、rbreak)在程序中打断点,实际场景中还可以使用 catch 或者 watch 中断程序的运行,有关它们的功能和用法,会在后续章节中给大家做详细讲解。

GDB watch命令:监控变量值的变化

GDB break命令》一节,给大家介绍了使用 break 命令在程序某一行的位置打断点。但还有一些场景,我们需要监控某个变量或者表达式的值,通过值的变化情况判断程序的执行过程是否存在异常或者 Bug。这种情况下,break 命令显然不再适用,推荐大家使用 watch 命令。

要知道,GDB 调试器支持在程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点。其中 break 命令打的就是普通断点,而 watch 命令打的为观察断点,关于捕捉断点,后续章节会做详细讲解。

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

所谓表达式,就是包含多个变量的式子,比如 a+b 就是一个表达式,其中 a、b 为变量。

对于监控 C、C++ 程序中某变量或表达式的值是否发生改变,watch 命令的语法非常简单,如下所示:

(gdb) watch cond

其中,conde 指的就是要监控的变量或表达式。

和 watch 命令功能相似的,还有 rwatch 和 awatch 命令。其中:

  • rwatch 命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
  • awatch 命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。

强调一下,watch 命令的功能是:只有当被监控变量(表达式)的值发生改变,程序才会停止运行。

举个例子:

(gdb) l <–列出要调试的程序源码
1 #include<stdio.h>
2 int main(int argc,char* argv[])
3 {
4 int num = 1;
5 while(num<=100)
6 {
7 num *= 2;
8 }
9 printf(“%d”,num);
10 return 0;
(gdb)
11 }
(gdb) b 4 <-- 使用 break 命令打断点
Breakpoint 1 at 0x115c: file main.c, line 4.
(gdb) r <-- 执行程序
Starting program: /home/ubuntu64/demo/main.exe

Breakpoint 1, main (argc=1, argv=0x7fffffffe088) at main.c:4
4 int num = 1;
(gdb) watch num <-- 监控程序中 num 变量的值
Hardware watchpoint 2: num
(gdb) c <-- 继续执行,当 num 值发生改变时,程序才停止执行
Continuing.

Hardware watchpoint 2: num

Old value = 0
New value = 2
main (argc=1, argv=0x7fffffffe088) at main.c:5
5 while(num<=100)
(gdb) c <-- num 值发生了改变,继续执行程序
Continuing.

Hardware watchpoint 2: num

Old value = 2
New value = 4
main (argc=1, argv=0x7fffffffe088) at main.c:5
5 while(num<=100)
(gdb)

有关代码中蓝色部分的含义,本文后续会做详细解释。

可以看到在程序运行过程中,通过借助 watch 命令监控 num 的值,后续只要 num 的值发生改变,程序都会停止。感兴趣的读者,可自行尝试使用 awatch 和 rwatch 命令,这里不再给出具体的示例。

如果我们想查看当前建立的观察点的数量,借助如下指令即可:

(gdb) info watchpoints

值得一提的是,对于使用 watch(rwatch、awatch)命令监控 C、C++ 程序中变量或者表达式的值,有以下几点需要注意:

  • 当监控的变量(表达式)为局部变量(表达式)时,一旦局部变量(表达式)失效,则监控操作也随即失效;
  • 如果监控的是一个指针变量(例如 *p),则 watch *p 和 watch p 是有区别的,前者监控的是 p 所指数据的变化情况,而后者监控的是 p 指针本身有没有改变指向;
  • 这 3 个监控命令还可以用于监控数组中元素值的变化情况,例如对于 a[10] 这个数组,watch a 表示只要 a 数组中存储的数据发生改变,程序就会停止执行。

如果读者只想学习如何使用 watch 命令,则读者这里即可。反之,如果想了解 watch 命令底层是如何实现的,可以继续往下阅读。

watch命令的实现原理

watch 命令实现监控机制的方式有 2 种,一种是为目标变量(表达式)设置硬件观察点,另一种是为目标变量(表达式)设置软件观察点。

所谓软件观点(software watchpoint),即用 watch 命令监控目标变量(表达式)后,GDB 调试器会以单步执行的方式运行程序,并且每行代码执行完毕后,都会检测该目标变量(表达式)的值是否发生改变,如果改变则程序执行停止。

可想而知,这种“实时”的判别方式,一定程度上会影响程序的执行效率。但从另一个角度看,调试程序的目的并非是为了获得运行结果,而是查找导致程序异常或 Bug 的代码,因此即便软件观察点会影响执行效率,一定程度上也是可以接受的。

所谓硬件观察点(Hardware watchpoint),和前者最大的不同是,它在实现监控机制的同时不影响程序的执行效率。简单的理解,系统会为 GDB 调试器提供少量的寄存器(例如 32 位的 Intel x86 处理器提供有 4 个调试寄存器),每个寄存器都可以作为一个观察点协助 GDB 完成监控任务。

需要注意的是,基于寄存器个数的限制,如果调试环境中设立的硬件观察点太多,则有些可能会失去作用,这种情况下,GDB 调试器会发出如下警告:

Hardware watchpoint num: Could not insert watchpoint

解决方案也很简单,就是删除或者禁用一部分硬件观察点。

除此之外,受到寄存器数量的限制,可能会出现:无法使用硬件观察点监控数据类型占用字节数较多的变量(表达式)。比如说,某些操作系统中,GDB 调试器最多只能监控 4 个字节长度的数据,这意味着 C、C++ 中 double 类型的数据是无法使用硬件观察点监测的。这种情况下,可以考虑将其换成占用字符串少的 float 类型。

目前,大多数 PowerPC 或者基于 x86 的操作系统,都支持采用硬件观点。并且 GDB 调试器在建立观察断点时,会优先尝试建立硬件观察点,只有当前环境不支持硬件观察点时,才会建立软件观察点。借助如下指令,即可强制 GDB 调试器只建立软件观察点:

set can-use-hw-watchpoints 0

注意,在执行此命令之前建立的硬件观察点,不会受此命令的影响。

注意,awatch 和 rwatch 命令只能设置硬件观察点,如果系统不支持或者借助如上命令禁用,则 GDB 调试器会打印如下信息:

Expression cannot be implemented with read/access watchpoint.

GDB catch命令:建立捕捉断点

要知道,GDB 调试器支持在被调试程序中打 3 种断点,分别为普通断点、观察断点和捕捉断点,其中普通断点用 break 命令建立(可阅读《GDB break》一节),观察断点用 watch 命令建立(可阅读《GDB watch》一节),本节将讲解如何使用 catch 命令建立捕捉断点。

和前 2 种断点不同,普通断点作用于程序中的某一行,当程序运行至此行时停止执行,观察断点作用于某一变量或表达式,当该变量(表达式)的值发生改变时,程序暂停。而捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。

用捕捉断点监控某一事件的发生,等同于在程序中该事件发生的位置打普通断点。

建立捕捉断点的方式很简单,就是使用 catch 命令,其基本格式为:

(gdb) catch event

其中,event 参数表示要监控的具体事件。对于使用 GDB 调试 C、C++ 程序,常用的 event 事件类型如表 1 所示。

event 事件含 义
throw [exception]当程序中抛出 exception 指定类型异常时,程序停止执行。如果不指定异常类型(即省略 exception),则表示只要程序发生异常,程序就停止执行。
catch [exception]当程序中捕获到 exception 异常时,程序停止执行。exception 参数也可以省略,表示无论程序中捕获到哪种异常,程序都暂停执行。
load [regexp] unload [regexp]其中,regexp 表示目标动态库的名称,load 命令表示当 regexp 动态库加载时程序停止执行;unload 命令表示当 regexp 动态库被卸载时,程序暂停执行。regexp 参数也可以省略,此时只要程序中某一动态库被加载或卸载,程序就会暂停执行。

除表中罗列的以外,event 参数还有其它一些写法,感兴趣的读者可查看 GDB官网进行了解,这里不再过多赘述。

注意,当前 GDB 调试器对监控 C++ 程序中异常的支持还有待完善,使用 catch 命令时,有以下几点需要说明:

  1. 对于使用 catch 监控指定的 event 事件,其匹配过程需要借助 libstdc++ 库中的一些 SDT 探针,而这些探针最早出现在 GCC 4.8 版本中。也就是说,想使用 catch 监控指定类型的 event 事件,系统中 GCC 编译器的版本最低为 4.8,但即便如此,catch 命令是否能正常发挥作用,还可能受到系统中其它因素的影响。
  2. 当 catch 命令捕获到指定的 event 事件时,程序暂停执行的位置往往位于某个系统库(例如 libstdc++)中。这种情况下,通过执行 up 命令,即可返回发生 event 事件的源代码处。
  3. catch 无法捕获以交互方式引发的异常。

如同 break 命令和 tbreak 命令的关系一样(前者的断点是永久的,后者是一次性的),catch 命令也有另一个版本,即 tcatch 命令。tcatch 命令和 catch 命令的用法完全相同,唯一不同之处在于,对于目标事件,catch 命令的监控是永久的,而 tcatch 命令只监控一次,也就是说,只有目标时间第一次触发时,tcath 命令才会捕获并使程序暂停,之后将失效。

接下来就以下面的 C++ 程序为例,给大家演示 catch(tcatch)命令的用法:

#include
using namespace std;
int main(){
int num = 1;
while(num <= 5){
try{
throw 100;
}catch(int e){
num++;
cout << “next” << endl;
}
}
cout << “over” << endl;
return 0;
}

此程序存储于 ~/demo/main.cpp 文件中( ~ 表示当前登陆用户的主目录)。

在此基础上,对 main.cpp 文件进行编译,并启动对该程序的调试:

[root@bogon demo]$ ls
main.cpp
[root@bogon demo]# g++ main.cpp -o main.exe -g
[root@bogon demo]$ ls
main.cpp main.exe
[root@bogon demo]# gdb main.exe -q
Reading symbols from main.exe…done.
(gdb)

通过观察程序可以看出,当前程序中通过 throw 手动抛出了 int 异常,此异常能够被 catch 成功捕获。假设我们使用 catch 命令监控:只要程序中引发 int 异常,程序就停止执行:

(gdb) catch throw int <-- 指定捕获“throw int”事件
Catchpoint 1 (throw)
(gdb) r <-- 执行程序
Starting program: ~/demo/main.exe

Catchpoint 1 (exception thrown), 0x00007ffff7e81762 in __cxa_throw ()
from /lib/x86_64-linux-gnu/libstdc++.so.6 <-- 程序暂停执行
(gdb) up <-- 回到源码
#1 0x0000555555555287 in main () at main.cpp:8
8 throw 100;
(gdb) c <-- 继续执行程序
Continuing.
next

Catchpoint 1 (exception thrown), 0x00007ffff7e81762 in __cxa_throw ()
from /lib/x86_64-linux-gnu/libstdc++.so.6
(gdb) up
#1 0x0000555555555287 in main () at main.cpp:8
8 throw 100;
(gdb)

如上所示,借助 catch 命令设置了一个捕获断点,该断点用于监控 throw int 事件,只要发生程序就会暂停执行。由此当程序执行时,其会暂停至 libstdc++ 库中的某个位置,借助 up 指令我们可以得知该异常发生在源代码文件中的位置。

同理,我们也可以监控 main.cpp 程序中发生的 catch event 事件:

(gdb) catch catch int
Catchpoint 1 (catch)
(gdb) r
Starting program: ~/demo/main.exe

Catchpoint 1 (exception caught), 0x00007ffff7e804d3 in __cxa_begin_catch ()
from /lib/x86_64-linux-gnu/libstdc++.so.6
(gdb) up
#1 0x00005555555552d0 in main () at main.cpp:9
9 }catch(int e){
(gdb) c
Continuing.
next

Catchpoint 1 (exception caught), 0x00007ffff7e804d3 in __cxa_begin_catch ()
from /lib/x86_64-linux-gnu/libstdc++.so.6
(gdb) up
#1 0x00005555555552d0 in main () at main.cpp:9
9 }catch(int e){
(gdb)

甚至于,在个别场景中,还可以使用 catch 命令监控 C、C++ 程序动态库的加载和卸载。就以 main.exe 为例,其运行所需加载的动态库可以使用 ldd 命令查看,例如:

[root@bogon demo]# ldd main.exe
linux-vdso.so.1 => (0x00007fffbc1ff000)
libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x0000003e75000000)
libm.so.6 => /lib64/libm.so.6 (0x00000037eee00000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000003e74c00000)
libc.so.6 => /lib64/libc.so.6 (0x00000037ee200000)
/lib64/ld-linux-x86-64.so.2 (0x00000037eda00000)

就以监控 libstdc++.so.6 为例,在 GDB 调试器中,通过执行如下指令,即可监控该动态库的加载:

(gdb) catch load libstdc++.so.6
Catchpoint 1 (load)
(gdb) r
Starting program: ~/demo/main.exe

Catchpoint 1
Inferior loaded /lib/x86_64-linux-gnu/libstdc++.so.6
/lib/x86_64-linux-gnu/libgcc_s.so.1
/lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/libm.so.6
0x00007ffff7fd37a5 in ?? () from /lib64/ld-linux-x86-64.so.2

以上实例仅演示了 catch 命令的用法,而 tcatch 命令的语法格式和 catch 完全相同,读者可自行尝试使用 tcatch 命令,观察它的功能。

GDB条件断点(condition命令)详解

前面章节给大家介绍了 GDB 调试器中普通断点、观察断点以及捕捉断点的功能和用法。其中值得一提的是,对于普通断点的建立,可以使用如下格式的 break 命令:

(gdb) break … if cond

… 参数用于指定生成断点的具体位置;cond 参数用于代指某个表达式。通过此方式建立的普通断点,只有当表达式 cond 成立(值为 True)时,才会发挥它的作用;反之,断点并不会使程序停止执行。

类似上面这种,以某个表达式的是否成立作为条件,从而决定自身是否生效的断点,又称为条件断点。除了普通断点外,观察断点和捕捉断点也可以成为条件断点。

需要说明的是,创建普通条件断点的方式,也同样适用于观察条件断点。通过执行如下命令,即可直接生成一个观察条件断点:

(gdb) watch expr if cond

参数 expr 表示要观察的变量或表达式;参数 cond 用于代指某个表达式。

但是,以上创建条件断点的方法,不适用于捕捉断点。换句话说,捕捉条件断点无法直接生成,需要借助 condition 命令为现有捕捉断点增添一个 cond 表达式,才能使其变成条件断点。

总的来说,借助 condition 命令,我们可以将现有的普通断点、观察断点以及捕捉断点变成条件断点;而普通条件断点和观察条件断点,可以分别通过 break if 命令和 watch if 命令直接生成。

接下来,我将给大家详细地讲解 condition 命令的用法。

GDB condition命令

严格来说,condition 命令的功能是:既可以为现有的普通断点、观察断点以及捕捉断点添加条件表达式,也可以对条件断点的条件表达式进行修改。

condition 命令没有缩写形式,使用方法很简单,语法格式如下:

(gdb) condition bnum expression
(gdb) condition bnum

参数 bnum 用于代指目标断点的编号;参数 expression 表示为断点添加或修改的条件表达式。

以上 2 种语法格式中,第 1 种用于为 bnum 编号的断点添加或修改 expression 条件表达式;第 2 种用于删除 bnum 编号断点的条件表达式,使其变成普通的无条件断点。

举个例子,这里以调试如下 C++ 程序为例:

#include
using namespace std;
int main(){
int num = 1;
while(num<20){
try{
throw num;
}catch(int &e){
num++;
}
}
cout << num << endl;
return 0;
}

程序存储位置为~/demo/main.cpp,并已经生成了可供 GDB 调试器使用的执行文件:

[root@bogon demo]# ls
main.cpp main.exe
[root@bogon demo]# gdb main.exe -q
Reading symbols from ~/demo/main.exe…done.
(gdb) l
1 #include
2 using namespace std;
3 int main(){
4 int num = 1;
5 while(num<20){
6 try{
7 throw num;
8 }catch(int &e){
9 num++;
10 }
(gdb)
11 }
12 cout << num << endl;
13 return 0;
14 }
(gdb)

接下来,为读者演示 condition 命令的功能和用法:

(gdb) b 9 <–添加普通断点
Breakpoint 1 at 0x12d0: file main.cpp, line 9.
(gdb) r
Starting program: /home/test/demo/main.exe

Breakpoint 1, main () at main.cpp:9
9 num++;
(gdb) rwatch num <-- 添加观察断点
Hardware read watchpoint 2: num
(gdb) catch throw int <-- 添加捕捉断点
Catchpoint 3 (throw)
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x00005555555552d0 in main() at main.cpp:9 breakpoint already hit 1 time
2 read watchpoint keep y num
3 catchpoint keep y exception throw matching: int
(gdb) condition 1 num3 <-- 为普通断点添加条件表达式
(gdb) condition 2 num
5 <-- 为观察断点添加条件表达式
(gdb) condition 3 num==7 <-- 为捕捉断点添加条件表达式
(gdb) c
Continuing.

Breakpoint 1, main () at main.cpp:9 <-- 普通条件断点触发
9 num++;
(gdb) p num
$1 = 3
(gdb) c
Continuing.

Hardware read watchpoint 2: num <-- 观察条件断点触发

Value = 5
0x000055555555526f in main () at main.cpp:7
7 throw num;
(gdb) c
Continuing.

Catchpoint 3 (exception thrown), 0x00007ffff7e81762 in __cxa_throw () <-- 捕捉条件断点触发
from /lib/x86_64-linux-gnu/libstdc++.so.6
(gdb) up
#1 0x0000555555555285 in main () at main.cpp:7
7 throw num;
(gdb) p num
$2 = 7
(gdb)

可以看到,通过借助 condition 命令为不同类型断点设置条件表达式,只有当条件表达式成立(值为 True)时,相应的断点才会触发从而使程序暂停运行。

GDB ignore命令

ignore 命令也可以使一个断点成为条件断点,但这里的“条件”并非自定义的表达式,而仅为一个整数,它用来表示该断点失效的次数。也就会说,ignore 命令可以使目标断点暂时失去作用,当断点失效的次数超过指定次数时,断点的功能会自动恢复。

ignore 命令也没有缩写形式,其语法格式如下:

ignore bnum count

参数 bnum 为某个断点的编号;参数 count 用于指定该断点失效的次数。

仍以 main.exe 为例:

(gdb) b 9
Breakpoint 1 at 0x400a33: file main.cpp, line 9.
(gdb) r
Starting program: ~/demo/main.exe

Breakpoint 1, main () at main.cpp:9
9 num++;
(gdb) p num
$1 = 1
(gdb) ignore 1 3
Will ignore next 3 crossings of breakpoint 1.
(gdb) c
Continuing.

Breakpoint 1, main () at main.cpp:9
9 num++;
(gdb) p num
$2 = 5
(gdb)

可以看到,执行 ignore 命令之前,num 变量的值为 1。借助 ignore 命令使编号为 1(作用于第 9 行)的断点失效 3 次后,继续执行程序,最终程序仍暂停至第 9 行,此时 num 的值变为 5。这这恰恰证明了 num 从 1 递增至 5 的过程中,编号为 1 的断点失效了 3 次。

GDB单步调试程序

调用GDB调试器的几种方式》一节中提到,借助 next 命令可以控制 GDB 单步执行程序。所谓单步调试,就是通过一行一行的执行程序,观察整个程序的执行流程,进而尝试发现一些存在的异常或者 Bug。

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

本节就来一一给大家讲解这 3 个命令的功能和用法。讲解过程中,将以调试如下 C 语言程序为例:

#include <stdio.h>
int print(int num){
int ret = num * num;
return ret;
}
int myfunc(int num){
int i = 1;
int sum = 0;
while(i <= num){
sum += print(i);
i++;
}
return sum;
}
int main(){
int num =0;
scanf(“%d”, &num);
int result = myfunc(num);
printf(“%d”, result);
return 0;
}

此程序存储在~/demo/main.c源文件中(~ 表示当前用户的主目录),功能是根据用户输入的 num 值,输出 12+22+…+num2 的值。

GDB next 命令

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

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

(gdb) next count

参数 count 表示单步执行多少行代码,默认为 1 行。

举个例子:

(gdb) b 16
Breakpoint 2 at 0x40058b: file main.c, line 16.
(gdb) r
Starting program: /root/demo/main.exe

Breakpoint 2, main () at main.c:16
16 int num =0;
(gdb) n 2 <-- 单步执行 2 次
3
18 int result = myfunc(num);
(gdb) n
19 printf(“%d”, result);
(gdb) n
20 return 0;
(gdb)

可以看到,当程序单步执行第 18 行时,继续执行 next 指令,下一次将要执行的是第 19 行代码,而非 myfunc() 函数内部的代码。

GDB step命令

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

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

(gdb) step count

参数 count 表示一次执行的行数,默认为 1 行。

仍以 main.exe 为例:

(gdb) b 18
Breakpoint 1 at 0x4005ab: file main.c, line 18.
(gdb) r
Starting program: ~/demo/main.exe
Breakpoint 1, main () at main.c:18
18 int result = myfunc(num);
(gdb) step <-- step 命令进入 myfunc() 函数内部执行
myfunc (num=0) at main.c:7
7 int i = 1;

可以看到,当程序暂停到包含 mufunc() 函数的代码行处时(此时该行代码尚未执行),如果使用 step 命令,则 GDB 在执行该函数代码的同时,会进入 mufunc() 函数内部,并暂停在函数内部的第一行代码处。反之如果使用 next 命令,则程序下一次会执行第 19 行代码,而不是第 7 行,这就是它们最大的不同之处。

GDB until命令

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

1、(gdb) until
2、(gdb) until location

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

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

以 main.exe 中 myfunc() 函数的循环为例:

(gdb) b 17
Breakpoint 1 at 0x1201: file main.c, line 17.
(gdb) r
Starting program: ~/demo/main.exe

Breakpoint 1, main () at main.c:17
17 scanf(“%d”, &num);
(gdb) u
3
18 int result = myfunc(num);
(gdb) step
myfunc (num=3) at main.c:7
7 int i = 1;
(gdb) u
8 int sum = 0;
(gdb) u
9 while(i <= num){
(gdb) u
10 sum += print(i);
(gdb) u
11 i++;
(gdb) u <-- 执行 i++ 操作
9 while(i <= num){
(gdb) u <-- 快速执行完循环体
13 return sum;
(gdb) p sum
$1 = 14

可以看到,这里当程序单步执行完第 11 行代码时,借助 until 命令快速执行完了整个循环体,并在第 13 行代码处停止执行。根据 p 命令输出的 num 变量的值可以确认,整个循环过程确定完整地执行完了。

until 命令还可以后跟某行代码的行号,以指示 GDB 调试器直接执行至指定位置后停止。举个例子:

(gdb) r
Starting program:~/demo/main.exe

Breakpoint 1, main () at main.c:17
17 scanf(“%d”, &num);
(gdb) until 19 <-- 执行至第 19 行停止
3
main () at main.c:19
19 printf(“%d”, result);
(gdb) p result
$3 = 14

可以看到,通过执行 until 19 命令,GDB 调试器直接从第 17 行代码处执行至指定的第 19 行。

如何使用GDB进行断点调试?

前面利用 3 节的内容,分别介绍了 GDB 调试器支持在被调试程序中打断点的 3 种方法,即 break、watch 以及 catch 命令。在此基础上,本节给大家讲解:如何借助断点对程序进行调试?

通过在程序的适当位置打断点,观察程序执行至该位置时某些变量(或表达式)的值,进而不断缩小导致程序出现异常或 Bug 的语句的搜索范围,并最终找到,整个过程就称为断点调试。

值得一提的是,整个断点调试的过程,除了要借助 break、watch 或者 catch 命令以外,还要借助其它一些命令,例如在前面章节中,我们已经使用过的 print 命令(查看变量的值)、continue 命令(使程序继续执行)等。

表 1 罗列了断点调试程序过程中,常用的一些命令以及各自的含义。

命令(缩写)功 能
run(r)启动或者重启一个程序。
list(l)显示带有行号的源码。
continue(c)让暂停的程序继续运行。
next(n)单步调试程序,即手动控制代码一行一行地执行。
step(s)如果有调用函数,进入调用的函数内部;否则,和 next 命令的功能一样。
until(u) until location(u location)当你厌倦了在一个循环体内单步跟踪时,单纯使用 until 命令,可以运行程序直到退出循环体。 until n 命令中,n 为某一行代码的行号,该命令会使程序运行至第 n 行代码处停止。
finish(fi)结束当前正在执行的函数,并在跳出函数后暂停程序的执行。
return(return)结束当前调用函数并返回指定值,到上一层函数调用处停止程序执行。
jump(j)使程序从当前要执行的代码处,直接跳转到指定位置处继续执行后续的代码。
print(p)打印指定变量的值。
quit(q)退出 GDB 调试器。

其中,next、step 以及 until 命令,已经在 《GDB单步调试》一节中做了详细介绍,本节不再赘述。另外,表 1 中罗列的这些命令读者无需死记硬背,因为实际使用 GDB 调试器时,它们都会常常用到,熟能生巧。

为了搞清楚表 1 中这些命令的功能和用法,这里仍以上节创建的 C 语言程序为例:

#include <stdio.h>
int print(int num){
int ret = num * num;
return ret;
}
int myfunc(int num){
int i = 1;
int sum = 0;
while(i <= num){
sum += print(i);
i++;
}
return sum;
}
int main(){
int num =0;
scanf(“%d”, &num);
int result = myfunc(num);
printf(“%d”, result);
return 0;
}

此程序存储在~/demo/main.c源文件中(~ 表示当前用户的主目录)。

  1. 首先,表 1 中 run、continue、list、next、print 以及 quit 命令的用法都非常简单,唯一需要注意的一点是,run 命令除了可以启动程序的执行,还可以在任何时候重新启动程序。

例如,以 main.c 为例:

[root@bogon demo]# gdb main.exe -q
Reading symbols from main.exe…
(gdb) l <-- list 命令的缩写,罗列出带有行号的源码
1 #include <stdio.h>
2 int print(int num){
3 int ret = num * num;
4 return ret;

15 int main(){
16 int num =0;
17 scanf(“%d”, &num);
18 int result = myfunc(num);
19 printf(“%d”, result);
20 return 0;
(gdb)
21 }
(gdb) b 16 <-- break 命令的缩写,在程序第 16 行处打普通断点
Breakpoint 1 at 0x11fa: file main.c, line 16.
(gdb) r <-- run 命令的缩写,启动程序
Starting program: /home/test/demo/main.exe
Breakpoint 1, main () at main.c:16
16 int num =0;
(gdb) next <-- 单步执行程序,即执行一行代码
17 scanf(“%d”, &num);
(gdb) next <-- 继续单步执行,此时需要输入一个整数传递给 num
3
18 int result = myfunc(num);
(gdb) p num <-- print 命令的缩写,显示 num 的值
$1 = 3 <-- $1 表示 num 变量所在存储区的名称,这里指的是 num 的值为 3
(gdb) n <-- 继续单步执行
19 printf(“%d”, result);
(gdb) n <-- 单步执行
20 return 0;
(gdb) q <-- 退出调试
A debugging session is active.

Inferior 1 [process 4576] will be killed.

Quit anyway? (y or n) y <-- 由于程序执行尚未结束,GDB 会进行再次确认
[root@bogon demo]#

事实上,以上很多命令还有其它的语法格式,只是不常用,这里不再过多赘述,感兴趣的读者可自行查阅 GDB 官网手册

接下来,重点给大家介绍表 1 中另外几个命令的用法。

GDB finish和return命令

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

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

仍以 main.exe 为例,如下演示了 finish 命令的用法:

(gdb) b 18
Breakpoint 1 at 0x4005ab: file main.c, line 18.
(gdb) r
Starting program: ~/demo/main.exe
3

Breakpoint 1, main () at main.c:18
18 int result = myfunc(num);
(gdb) step
myfunc (num=3) at main.c:7
7 int i = 1;
(gdb) n
8 int sum = 0;
(gdb) n
9 while(i <= num){
(gdb) finish
Run till exit from #0 myfunc (num=3) at main.c:9
0x00000000004005b5 in main () at main.c:18
18 int result = myfunc(num);
Value returned is $1 = 14
(gdb)

可以看到,当程序运行至第 9 行处使用 finish 命令,GDB 调试器会执行完 myfunc() 函数中的剩余代码,并在执行完函数后停止。接下来重新执行程序,观察 return 命令的功能:

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: ~/demo/main.exe
3

Breakpoint 1, main () at main.c:18
18 int result = myfunc(num);
(gdb) step
myfunc (num=3) at main.c:7
7 int i = 1;
(gdb) n
8 int sum = 0;
(gdb) n
9 while(i <= num){
(gdb) return 5
Make myfunc return now? (y or n) y
#0 0x00000000004005b5 in main () at main.c:18
18 int result = myfunc(num);
(gdb) n
19 printf(“%d”, result);
(gdb) p result
$3 = 5

可以看到,同样程序执行至第 9 行,借助 return 命令会立即终止执行 myfunc() 函数,同时手动指定该函数的返回值为 5。因此,最终 result 变量的值为 5,而不再是 14。

GDB jump命令

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

(gdb) jump location

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

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

举个例子:

(gdb) b 16
Breakpoint 1 at 0x40058b: file main.c, line 16.
(gdb) r
Starting program: ~/demo/main.exe

Breakpoint 1, main () at main.c:16
16 int num = 0;
(gdb) jump 19
Continuing at 0x4005b8.
0
Program exited normally.

可以看到,由于借助 jump 指令跳过了 result 变量的初始化过程,因此 result 变量的值为 0(或者垃圾值)。

注意,从第 16 行直接跳到第 19 行执行,并不意味着 result 变量不能使用。因为对于可执行文件而言,并不存在 num、result 这些变量名,它们都已经被转化为了地址(确定地说是偏移地址),并且程序在执行时,位于 main() 函数中的所有变量的存储地址都会被确定。也就是说,当我们跳到第 19 行输出 result 的值时,实际上是取其存储地址中的数据,只不过由于 result 没有初始化,所以最终结果值可能为 0,也可能为垃圾值。

GDB print和display命令:查看变量的值

前面章节中提到,使用 GDB 调试程序,最常用的方法是:单步调试或者断点调试程序,期间通过查看某个变量或者表达式的值,判断当前程序的执行过程是否正确,不断缩小异常或 Bug 位于代码中的范围,最终找到并修复。

对于在调试期间查看某个变量或表达式的值,GDB 调试器提供有 2 种方法,即使用 print 命令或者 display 命令。本节就对这 2 个命令的功能和用法做详细的讲解,整个讲解过程将以调试如下 C 语言程序为例:

#include <stdio.h>
int main(){
int num,result=0,i=0;
scanf(“%d”, &num);
while(i<=num){
result += i;
i++;
}
printf(“result=%d\n”, result);
return 0;
}

此程序存储在~/demo/main.c文件中(~ 代指当前系统登录用户的主目录),并且已经其编译为可供 GDB 调试的 main.exe 可执行文件:

[root@bogon demo]# gcc main.c -o main.exe -g
[root@bogon demo]# ls
main.c main.exe

GDB print命令

前面章节中,我们已经多次接触并使用了 print 命令,它的功能就是在 GDB 调试程序的过程中,输出或者修改指定变量或者表达式的值。

所谓表达式,简单理解就是由多个变量构成的式子。例如 a、b、c 为单个变量,a+b、a+b*c 即为表达式。

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

(gdb) print num
(gdb) p num

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

以调试 main.exe 为例:

[root@bogon demo]# gdb main.exe -q
Reading symbols from ~/demo/main.exe…done.
(gdb) l
1 #include <stdio.h>
2 int main(){
3 int num,result=0,i=0;
4 scanf(“%d”, &num);
5 while(i<=num){
6 result += i;
7 i++;
8 }
9 printf(“result=%d\n”, result);
10 return 0;
(gdb)
11 }
(gdb) b 3
Breakpoint 1 at 0x40053c: file main.c, line 3.
(gdb) r
Starting program: /root/demo/main.exe

Breakpoint 1, main () at main.c:4
4 scanf(“%d”, &num);
(gdb) n
3
5 while(i<=num){
(gdb) p num <–输出 num 的值
$1 = 3
(gdb) p num=4 <-- 修改 num 的值为 4
$2 = 4
(gdb) b 9
Breakpoint 2 at 0x400569: file main.c, line 9.
(gdb) c
Continuing.

Breakpoint 2, main () at main.c:9
9 printf(“result=%d\n”, result);
(gdb) p result <-- 输出 result 的值
$2 = 10
(gdb) p result=20 <-- 修改 result 的值
$2 = 20
(gdb) c
Continuing.
result=20

Program exited normally.
(gdb)

可以看到,调试 main.exe 的过程中,我们先后使用 p 命令输出了程序中 num 和 result 变量的值,同时还使用该命令对它们的值做了修改。

print 命令还有更高级的用法,后续会给读者做详细的讲解。

GDB display命令

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

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

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

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

其中,expr 表示要查看的目标变量或表达式;参数 fmt 用于指定输出变量或表达式的格式,表 1 罗列了常用的一些 fmt 参数。

/fmt功 能
/x以十六进制的形式打印出整数。
/d以有符号、十进制的形式打印出整数。
/u以无符号、十进制的形式打印出整数。
/o以八进制的形式打印出整数。
/t以二进制的形式打印出整数。
/f以浮点数的形式打印变量或表达式的值。
/c以字符形式打印变量或表达式的值。

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

仍以调试 main.exe 为例:

(gdb) b 4
Breakpoint 1 at 0x40053c: file main.c, line 4.
(gdb) b 9
Breakpoint 2 at 0x400569: file main.c, line 9.
(gdb) r
Starting program: /root/demo/main.exe

Breakpoint 1, main () at main.c:4
4 scanf(“%d”, &num);
(gdb) display num
1: num = 32767
(gdb) display/t result
2: /t result = 0
(gdb) n
3
5 while(i<=num){
2: /t result = 0
1: num = 3
(gdb) c
Continuing.

Breakpoint 2, main () at main.c:9
9 printf(“result=%d\n”, result);
2: /t result = 110
1: num = 3
(gdb) c
Continuing.
result=6

Program exited normally.
(gdb)

可以看到,使用 display 命令查看 num 和 result 变量值时,不仅在执行该命令的同时会看到目标变量的值,后续每次程序停止执行时,GDB 调试器都会将目标变量的值打印出来。

事实上,对于使用 display 命令查看的目标变量或表达式,都会被记录在一张列表(称为自动显示列表)中。通过执行info dispaly命令,可以打印出这张表:

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y /t result
1: y num

其中,各列的含义为:

  • Num 列为各变量或表达式的编号,GDB 调试器为每个变量或表达式都分配有唯一的编号;
  • Enb 列表示当前各个变量(表达式)是处于激活状态还是禁用状态,如果处于激活状态(用 y 表示),则每次程序停止执行,该变量的值都会被打印出来;反之,如果处于禁用状态(用 n 表示),则该变量(表达式)的值不会被打印。
  • Expression 列:表示查看的变量或表达式。

对于不需要再打印值的变量或表达式,可以将其删除或者禁用。

  1. 通过执行如下命令,即可删除自动显示列表中的变量或表达式:

(gdb) undisplay num…
(gdb) delete display num…

参数 num… 表示目标变量或表达式的编号,编号的个数可以是多个。

举个例子:

(gdb) undisplay 1
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y /t result
(gdb)

可以看到,借助 undisplay 命令成功删除了编号为 1 的 num 变量。

  1. 通过执行如下命令,可以禁用自动显示列表中处于激活状态下的变量或表达式:

(gdb) disable display num…

num… 表示要禁用的变量或表达式的编号,编号的个数可以是多个,表示一次性禁用多个变量或表达式

举个例子:

(gdb) disable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: n /t result
(gdb)

可以看到,编号为 2 的 result 变量的 Enb 由 y 变成了 n。处于禁用状态的变量或表达式,程序停止执行时将不再自动打印出它们的值。

当然根据需要,也可以激活当前处于禁用状态的变量或表达式,执行如下命令即可:

(gdb) enable display num…

参数 num… 表示要激活的变量或表达式的编号,编号的个数可以是多个,表示一次性激活多个变量或表达式。

举个例子:

(gdb) enable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2: y /t result
(gdb)

总的来说,每次程序停止执行时,GDB 调试器会将自动显示列表中处于激活状态下的变量或表达式的值打印出来,display 命令可以实现在查看目标变量或表达式的值的同时,将其添加到自动显示列表中,而 print 命令则只会打印出目标变量或表达式的值。

GDB禁用和删除断点

我们知道,GDB 调试器支持 3 种断点,分别为普通断点(用 break 命令创建)、观察断点(用 watch 命令建立)以及捕捉断点(用 catch 命令建立)。并且如果需要的话,我们可以在被调试程序中打多个断点,甚至于 GDB 允许在同一位置打多个断点。

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

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

本节教大家如何使用命令删除或者禁用之前已建好的断点,讲解过程中,将以调试如下 C 语言程序为例:

#include <stdio.h>
int main(){
int num = 0;
scanf(“%d”, &num);
printf(“%d”, num);
return 0;
}

程序存储在~/demo/main.c文件中,并已将其编译为可调试的 main.exe 可执行文件:

[root@bogon demo]# gcc main.c -o main.exe -g
[root@bogon demo]# ls
main.c main.exe

查看当前已建好的断点

对于当前调试环境中已经建好且尚未删除的断点,可以通过以下 2 种方式查看它们。

  1. 借助如下指令,可以查看当前调试环境中存在的所有断点,包括普通断点、观察断点以及捕捉断点:

(gdb) info breakpoint [n]
(gdb) info break [n]

参数 n 作为可选参数,为某个断点的编号,表示查看指定断点而非全部断点。

要知道,任何类型的断点在建立时,GDB 调试器都会为其分配一个独一无二的断点编号。以 main.exe 为例,我们尝试建立如下断点:

(gdb) b 1
Breakpoint 1 at 0x1189: file main.c, line 2.
(gdb) r
Starting program: ~/demo/main.exe

Breakpoint 1, main () at main.c:2
2 int main(){
(gdb) watch num
Hardware watchpoint 2: num
(gdb) catch throw int
Catchpoint 3 (throw)

可以看到,我们通过 break 命令建立了一个普通断点,其编号为 1;通过 watch 命令建立了一个观察断点,其编号为 2;通过 catch 命令建立了一个捕捉断点,其编号为 3。

在此基础上,可以通过执行 info break 或者 info breakpoint 命令,查看所有断点的具体信息:

(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555555189 in main at main.c:2 breakpoint already hit 1 time
2 hw watchpoint keep y num
3 catchpoint keep y exception throw matching: int
(gdb)

借助每个断点不同的编号,也可以进行精准查询:

(gdb) info break 1
Num Type Disp Enb Address What
1 breakpoint keep y 0x000000000040053c in main at main.c:2 breakpoint already hit 1 time

以上输出信息中各列的含义分别是:断点编号(Num)、断点类型(Type)、是临时断点还是永久断点(Disp)、目前是启用状态还是禁用状态(Enb)、断点的位置(Address)、断点当前的状态(作用的行号、已经命中的次数等,用 What 列表示)。

  1. 除此之外,对于调试环境中已经建好且未删除的观察断点,也可以使用 info watchpoint 命令进行查看,语法格式如下:

(gdb) info watchpoint [n]

n 为可选参数,为某个观察断点的编号,功能是只查看该编号的观察断点的信息,而不是全部的观察断点。

继续在上面的调试环境中,执行如下指令:

(gdb) info watchpoint
Num Type Disp Enb Address What
2 hw watchpoint keep y num
(gdb) info watchpoint 1
No watchpoint number 1.

由于当前环境中仅有 1 个观察断点,因此 info watchpoint 命令仅罗列了编号为 2 的观察断点的信息。需要注意的是,该命令仅能用于查看观察断点,普通断点和捕捉断点无法使用该命令。

GDB删除断点

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

1) clear命令

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

(gdb) clear location

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

在上面调试环境中,继续执行如下命令:

(gdb) clear 2

(gdb) info break
Deleted breakpoint 1
Num Type Disp Enb Address What
2 hw watchpoint keep y num
3 catchpoint keep y exception throw matching: int
(gdb)

可以看到,断点编号为 1、位于程序第 2 行代码处的普通断点已经被删除了。

2) delete 命令

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

delete [breakpoints] [num]

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

举个例子:

(gdb) delete 2
(gdb) info break
Num Type Disp Enb Address What
3 catchpoint keep y exception throw matching: int

可以看到,delete 命令删除了编号为 2 的观察断点。

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

(gdb) delete
Delete all breakpoints? (y or n) y
(gdb) info break
No breakpoints or watchpoints.

GDB禁用断点

所谓禁用,就是使目标断点暂时失去作用,必要时可以再将其激活,恢复断点原有的功能。

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

disable [breakpoints] [num…]

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

举个例子:

(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555555189 in main at main.c:2 breakpoint already hit 1 time
2 hw watchpoint keep y num
3 catchpoint keep y exception throw matching: int
(gdb) disable 1 2
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000555555555189 in main at main.c:2 breakpoint already hit 1 time
2 hw watchpoint keep n num
3 catchpoint keep y exception throw matching: int
(gdb)

可以看到,对于用 disable 命令禁用的断点,Enb 列用 n 表示其处于禁用状态,用 y 表示该断点处于激活状态。

对于禁用的断点,可以使用 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 命令可以同时激活多个断点。

仍以上面的调试环境为例,当下程序停止在第 2 行(main() 函数开头处),此时执行如下命令:

(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000555555555189 in main at main.c:2 breakpoint already hit 1 time
2 hw watchpoint keep n num
3 catchpoint keep y exception throw matching: int
(gdb) enable delete 2
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000555555555189 in main at main.c:2 breakpoint already hit 1 time
2 hw watchpoint del y num
3 catchpoint keep y exception throw matching: int
(gdb) c
Continuing.

Hardware watchpoint 2: num

Old value = 32767
New value = 0
main () at main.c:4
4 scanf(“%d”, &num);
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep n 0x0000555555555189 in main at main.c:2 breakpoint already hit 1 time
3 catchpoint keep y exception throw matching: int
(gdb)

可以看到,通过借助 enable delete 命令,我们激活了编号为 2 的观察断点,但其只能作用 1 次,因此当继续执行程序时,其会在程序第 4 行暂停,随时该断点会被删除。

如何用GDB调试多线程程序?

顾名思义,多线程程序指的就是拥有多个(≥2)线程的程序,各个线程可以独立完成一项任务,更重要的是它们可以并发执行。这也就意味着完成相同的任务,多线程程序执行花费的时间往往会少于单线程程序。

但有得也有失,多线程程序的编写更容易产生异常或 Bug(例如线程之间因竞争同一资源发生了死锁、多个线程同时对同一资源进行读和写等等),调试多线程程序,往往需要耗费更多的精力。

庆幸的是,GDB 调试器不仅仅支持调试单线程程序,还支持调试多线程程序。本质上讲,使用 GDB 调试多线程程序的过程和调试单线程程序类似,不同之处在于,调试多线程程序需要监控多个线程的执行过程,进而找到导致程序出现问题的异常或 Bug,而调试单线程程序只需要监控 1 个线程。

表 1 罗列了 GDB 调试多线程程序时常用的命令以及它们各自的功能。

调试命令功 能
info threads查看当前调试环境中包含多少个线程,并打印出各个线程的相关信息,包括线程编号(ID)、线程名称等。
thread id将线程编号为 id 的线程设置为当前线程。
thread apply id… commandid… 表示线程的编号;command 代指 GDB 命令,如 next、continue 等。整个命令的功能是将 command 命令作用于指定编号的线程。当然,如果想将 command 命令作用于所有线程,id… 可以用 all 代替。
break location thread id在 location 指定的位置建立普通断点,并且该断点仅用于暂停编号为 id 的线程。
set scheduler-locking offon

表 1 也仅罗列了 GDB 调试多线程程序的一部分常用命令,有关更多其他命令,读者可前往 GDB官网了解。

接下来,我将以调试如下 C 语言多线程程序为例,给大家详细讲解表 1 中这些命令的功能和用法:

#include <stdio.h>
#include <pthread.h>
void* thread_job(void*name)
{
char * thread_name = (char*)name;
printf(“this is %s\n”,thread_name);
printf(“http://c.biancheng.net\n”);
}
int main()
{
pthread_t tid1,tid2;
pthread_create(&tid1, NULL, thread_job, “thread1_job”);
pthread_create(&tid2, NULL, thread_job, “thread2_job”);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
printf(“this is main\n”);
return 0;
}

此程序的存储位置为 ~/demo/main.c。可以看到,此程序中包含 3 个线程,分别为 main 主线程、tid1 子线程和 tid2 子线程。

需要注意的是,将此程序编译为可供 GDB 调试的可执行程序时,需执行如下命令:

gcc main.c -o main.exe -g -lpthread

因为 pthread 线程库并不属于 Linux 系统中的默认库,所以编译、链接时就需要为 gcc 命令附加 -lpthread 参数。

GDB查看所有线程

info threads 命令的功能有 2 个,既可以查看当前调试环境下存在的线程数以及各线程的具体信息,也可以通过指定线程的编号查看某个线程的具体信息。

info threads 命令的完整语法格式如下:

(gdb) info threads [id…]

其中,参数 id… 作为可选参数,表示要查看的线程编号,编号个数可以是多个。

以 main.exe 程序为例:

(gdb) b 6
Breakpoint 1 at 0x11d9: file main.c, line 6.
(gdb) r
Starting program: ~/demo/main.exe
[Thread debugging using libthread_db enabled]
Using host libthread_db library “/lib/x86_64-linux-gnu/libthread_db.so.1”.
[New Thread 0x7ffff7d9f700 (LWP 54283)] <–新线程
[New Thread 0x7ffff759e700 (LWP 54284)] <–新线程
[Switching to Thread 0x7ffff7d9f700 (LWP 54283)] <–该线程作为当前线程,因为它最先碰到断点

Thread 2 “main.exe” hit Breakpoint 1, thread_job (name=0x555555556027) at main.c:6
6 char * thread_name = (char*)name;
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7da0740 (LWP 54279) “main.exe” __pthread_clockjoin_ex (threadid=140737351644928, thread_return=0x0, clockid=, abstime=, block=) at pthread_join_common.c:145
* 2 Thread 0x7ffff7d9f700 (LWP 54283) “main.exe” thread_job (name=0x555555556027) at main.c:6
3 Thread 0x7ffff759e700 (LWP 54284) “main.exe” thread_job (name=0x555555556033) at main.c:6

其中 Id 列表示各个线程的编号(ID 号);Target Id 列表示各个线程的标识符;Frame 列打印各个线程执行的有关信息,例如线程名称,线程暂停的具体位置等。

要知道,使用 GDB 调试多线程程序时,同一时刻我们调试的焦点都只能是某个线程,被称为当前线程。整个调试过程中,GDB 调试器总是会从当前线程的角度为我们打印调试信息。如上所示,当执行 r 启动程序后,GDB 编译器自行选择标识号为 LWP 54283(编号为 2)的线程作为当前线程,则随后打印的暂停运行的信息就与该线程有关,而没有打印出编号为 1 和 3 的暂停信息。

GDB 调试器为了方便用户快速识别出当前线程,执行 info thread 命令后,Id 列前标有 * 号的线程即为当前线程。

这里要提醒大家的,我们输入的调试命令并不仅仅作用于当前线程,例如 continue、next 等,默认情况下它们作用于所有线程。

GDB调整当前线程

用 GDB 调试多线程程序的过程中,根据需要可以随时对当前线程进行调整,这就需要用到 thead id 命令。

thread id 命令用于将编号为 id 的线程设定为当前线程。举个例子:

(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff759e700 (LWP 54284))] <–切换当前线程
#0 thread_job (name=0x555555556033) at main.c:6
6 char * thread_name = (char*)name;
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7da0740 (LWP 54279) “main.exe” __pthread_clockjoin_ex (threadid=140737351644928, thread_return=0x0, clockid=, abstime=, block=) at pthread_join_common.c:145
2 Thread 0x7ffff7d9f700 (LWP 54283) “main.exe” thread_job (name=0x555555556027) at main.c:6
* 3 Thread 0x7ffff759e700 (LWP 54284) “main.exe” thread_job (name=0x555555556033) at main.c:6

可以看到,改变当前线程的同时,GDB 调试器为我们打印出了该线程暂停执行的具体信息。再次执行 info threads 命令可以看到,编号为 3 的线程确实成为了新的当前线程。

GDB执行特定线程

如果想单独控制某一线程进行指定的操作,可以借助 thread apply id… command 命令实现:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
83)] <–该线程作为当前线程,因为它最先碰到断点

Thread 2 “main.exe” hit Breakpoint 1, thread_job (name=0x555555556027) at main.c:6
6 char * thread_name = (char*)name;
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7da0740 (LWP 54279) “main.exe” __pthread_clockjoin_ex (threadid=140737351644928, thread_return=0x0, clockid=, abstime=, block=) at pthread_join_common.c:145
* 2 Thread 0x7ffff7d9f700 (LWP 54283) “main.exe” thread_job (name=0x555555556027) at main.c:6
3 Thread 0x7ffff759e700 (LWP 54284) “main.exe” thread_job (name=0x555555556033) at main.c:6

其中 Id 列表示各个线程的编号(ID 号);Target Id 列表示各个线程的标识符;Frame 列打印各个线程执行的有关信息,例如线程名称,线程暂停的具体位置等。

要知道,使用 GDB 调试多线程程序时,同一时刻我们调试的焦点都只能是某个线程,被称为当前线程。整个调试过程中,GDB 调试器总是会从当前线程的角度为我们打印调试信息。如上所示,当执行 r 启动程序后,GDB 编译器自行选择标识号为 LWP 54283(编号为 2)的线程作为当前线程,则随后打印的暂停运行的信息就与该线程有关,而没有打印出编号为 1 和 3 的暂停信息。

GDB 调试器为了方便用户快速识别出当前线程,执行 info thread 命令后,Id 列前标有 * 号的线程即为当前线程。

这里要提醒大家的,我们输入的调试命令并不仅仅作用于当前线程,例如 continue、next 等,默认情况下它们作用于所有线程。

GDB调整当前线程

用 GDB 调试多线程程序的过程中,根据需要可以随时对当前线程进行调整,这就需要用到 thead id 命令。

thread id 命令用于将编号为 id 的线程设定为当前线程。举个例子:

(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff759e700 (LWP 54284))] <–切换当前线程
#0 thread_job (name=0x555555556033) at main.c:6
6 char * thread_name = (char*)name;
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7da0740 (LWP 54279) “main.exe” __pthread_clockjoin_ex (threadid=140737351644928, thread_return=0x0, clockid=, abstime=, block=) at pthread_join_common.c:145
2 Thread 0x7ffff7d9f700 (LWP 54283) “main.exe” thread_job (name=0x555555556027) at main.c:6
* 3 Thread 0x7ffff759e700 (LWP 54284) “main.exe” thread_job (name=0x555555556033) at main.c:6

可以看到,改变当前线程的同时,GDB 调试器为我们打印出了该线程暂停执行的具体信息。再次执行 info threads 命令可以看到,编号为 3 的线程确实成为了新的当前线程。

GDB执行特定线程

如果想单独控制某一线程进行指定的操作,可以借助 thread apply id… command 命令实现:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-6FO3v4nY-1713117543575)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值