gdb使用总结(中)

[复制链接]
查看771 | 回复6 | 2023-9-7 20:52:51 | 显示全部楼层 |阅读模式

gdb断点是调试的重要手段,常用的断点有一段断点(break),观察断点(watch)、捕捉断点(catch)和条件断点(condition)。除了这些断点之外,还有单步调试等手段常用来做调试。

gdb设置断点-高级应用

在上一节中有提到简单的断点设置方法,即直接通过break number 设置断点! 本节将在此基础上深入研究。

同样的还是基于一段简单的程序进行学习!

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

并编译成可调试的可执行程序:gcc -g main.c -o main.out

image-20230331163516574

通过main.out 进入gdb调试器

image-20230331163911347

break常用命令

(gdb) break location    // 在location位置暂停运行
(gdb) break location if <condition>    // 如果条件成立, 在location位置暂停运行

上述location参数列表如下:

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 函数的开头位置打断点。

接下来看看实际的使用:

调试的时候首先用list看看程序的源代码,一来可以判断file有没有加载进来,而来可以了解整体程序结构:

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

然后通过打断点查看程序运行状态

(gdb) break 4    <---- 在第四行设置断点
Breakpoint 1 at 0x1144: file main.c, line 4.
(gdb) run    <---- 开始运行,在第一个断点处停止
Starting program: /root/c/c.biancheng.com/gdb_demo/break_demo/main.out 

Breakpoint 1, main (argc=1, argv=0x7fffffffeb68) at main.c:4
4       int num = 1;
(gdb) break +1    <---- 在当前断点的基础上+1 设置断点(4+1)
Breakpoint 2 at 0x55555555514b: file main.c, line 5.
(gdb) continue    <---- 继续运行,下一个断点停止
Continuing.

Breakpoint 2, main (argc=1, argv=0x7fffffffeb68) at main.c:5
5       while(num<100)
(gdb) break 7 if num > 10    <---- 当num大于10 的时候在第7行设置断点
Breakpoint 3 at 0x55555555514d: file main.c, line 7.
(gdb) continue    <---- 继续运行,下一个断点停止
Continuing.

Breakpoint 3, main (argc=1, argv=0x7fffffffeb68) at main.c:7
7           num *= 2;
(gdb) print num    <---- 打印此时num的值,确实大于10了
$1 = 16
(gdb)

gdb tbreak命令

tbreak命令是对break命令的封装,与break命令不同的是,tbreak打出的断点是一次性的,也就是暂停运行一次后,此断点消失,这个断点常用在循环中。来看看使用:

基于上面break 7 if num > 10 的断点继续看,我们先在此基础上continue,继续运行看看:

(gdb) break 7 if num > 10
Breakpoint 3 at 0x55555555514d: file main.c, line 7.
(gdb) continue
Continuing.

Breakpoint 3, main (argc=1, argv=0x7fffffffeb68) at main.c:7
7           num *= 2;
(gdb) print num
$3 = 16
(gdb) continue    <---- 在上面实验的基础上continue,继续运行
Continuing.

Breakpoint 3, main (argc=1, argv=0x7fffffffeb68) at main.c:7
7           num *= 2;    <---- 断点停止
(gdb) print num    <---- 打印num的值
$4 = 32
(gdb)

然后我们同样的情况使用tbreak 打个断点看看

(gdb) tbreak 7 if num > 10    <---- 使用tbreak
Temporary breakpoint 1 at 0x114d: file main.c, line 7.
(gdb) run
Starting program: /root/c/c.biancheng.com/gdb_demo/break_demo/main.out 

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffeb68) at main.c:7
7           num *= 2;    <---- 断点停止
(gdb) print num    <---- 首次打印num的值
$1 = 16
(gdb) continue    <---- 继续运行
Continuing.
num=128[Inferior 1 (process 37219) exited normally]    <---- 程序直接结束了!num的值是128
(gdb)

通过上面的实验发现,tbreak打的断点只生效了一次。

gdb rbreak命令

rbreak命令也是基于break的封装,其作用是,只会在指定的函数处打断点,并且断点一直生效,语法格式为:

rbreak regex regex表示正则表达式,也就是说,我们不需要精准的输入完整的函数名,只要正则能匹配就可以打断点。

很好理解,懒得写函数了!

有时候需要持续监控某一个变量的变化,这个时候打break这种断点很显然已经不合适了,所以gdb还提供了其他的方式。gdb共有三种断点方式:break(普通断点)、观察断点和捕捉断点。接下来我们看看观察断点:

gdb设置观察断点-watch

watch命令的功能是:只有当被监控变量发生变化的时候,程序才会暂停。和 watch 命令功能相似的,还有 rwatch 和 awatch 命令

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

还是以之前break的程序作为例子来学习watch

(gdb) break 5
Breakpoint 1 at 0x114b: file main.c, line 5.
(gdb) run
Starting program: /root/c/c.biancheng.com/gdb_demo/break_demo/main.out 

Breakpoint 1, main (argc=1, argv=0x7fffffffeb68) at main.c:5
5       while(num<100)
(gdb) print num
$1 = 1    <---- 此时num的值是1
(gdb) watch num    <---- 使用watch为num打观察断点
Hardware watchpoint 2: num
(gdb) continue    <---- 继续运行
Continuing.

Hardware watchpoint 2: num

Old value = 1
New value = 2    <---- num 值发生了改变
main (argc=1, argv=0x7fffffffeb68) at main.c:5
5       while(num<100)        <---- 暂停了
(gdb) continue    <---- 继续运行
Continuing.

Hardware watchpoint 2: num

Old value = 2
New value = 4    <---- num 值再次发生了改变
main (argc=1, argv=0x7fffffffeb68) at main.c:5
5       while(num<100)        <---- 暂停了
(gdb)

上面的实验说明,watch监控的变量值一旦发生变化,程序立刻暂停。

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

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

gdb设置捕捉断点-catch

捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行,其语法为:

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

注意,当前 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 命令才会捕获并使程序暂停,之后将失效。

使用下面这个程序看看catch的使用:

#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;
}
// 编译
g++ -g main.cpp -o main.out
root@klelee:~/c/c.biancheng.com/gdb_demo/catch_demo# gdb main.out -q
Reading symbols from main.out...
(gdb) catch throw int    <---- 指定捕获“throw int”事件
Catchpoint 1 (throw)
(gdb) run    <---- 开始运行
Starting program: /root/c/c.biancheng.com/gdb_demo/catch_demo/main.out 

Catchpoint 1 (exception thrown), 0x00007ffff7e98c2e in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6    <---- 异常停止
(gdb) up    <---- 查看上一条执行的代码
#1  0x00005555555551ef in main () at main.cpp:7
7               throw 100;
(gdb) continue    <---- 继续执行
Continuing.
next

Catchpoint 1 (exception thrown), 0x00007ffff7e98c2e in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6    <---- 异常停止
(gdb) up    <---- 查看上一条执行的代码
#1  0x00005555555551ef in main () at main.cpp:7
7               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)

<留坑,准备写load选项>

gdb设置条件断点

条件断点在之前的break 断点中已经接触过了,就是当条件触发的时候在指定位置设置断点。现在要讲的是,给指定的断点设置条件。语法如下:

(gdb) condition bnum expression
(gdb) condition bnum

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

以下列程序为例来看看具体的使用方式:

#include <stdio.h>
int main ()
{
    unsigned long long int n, sum;
    n = 1;
    sum = 0;
    while (n <= 100)
    {
        sum = sum + n;
        n = n + 1;
    }
    return 0;
}

对上面的程序进行编译调试,如下:

(gdb) list
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;
11          }
12          return 0;
13      }
(gdb) break 9 if sum=10    <---- 如果sum等于10,在第9行暂停运行
Note: breakpoint 1 also set at pc 0x55555555513b.
Breakpoint 2 at 0x55555555513b: file main.c, line 9.
(gdb) run    <---- 开始运行,第一个断点处暂停
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/klelee/github/c/c.biancheng.com/gdb_demo/main.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 1, main () at main.c:9
9               sum = sum + n;    <---- 暂停了
(gdb) print sum    <---- 打印sum的值,确认是我们设置的
$2 = 10
(gdb) info break    <---- 查看当前存在的断点
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000055555555513b in main at main.c:9
        stop only if sum = 1
        breakpoint already hit 1 time
2       breakpoint     keep y   0x000055555555513b in main at main.c:9
        stop only if sum=10
        breakpoint already hit 1 time
(gdb) condition 2 sum=21    <---- 我们将第二个断点的条件改成sum=21
(gdb) continue    <---- 继续运行
Continuing.

Breakpoint 1, main () at main.c:9
9               sum = sum + n;    <---- 暂停了
(gdb) print sum    <---- 打印sum的值,确认是我们设置的
$3 = 21
(gdb) 

以上就是condition命令的一个使用方法。

gdb单步调试程序

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

用下面的例子来学习单步调试:

#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;
}

next命令

特点是对于函数,next只是会把它当成一行指令。来看看具体使用吧:

(gdb) list
1       #include <stdio.h>
2       int print(int num){
3           int ret = num * num;
4           return ret;
5       }
6       int myfunc(int num){
7           int i = 1;
8           int sum = 0;
9           while(i <= num){
10              sum += print(i);
(gdb) 
11              i++;
12          }
13          return sum;
14      }
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) 
Line number 22 out of range; main.c has 21 lines.
(gdb) break 16   <---- 在16行打一个普通断点
Breakpoint 1 at 0x11be: file main.c, line 16.
(gdb) run
Starting program: /home/klelee/github/c/c.biancheng.com/gdb_demo/step_demo/main.out 

...

Breakpoint 1, main () at main.c:16
16          int num =0;    <---- 在此处停止
(gdb) next    <---- 下一步是一个scanf语句,我们看一下next之后的效果
17          scanf("%d", &num);    <---- next之后并没有让我们输入,也就是说下一步,但不执行
(gdb) next    <---- 继续next
3    <---- 这里响应scanf了
18          int result = myfunc(num);
(gdb) next
19          printf("%d", result);
(gdb) next    <---- 这个next直接到return语句,表明next不会进入到print函数内部去单步调试
20          return 0;
(gdb)

从上面的例子可以看出来next语句,会把函数当成一个语句,一次性执行!

step命令

step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行。

until命令

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

(gdb) until
(gdb) until location

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

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

断点禁用和删除

在程序调试过程中使用break打出来的断点,我们之前说到是永久有效的。假如说在某个程序的循环结构中打的断点,我们不需要了,那就需要考虑进行删除操作,或者禁用掉。

在删除或禁用之前需要先查看当前程序中有的断点。

info命令查看断点

(gdb) list
2   int print(int num){
3       int ret = num * num;
4       return ret;
5   }
6   int myfunc(int num){
7       int i = 1;
8       int sum = 0;
9       while(i <= num){
10          sum += print(i);
11          i++;
(gdb) 
12      }
13      return sum;
14  }
15  int main(){
16      int num =0;
17      scanf("%d", &num);
18      int result = myfunc(num);
19      printf("%d", result);
20      return 0;
21  }
(gdb) break 11    <---- 打第一个断点
Breakpoint 1 at 0x1182: file main.c, line 11.    <---- 断点类型为:breakpoint 编号为1
(gdb) break 16
Breakpoint 2 at 0x119b: file main.c, line 16.    <---- 断点类型为:breakpoint 编号为2
(gdb) run  
Starting program: /root/c/c.biancheng.com/gdb_demo/main.out 

Breakpoint 2, main () at main.c:16
16      int num =0;
(gdb) continue
Continuing.
3

Breakpoint 1, myfunc (num=3) at main.c:11
11          i++;
(gdb) continue
Continuing.

Breakpoint 1, myfunc (num=3) at main.c:11
11          i++;
(gdb) info break    <----  查看现在存在的断点
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555555182 in myfunc at main.c:11
    breakpoint already hit 2 times
2       breakpoint     keep y   0x000055555555519b in main at main.c:16
    breakpoint already hit 1 time
(gdb)

可能当我们了解当前阶段的运行状态之后需要删除某些断点,或者断点行数设置错误,就需要删除一些断点了。删除断点通常有两种方法,分别是clear命令和delete命令。

删除断点命令——clear和delete

  • clear:删除指定点的所有断点
  • delete:删除所有断点,或指定点的断点
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555555182 in myfunc at main.c:11
    breakpoint already hit 3 times
2       breakpoint     keep y   0x000055555555519b in main at main.c:16
    breakpoint already hit 1 time
(gdb) delete 2    <---- 删除断点编号为2的断点
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000555555555182 in myfunc at main.c:11
    breakpoint already hit 3 times
(gdb) clear 11    <---- 删除在第11行的所有断点
Deleted breakpoint 1 
(gdb)

禁用断点命令——disable

禁用和删除的区别就在于断点还在不在。其常用语法为:

(gdb) disable    # 禁用所有断点
(gdb) disable num1 num2 ...    # 禁用指定的断点

如下:

(gdb) list
14  }
15  int main(){
16      int num =0;
17      scanf("%d", &num);
18      int result = myfunc(num);
19      printf("%d", result);
20      return 0;
21  }
(gdb) 
Line number 22 out of range; main.c has 21 lines.
(gdb) break 16    <---- 设置一个普通断点
Breakpoint 3 at 0x55555555519b: file main.c, line 16.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/c/c.biancheng.com/gdb_demo/main.out 

Breakpoint 3, main () at main.c:16
16      int num =0;
(gdb) watch num    <---- 设置一个观察断点
Hardware watchpoint 4: num
(gdb) info break    <---- 查看所有断点
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x000055555555519b in main at main.c:16    <---- 此时断点状态为y
    breakpoint already hit 1 time
4       hw watchpoint  keep y                      num  
(gdb) disable 3    <---- 禁用第三个断点
(gdb) info break
Num     Type           Disp Enb Address            What
3       breakpoint     keep n   0x000055555555519b in main at main.c:16    <---- 断点还在,只是状态为n
    breakpoint already hit 1 time
4       hw watchpoint  keep y                      num
(gdb)

打开已禁用的断点——enable

既然禁用断点让其保存,那就要有重新使用的方法,那就是enable,其语法也和disable相对应

(gdb) disable
(gdb) info break
Num     Type           Disp Enb Address            What
3       breakpoint     keep n   0x000055555555519b in main at main.c:16
    breakpoint already hit 1 time
4       hw watchpoint  keep n                      num
(gdb) enable
(gdb) info break
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x000055555555519b in main at main.c:16
    breakpoint already hit 1 time
4       hw watchpoint  keep y                      num
(gdb) disable 4
(gdb) info break
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x000055555555519b in main at main.c:16
    breakpoint already hit 1 time
4       hw watchpoint  keep n                      num
(gdb) enable 4
(gdb) info break
Num     Type           Disp Enb Address            What
3       breakpoint     keep y   0x000055555555519b in main at main.c:16
    breakpoint already hit 1 time
4       hw watchpoint  keep y                      num
(gdb)
回复

使用道具 举报

可乐klelee | 2023-9-7 20:54:26 | 显示全部楼层
随便写写第二天
回复 支持 反对

使用道具 举报

ai_mcu | 2023-9-7 21:11:40 | 显示全部楼层
抢个板凳
明天总会更好
回复

使用道具 举报

ai_mcu | 2023-9-7 21:13:11 | 显示全部楼层

有一说一,你这随便写写含金量不少
明天总会更好
回复 支持 反对

使用道具 举报

爱笑 | 2023-9-7 21:35:03 来自手机 | 显示全部楼层
地板!
用心做好保姆工作
回复

使用道具 举报

干簧管 | 2023-11-24 19:04:33 | 显示全部楼层
回复

使用道具 举报

干簧管 | 2023-11-25 19:12:15 | 显示全部楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则