Linux:调试器 - gdb

Linux:调试器 - gdb


gbd基本概念

GDB (GNU Debugger) 是一个强大的命令行调试工具,用于调试各种编程语言(如C、C++、Java、Python等)编写的程序。使用 gdb可以帮助开发人员更快地定位和修复程序中的缺陷,提高代码质量和开发效率。它是 Linux/Unix 系统上最常用的调试工具之一。

先在Linux主机上安装gdb

powershell 复制代码
yum install -y gdb

该指令需要root权限,要么sudo进行提权,要么以root身份执行。

如果一个可执行程序想要被gdb调试,那么该可执行程序必须带有调试信息,也就是以debug形式发布。我们现在有一个test.c源文件:

如果直接使用gcc那么其编译出来就是release版本:

powershell 复制代码
gcc -o test-r test.c

带上-g选项后,gcc会以debug形式编译:

powershell 复制代码
gcc -g -o test-d test.c

可以看到的是,debug版本的可执行程序test-d明显比release版本的大。我们可以通过readelf指令来查看可执行文件中有没有调试信息,可执行文件也是有固定格式的,这个格式叫做ELF,而readelf指令就是用于查看可执行文件内部内容的。

先查看release版本的文件:

powershell 复制代码
readelf -S test-r | grep debug

以上指令,管道左侧用于输出可执行文件内的内容,右侧用于筛选含debug的字段,最后该指令什么也没有输出,说明release版本内部不存在debug信息,也就是调试信息。

再查看debug版本的文件:

powershell 复制代码
readelf -S test-d | grep debug

输出结果如下:

可见该文件内部确实有debug调试信息。

随后我们就可以直接用gdb来调试可执行程序了:

powershell 复制代码
gdb test-d

当看到以下页面,说明成功开始调试了:

如果想退出,输入q或者ctrl + d


gbd调试

我以以下代码为例,来进行调试示范:

c 复制代码
#include <stdio.h>

int getNum(int n)
{
    int sum = 0;

    int j;
    for(j = 1; j <= n; j++)
    {
        sum += j;
    }

    return sum;
}

int main()
{
    int i, num = 0;

    for(i = 0; i < 10; i++)
    {
        num += getNum(i);
    }

    printf("%d\n", num);

    return 0;                                                                                           
}

浏览

l #:列出以#行为中心的10行代码
l:从上一次的最后一行开始,列出往后的10行代码

此处的l也可以改为list

第一次执行l 1,就会列出从第1行开始的代码:

再次输入l,则会从上一次的后一行代码开始,也就是第11行开始:

输入l 16

其不是从第16行开始,而是把第16行放在最中间,之前l 1从第一行开始,是因为第一行上面没有代码了。

l 函数名:列出某个函数的源代码

比如l main,就是列出main函数的代码:

不过其不是把main放在第一行,而是把main放在中心。


断点

b #:在行号为#处设置一个断点、
b 函数名:在函数的开头设置一个断点

bbreak的简写,此处的b改为break也可以。

现在我们要在第22行设置断点,输入b 22

其显示我们把断点设置在了第22行,断点序号为1

再给getNum函数设置一个断点,b getNum

其显示我们把断点设置在了第5行,断点序号为2

如果想查看我们设置过的断点:

info b:查看断点信息

此时就列出了目前所有的断点信息,Num表示断点编号;Enb表示当前断点是否生效;What描述了该断点的信息。

d #:删除编号为#的断点

此处ddelete的缩写,把d换为delete也可以。

使用d 2,把编号为2的断点删掉:

此时再info b,就只剩下编号为1的断点了。

disable #:禁用编号为#的断点

使用disable 1,把1号断点禁止:

再次info b,可以看到一号断点的Enb属性变为n了,表示该断点失效了。

enable #:启用编号为#的断点

使用enable 1,把1号断点启用:

再次info b,可以看到一号断点的Enb属性变为y了,表示该断点启用了。

总结一下断点相关命令:

命令 功能
b # 在行号为#处设置一个断点
b 函数名 在函数的开头设置一个断点
info b 查看断点信息
d # 删除编号为#的断点
disable # 禁用编号为#的断点
enable # 启用编号为#的断点

运行

r:运行程序

此处rrun的简写,使用run也可以

对当前程序使用r后,直接执行到了结尾,并输出结果165exit normally表示程序正常退出。

现在我们使用b getNumgetNum函数上打一个断点,再次执行r指令:

可以看到,此时没有直接执行完程序,而是执行到断点处就停止了。我们再执行一次r

其发出询问:"是否要从头开始执行",也就是说第一次使用r指令,会执行到下一个断点,如果没有断点就执行到程序结束,但是每次使用r都必须是从头开始执行的。因此r指令一般用于进入程序,后续的调试一般不用r

c:执行到下一个断点

此处的ccontinue的缩写,使用continue也可以。

对刚刚的程序执行c

第一次执行r指令,到达第一个断点,也就是第一次调用getNum的时候,此时参数n = 0。第二次执行c指令,到达下一个断点,第二次调用getNum此时参数n = 1。因此c用于断点之间的跳转。

n:逐过程调试

现在我们删除原先的getNum断点,把断点打在第22行:

也就是语句num += getNum(i);处。

对该程序多次使用n

第一次执行n,停在了for循环的语句;第二次执行n,停在了num += getNum(i);;第三次执行n,停在了for循环的语句。

逐过程调试的特点在于不会进入函数内部,把函数当成一个语句执行。

s:逐语句调试

示例:

一开始我们处于num += getNum(i);中,此时执行s指令,其直接跳转到了getNum函数的内部,到达其第一条语句int sum = 0;

逐语句调试的特点在于会进入函数内部,详细展示函数内部的执行细节。

finish:执行到当前函数返回

示例:

一开始我们处于getNum函数的第一条语句int sum = 0;处,此时直接执行finish指令,跳转到了函数结束,并告知本次调用函数返回值为6

总结:

命令 功能
r 运行程序
c 执行到下一个断点
n 逐过程调试
s 逐语句调试
finish 执行到当前函数返回

变量

我们也可以在gdb中随时查看变量的值。

p #:输出变量值

示例:

现在处于某一次调用getNum的过程中,使用p sum得到当前sum = 15p j得到当前j = 5p n得到当前n = 8

display #:跟踪名为#的变量,每次调试都会输出该变量的值

示例:

先跟踪nsumj三个变量:

支持c进行调试:

可以看到,其附带输出了nsumj的值。

每个变量前面都要一个数字,这是每个变量的编号。

undisplay #:取消对编号为#的变量的跟踪

示例:

一开始跟踪了nsumj三个变量,此时执行指令undisplay 2,就取消跟踪了sum变量。再次调试时,就没有sum变量了。

总结:

命令 功能
p # 输出变量值
display # 跟踪一个变量,每次调试都会输出该变量的值
undisplay # 取消对变量的跟踪

相关推荐
o(╥﹏╥)6 分钟前
在 Ubuntu 上安装 VS Code
linux·运维·vscode·ubuntu·vs
AI慧聚堂30 分钟前
自动化 + 人工智能:投标行业的未来是什么样的?
运维·人工智能·自动化
不爱学英文的码字机器33 分钟前
[Linux] Shell 命令及运行原理
linux·运维·服务器
cdut_suye44 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
qq_433618441 小时前
shell 编程(三)
linux·运维·服务器
苹果醋31 小时前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
两张不够花1 小时前
Jenkins 持续集成部署
运维·jenkins
Tlzns1 小时前
Linux网络——UDP的运用
linux·网络·udp
码农土豆1 小时前
PaddlePaddle飞桨Linux系统Docker版安装
linux·docker·paddlepaddle
Hacker_xingchen1 小时前
天融信Linux系统安全问题
linux·运维·系统安全