2024年最全C C++调试总结:IDE(visual studio和Dev C+,2024年最新看完直接怼项目经理

img
img

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

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

如果你需要这些资料,可以戳这里获取

注意,该指令在启动 GDB 的同时,会打印出一堆免责条款。通过添加 --silent(或者 -q、–quiet)选项,可将比部分信息屏蔽掉:

[root@bogon demo]# gdb main.exe --silent
Reading symbols from main.exe...(no debugging symbols found)...done.
(gdb) 

无论使用以上哪种方式,最终都可以启动 GDB 调试器,启动成功的标志就是最终输出的 (gdb)。通过在 (gdb) 后面输入指令,即可调用 GDB 调试进行对应的调试工作。

GDB 调试器提供有大量的调试选项,可满足大部分场景中调试代码的需要。如表 1 所示,罗列了几个最常用的调试指令及各自的作用:

调试指令作 用
(gdb) break xxx (gdb) b xxx在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置。
(gdb) run (gdb) r执行被调试的程序,其会自动在第一个断点处暂停执行。
(gdb) continue (gdb) c当程序在某一断点处停止运行后,使用该指令可以继续执行,直至遇到下一个断点或者程序结束。
(gdb) next (gdb) n令程序一行代码一行代码的执行。
(gdb) print xxx (gdb) p xxx打印指定变量的值,其中 xxx 指的就是某一变量名。
(gdb) list (gdb) l显示源程序代码的内容,包括各行代码所在的行号。
(gdb) quit (gdb) q终止调试。

如上所示,每一个指令既可以使用全拼,也可以使用其首字母表示。另外,表 1 中罗列的指令仅是冰山一角,GDB 还提供有大量的选项,可以通过 help 选项来查看。有关 help 选项的具体用法,读者可阅读《GDB查看命令》一节,这里不再做具体赘述。

仍以 main.exe 可执行程序为例,接下来为读者演示表 1 中部分选项的功能和用法:

(gdb) l           <-- 显示带行号的源代码
1 #include <stdio.h>
2 int main ()
3 {
4   unsigned long long int n, sum;
5   n = 1;
6   sum = 0;
7   while (n <= 100)
8   {
9     sum = sum + n;
10     n = n + 1;
(gdb)           <-- 默认情况下,l 选项只显示 10 行源代码,如果查看后续代码,安装 Enter 回车即可                                
11   }
12   return 0;
13 }
(gdb) b 7        <-- 在第 7 行源代码处打断点
Breakpoint 1 at 0x400504: file main.c, line 7.
(gdb) r          <-- 运行程序,遇到断点停止
Starting program: /home/mozhiyan/demo1/main.exe

Breakpoint 1, main () at main.c:7
7   while (n <= 100)
Missing separate debuginfos, use: debuginfo-install glibc-2.17-55.el7.x86_64
(gdb) p n        <-- 查看代码中变量 n 的值
$1 = 1          <-- 当前 n 的值为 1,$1 表示该变量所在存储区的名称
(gdb) b 12       <-- 在程序第 12 行处打断点
Breakpoint 2 at 0x40051a: file main.c, line 12.
(gdb) c         <-- 继续执行程序
Continuing.

Breakpoint 2, main () at main.c:12
12   return 0;
(gdb) p n        <-- 查看当前 n 变量的值
$2 = 101        <-- 当前 n 的值为 101
(gdb) q         <-- 退出调试
A debugging session is active.

Inferior 1 [process 3080] will be killed.

Quit anyway? (y or n) y         <-- 确实是否退出调试,y 为退出,n 为不退出
[root@bogon demo]#

后续章节会对以上指令做详细的讲解,这里简单了解即可,不必深究。

调用GDB调试器的4种方式

GDB调试C/C++程序》一节演示了用 GDB 调试 C(或者 C++)程序的整个过程,其中对 main.exe 文件启动 GDB 调试,执行的指令为:

[root@bogon demo]# gdb main.exe
GNU gdb (GDB) 8.0.1
Copyright © 2017 Free Software Foundation, Inc.

(gdb)

要知道,这仅是调用 GDB 调试器最常用的一种方式,GDB 调试器还有其它的启动方式。并且,为了满足不同场景的需要,启动 GDB 调试器时还可以使用一些参数选项,从而控制它启动哪些服务或者不启动哪些服务。

调用GDB的方式

总的来说,调用 GDB 调试器的方法有 4 种。

  1. 直接使用 gdb 指令启动 GDB 调试器:

[root@bogon demo]# gdb
ubuntu64@ubuntu64-virtual-machine:~/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 调试器,由于事先未指定要调试的具体程序,因此需启动后借助 file 或者 exec-file 命令指定(后续章节会做详细讲解)。

2) 调试尚未执行的程序

对于具备调试信息(使用 -g 选项编译而成)的可执行文件,调用 GDB 调试器的指令格式为:

gdb program

其中,program 为可执行文件的文件名,例如上节创建好的 main.exe。

3) 调试正在执行的程序

在某些情况下,我们可能想调试一个当前已经启动的程序,但又不想重启该程序,就可以借助 GDB 调试器实现。

也就是说,GDB 可以调试正在运行的 C、C++ 程序。要知道,每个 C 或者 C++ 程序执行时,操作系统会使用 1 个(甚至多个)进程来运行它,并且为了方便管理当前系统中运行的诸多进程,每个进程都配有唯一的进程号(PID)。

如果需要使用 GDB 调试正在运行的 C、C++ 程序,需要事先找到该程序运行所对应的进程号。查找方式很简单,执行如下命令即可:

pidof 文件名

比如,我们将上节创建的 main.c 源文件修改为:

#include <stdio.h>
int main()
{
    int num = 1;
    while(1)
    {
        num++;
    }
    return 0;
}

执行 gcc main.c -o main.exe -g 编译指令,获得该源程序对应的具备调试信息的 main.exe 可执行文件,并在此基础上执行:

[root@bogon demo]# ./main.exe
<–程序一直运行

显然,程序中存在死循环(5~8 行),它会一直执行。此时,借助 pidof 指令即可获取它对应的进程号:

[root@bogon demo]# pidof main.exe
1830

可以看到,当前正在执行的 main.exe 对应的进程号为 1830。在此基础上,可以调用 GDB 对该程序进行调试,调用指令有以下 3 种形式:

  1. gdb attach PID
  2. gdb 文件名 PID
  3. gdb -p PID

其中,PID 指的就是要调取的程序对应的进程号。

以调试进程号为 1830 的程序为例,执行如下指令:

[root@bogon demo]# gdb -p 1830
GNU gdb (GDB) 8.0.1
Copyright © 2017 Free Software Foundation, Inc.
… <-- 省略部分输出信息
0x00005645319c613c in main () at main.c:6
6 num++;

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

(gdb) l <-- 查看源码以及各行行号
1 #include<stdio.h>
2 int main()
3 {
4 int num = 1;
5 while(1){
6 num++;
7 }
8 return 0;
9 }
(gdb) b 6 <–在程序第 6 行代码处打断点
Breakpoint 1 at 0x5645319c6138: file main.c, line 6.
(gdb) c <–令程序进行执行,其会在下一个断点处停止
Continuing.

Breakpoint 1, main () at main.c:6
6 num++;
(gdb) p num <-- 查看当前 num 的值
$2 = 47100335

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

  1. 执行 detach 指令,使 GDB 调试器和程序分离;
  2. 执行 quit(或 q)指令,退出 GDB 调试。
4) 调试执行异常崩溃的程序

除了以上 3 种情况外,C 或者 C++ 程序运行过程中常常会因为各种异常或者 Bug 而崩溃,比如内存访问越界(例如数组下标越界、输出字符串时该字符串没有 \0 结束符等)、非法使用空指针等,此时就需要调试程序。

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

默认情况下,Linux 系统是不开启 core dump 这一功能的,读者可以借助执行ulimit -c指令来查看当前系统是否开启此功能:

[root@bogon demo]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited

其中,如果 core file size(core 文件大小)对应的值为 0,表示当前系统未开启 core dump 功能。这种情况下,可以通过执行如下指令改变 core 文件的大小:

[root@bogon demo]# ulimit -c unlimited
[root@bogon demo]# ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited

其中,unlimited 表示不限制 core 文件的大小。

由此,当程序执行发生异常崩溃时,系统就可以自动生成相应的 core 文件。

举个例子,修改 main.c 源程序文件中的代码为:

#include <stdio.h>
int main()
{
    char \*p = NULL;
    \*p = 123;
    return 0;
}

重新编译,即执行如下指令:

[root@bogon demo]# gcc main.c -o main.exe -g
[root@bogon demo]# ./main.exe
Segmentation fault (core dumped) <–发生段错误,并生成了 core 文件
[root@bogon demo]# ls
core main.c main.exe

段错误又称为访问权限冲突,指的是当前程序访问了不可访问的存储空间,比如访问的不存在的空间,又或者是受系统保护的内存空间。

观察此程序不难发现,由于 p 指针初始化为 NULL,即不指向任何存储空间,但后续却执行*p=123操作,显然是不可行的。因此,该程序执行时会发生崩溃,Linux 系统会记录必要的崩溃信息,并存储到 core 文件中。

默认情况下,core 文件的生成位置同该程序所在的目录相同。当然我们也可以指定 core 文件的生成的位置,感兴趣的读者可自行研究,这里不再介绍。

对于 core 文件的调试,其调用 GDB 调试器的指令为:

[root@bogon demo]# gdb main.exe core
GNU gdb (GDB) 8.0.1
Copyright © 2017 Free Software Foundation, Inc.

Reading symbols from main.exe…
[New LWP 4296]

warning: Unexpected size of section .reg-xstate/4296' in core file. Core was generated by./main.exe’.
Program terminated with signal SIGSEGV, Segmentation fault.

warning: Unexpected size of section `.reg-xstate/4296’ in core file.
#0 0x00005583b933013d in main () at main.c:5
5 *p = 123;

可以看到,程序发生崩溃的位置是在 main.c 中的第 5 行。甚至于,对于 core 文件中记录的崩溃信息,可以使用 where、print、bt 等指令查看,有关这些指令的功能和用法,由于并非本节重点,这里不再具体赘述,后续章节会做详细讲解。

GDB调试器启动可用参数

表 1 罗列了一些在启动 GDB 调试器时常用的指令参数,以及它们各自的功能。

参 数功 能
-pid number -p number调试进程 ID 为 number 的程序。
-symbols file -s file仅从指定 file 文件中读取符号表。
-q -quiet -silent取消启动 GDB 调试器时打印的介绍信息和版权信息
-cd directory以 directory 作为启动 GDB 调试器的工作目录,而非当前所在目录。
–args 参数1 参数2…向可执行文件传递执行所需要的参数。

其中有些参数,我们已经在前面的学习给大家做了具体的演示,这里不再重复赘述,读者可自行尝试使用。除此之外,启动 GDB 调试器时还有其它参数指令可以使用,感兴趣的读者可查阅 GDB 官网做系统了解。有关表 1 以及 GDB 调试器支持的其它指令,后续章节用到时会做详细讲解。

gdb run(r)命令:启动程序

使用 GDB 调试器调试程序的过程,其实就是借助 GDB 调试器来监控程序的执行流程,进而发现程序中导致异常或者 Bug 的代码。通过前面章节的学习,读者已经学会了如何启动 GDB 调试器,在此基础上,本节继续为大家讲解如何在 GDB 调试器中启动(运行)程序,以及启动程序过程中的一些注意事项。

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

  • 默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处;
  • start 指令会执行程序至 main() 主函数的起始位置,即在 main() 函数的第一行语句处停止执行(该行代码尚未执行)。

可以这样理解,使用 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 <iostream>
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 <iostream>
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 <iostream>
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 num==3       <-- 为普通断点添加条件表达式
(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


![img](https://img-blog.csdnimg.cn/img_convert/425f7bfc9d65c364d9432f01aae086c1.png)
![img](https://img-blog.csdnimg.cn/img_convert/f93fe8e8e56c27193f6cd912a6af3c75.png)

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

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

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

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


[外链图片转存中...(img-iLBoqNiF-1715597529134)]
[外链图片转存中...(img-shCad5L3-1715597529134)]

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

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

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值