gdb调试0基础教程

发布于:2025-05-10 ⋅ 阅读:(9) ⋅ 点赞:(0)

调试

我会先介绍一遍基础的调试知识,随后用一个精心准备代码示例来调试找到问题所在,尽可能用到所有常用的调试命令,希望你能跟着我说的尝试一遍,相信完成之后你会稍微体会到gdb的强大之处

编译

想要能够调试必须让编译携带调试信息,需要带上-g命令

g++ main.cpp -o main -Wall -g  # Wall是用于输出警告信息建议加上,想要调试一定要用gdb

如果没有安装则安装一下gdb

sudo apt install gdb

开始调试

使用如下命令进入调试

gdb main #main为可执行程序

退出使用

quit

调试常用命令

我会在这里给你列举一下调试常用的命令,不做示例讲解,随后在进行示例演示,以便于日后你查看(你可以使用简写也能使用括号里的全称)

  1. r(run)运行程序,或者是重新运行
  2. q(quit)退出gdb
  3. b(break 打断点
  4. n(next)一行行运行逐过程,不会进入函数体
  5. s(step) 单步运行,会进入函数体
  6. fin(finish)类似与vscode的单步跳出,你可以跳出函数
  7. c(continue)继续运行程序直到他停住或者终止
  8. i(info) 查看信息 i b查看所有断点信息 i locals查看所有局部变量
  9. l(ls)下翻文件,显示10行
  10. d(delete)删除断点 d 1删除1号断点,如果不指定则直接删除所有
  11. ig(ignorance)忽略断点 例如ig 1 10忽略一号断点十次
  12. p(print)打印变量值 p x打印x的值 也可以修改表达式的值 p x =10
  13. ba(backtrace)显示调用堆栈
  14. f(frame)选择堆栈
  15. condition为已有断点添加条件
  16. dis(disable)禁用断点
  17. en(enable)启用断点
  18. pt(ptype)打印变量类型
  19. disp(display)显示表达式的值
  20. lay(layout)显示layout asm显示汇编 layout src显示文件

设置命令行参数

set args arg1 arg2 arg3 # 注意set args为命令 arg1为你想要设置的第一个参数

假设我们编写了这样的c++程序,他需要命令行参数

#include <iostream>

int main(int argn, char *args[])
{   
    using namespace std;
    cout << "共有 " << argn << "个参数"<<endl;
    for(int i=0;i<argn;i++){
        cout << args[i]<<" ";
    }
    cout << endl;
    return 0;                                                                   
}

先进行编译

g++ main.cpp -o main -Wall -g

测试调用
在这里插入图片描述
进入gdb调试
在这里插入图片描述
使用run直接运行,可以看到默认的启动命令是/home/wxy/main ,gdb支持前缀原则,你用r也能运行
在这里插入图片描述
使用set args设置命令行参数
在这里插入图片描述

示例

将用一个简单的例子带你了解一下调试的完整流程,使用的代码如下

//fun.h
int factorial(int n){                                                                                          
    if(n==0){
        return 1;
    } 
    return n*factorial(n-1);
} 

一个非常简单的计算阶乘的函数

//main.cpp
  #include <iostream>
  #include "fun.h"
  int main(int argc, char* argv[])
  {
      using namespace std;
      int FAC=stoi(argv[1]);
      for(int i=0;i<FAC;i++){
          cout << i << "!= " << factorial(i) << endl;
      }
      return 0;
  }

一个接受一个参数用于生成阶乘并打印的函数

编译

g++ main.cpp -o main -g // 注意加上-g我们需要调试信息

试运行一下

在这里插入图片描述
没有问题,但是在参数为20的时候发现出现了负数,这不正常,希望能调试一下看看(聪明的你知道这是超出了int的范围,不过先不管)
在这里插入图片描述

开始调试

进入调试

gdb main

在这里插入图片描述
我们可以使用l(list)来查看一下当前的文件
在这里插入图片描述
你可以使用l filename来查看别的文件
例如我们这里有fun.h

l fun.h:1 // 显示1行附近的内容

在这里插入图片描述
你也可以用函数名
在这里插入图片描述
注意我们现在需要一个参数,直接用r运行是不行的

在这里插入图片描述
通过 set args 20来设定参数
在这里插入图片描述
或者r 20这样附加参数
在这里插入图片描述
这样程序可以直接运行了

随后需要加上断点

cout << i << "!= " << factorial(i) << endl;

我们需要在这一行加上断点
在这里插入图片描述
这是在第八行所以直接

(gdb)b 8

在这里插入图片描述

接下来可以试着运行一下r
在这里插入图片描述
如何使程序可以在函数里面中断呢?
你也许想到了单步调试使用s(step)
在这里插入图片描述
可以看到这进入了<<的重载运算符,并不是我们想要的factorial
你可以使用fin(finish)来跳出这个过程
在这里插入图片描述
可以看到这个的返回值
再使用一次单步调试,可以看到这里是为了输出!=
在这里插入图片描述
继续跳出
在这里插入图片描述
再按一次s可以看到进入了我们想要的factorial
在这里插入图片描述
l查看一下我们处于fun.h文件之中了
在这里插入图片描述
此时你可以直接用

b 5

为第5行加上断点
在这里插入图片描述
c(continue)可以让程序运行
可以看到停到了我们新加的断点
在这里插入图片描述
再按一次c可以看到程序跳出了递归,没有终端因为没有运行到第五行直接从if返回了,打印输出了

1!=1

在这里插入图片描述
使用i b (info break)来查看断点信息
在这里插入图片描述
可以看到1号断点已经命中了两次,2号则是1次
现在删除2号

d 2

在这里插入图片描述
在实际的过程中,大部分情况下你其实没必要这样单步调试过去,完全可以直接给别的文件直接加上断点

b fun.h:5

在这里插入图片描述

现在有一个问题17!是出现问题的,我们需要跳到第17次循环,可以利用我们提到的(ig)ignore
使用p(print)打印一下i的值
可以看到当前是第二次循环
在这里插入图片描述
我们想跳到第17次,可以忽略1号断点14次来试试看
在这里插入图片描述

可以看到info信息显示也能看到设置了忽略
开始按c运行!
在这里插入图片描述
oops!出现了点小问题,三号断点会影响我们的循环,我们还需要他,所以请不要删除,可以使用dis(disable)禁用他就好
在这里插入图片描述

可以看到Enb这一位被我们设置成了n即代表no即被我们禁用了,他将不会再起作用
继续运行c(continue)
在这里插入图片描述
可以看到现在准确地停在了i=17的位置,接下来别忘了启用我们禁用的断点,使用(en)enable
在这里插入图片描述
可以看到成功启用,然后你可以注意到1号断点即使使用了ignore也能让其显示正确的被命中次数
继续运行
在这里插入图片描述

接下来我们换一种更简单的方式来进行中断,我们想看到factorial的逐级运算结果,想先进入到最深层的递归,可以看到递归的退出条件是n=0,所以我们利用(cond)condition来为3号断点加上条件

cond 3 n==1

只有在n==1时断点才停止
在这里插入图片描述
注意你也可以在创建断点时为其加上条件

b fun.h:5 if n==1

注意这个if是必不可少的

基本上支持c语言的大部分逻辑操作

开始运行

在这里插入图片描述
准确的停到了n==1时,比你计算命中次数方便地多
可以再使用cond删除断点条件

cond 3

在这里插入图片描述
s单步运行,可以看到马上要退出循环了
在这里插入图片描述
我们可以用bt(backtrace)来查看一下堆栈的信息
在这里插入图片描述
可以看到当前是#0我们可以通过f(frame)来选择堆栈号
在这里插入图片描述
我们跳到了main的堆栈
试一下我们学的打印本地变量的操作

info locals

在这里插入图片描述
这可以帮助你查看每个堆栈的信息
回到当前的堆栈
在这里插入图片描述
单步运行,马上要执行返回了
在这里插入图片描述
可以利用layout src打开界面查看一下
在这里插入图片描述
继续单步运行
在这里插入图片描述
可以看到layout帮我们直接显示了当前代码执行的位置,如果你喜欢可以使用
在这里插入图片描述
即将返回
在这里插入图片描述
这里我又单步了一次,但是你可以注意到没有返回值,你可以使用fin查看到返回值
在这里插入图片描述
可以看到当前的n为2,我们可以预想到下一个返回值应该是1*2=2,结果如我们所料
在这里插入图片描述
设置一个display这样每次fin的时候都会帮我打印n的值
在这里插入图片描述
可以利用layout asm打开汇编代码
在这里插入图片描述
你可以看到我们的返回值其实是在寄存器eax里面,所以其实你可以利用他来打印返回值

p $eax

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意你不输入指令直接按下回车即可重复上一条命令
在这里插入图片描述
在这里插入图片描述
你可以看到12的阶乘为479001600这没有问题,但是13的阶乘为1932053504,这比实际的13的阶乘6227020800要小,这是由于整形的表示最大的值也只有2147483647发生了溢出

12!的二进制为      0001 1100 1000 1100 1111 1100 0000 0000 	479001600
13!的二进制为 0001 0111 0011 0010 1000 1100 1100 0000 0000

发生了异常,由于使用的是int所以只会有32为保留

0111 0011 0010 1000 1100 1100 0000 0000 正好就是 1932053504

结束

充分运用好例子里面提供的指令,在调试的时候基本够用了,如果你想了解更多的指令内容可以用help 指令的形式查看

gdb的内容可能比你想象的要多非常多,你可以在这里查看,有一本书专门的讲解,如果你需要的功能非常复杂的话,可以看这里


网站公告

今日签到

点亮在社区的每一天
去签到