目录

Linux下的gcc与gdb

代码编译与链接

函数库

gdb介绍和安装

gdb基本使用指令

示例代码

debug模式和release模式

基本指令

进入gdb调试与显示调试代码

创建断点与删除断点

启用和禁用断点

执行代码

逐语句和逐过程调试

断点跳转

显示指定变量以及对应内容

打印变量的值

执行到指定行

执行完当前作用域的代码

监视变量

运行时临时修改变量内容

条件断点

退出gdb调试

cgdb增强调试过程


Linux下的gcc与gdb

在前面C语言的编译与链接章节中学到了C语言的可执行程序是如何从代码一步一步生成的,下面是前面用到的指令

代码编译与链接

代码编译预处理

gcc -E test.c -o test.i
  • 选项-E,该选项的作用是让gcc在预处理结束后停止编译过程
  • 选项-o是指目标文件,.i文件为已经过预处理的C原始程序

预处理生成汇编

gcc -S test.c -o test.s
如果已经在前面执行了编译预处理生成了 test.i文件,则可以直接用 test.i文件而不是 test.c文件
  • 用户可以使用-S选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码

汇编生成二进制文件

gcc -c test.c -o test.o
如果已经在前面执行了编译预处理生成了 test.s文件,则可以直接用 test.s文件而不是 test.c文件
  • 使用选项-c就可看到汇编代码已转化为.o的二进制目标代码,.o文件也被称为可重定位目标文件

二进制文件链接生成可执行程序

gcc test.c -o test
如果已经在前面执行了编译预处理生成了 test.o文件,则可以直接用 test.o文件而不是 test.c文件

函数库

函数库一般分为静态库和动态库两种

  1. 静态库:编译链接时把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,在运行时不再需要库文件了。其后缀名一般为.a
  2. 动态库:在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为.sogcc在编译时默认使用动态库。完成了链接之后,gcc就可以生成可执行文件

直接编译时选择动态库:gcc test.c -o test

使用静态编译选项-static时选择静态库:gcc -o test test.c -static

gdb介绍和安装

gdb(GNU调试器)是GNU项目的一部分,用于调试C、C++、Pascal、Objective-C、Ada以及其他语言编写的程序。它允许开发者执行一系列的调试操作,如启动程序、设置断点、监视变量、单步执行代码、改变程序状态等,以便于找出并修正程序中的错误

在CentOS安装gdb可以使用下面的指令:

sudo yum install -y gdb

需要查看gdb版本时可以使用下面的命令:

gdb --version

gdb基本使用指令

示例代码

以下面的代码作为本次的调试用例:

#include <stdio.h>

int add(int start, int end) {
    int sum = 0;
    for(int i = start; i <= end; i++) {
        sum += i;
    }

    return sum;
}

int main() {
    printf("Start programme\n");

    int start = 0;
    int end = 20;
    int result = add(start, end);
    printf("result = %d\n", result);

    printf("End programme\n");

    return 0;
}

对应的Makefile文件内容如下:

TARGET=test
SRC=test.c

$(TARGET):$(SRC)
    $(CC) -o $@ $^ -std=gnu99

.PHONY:clean
clean:
    rm -rf test

debug模式和release模式

默认情况下,直接使用gcc编译生成的可执行程序是release模式下的程序,不包含任何debug调试信息,并且release模式下无法进行调试。如果需要使用gcc编译生成的可执行程序是debug模式下的程序,就需要带上-g选项,生成的可执行文件包含debug信息,并且debug模式可以对代码进行调试

在CentOS下可以使用下面的指令判断一个文件是否使用debug模式编译生成

readelf -S test | grep -i debug

在Ubuntu下直接使用file+可执行程序文件即可查看到是否带有debug的字段即可判断是否使用debug模式编译生成

使用初始的Makefile进行编译时,生成的是release模式下的可执行程序,所以不带有任何debug信息:

而将Makefile中的内容修改为如下:

# ...
$(TARGET):$(SRC)
    $(CC) -o $@ $^ -std=gnu99 -g
# ...

此时就会显示debug相关字段的信息

基本指令

进入gdb调试与显示调试代码

在创建出debug模式下的可执行程序test后,在终端输入gdb test,即可进入调试模式

在调试中,输入l+对应数字显示指定行以及之前的代码,默认一共显示10行代码,而l后方的数字对应行的代码一般会出现在展示的10行代码中央

如果输入 l 0则默认从第一行代码展示,一共展示10行,效果同 l 1。另外, gdb中存在指令记忆,不输入任何指令会默认执行上一条指令

技巧:在gdb调试过程中,如果想要看到文件中的完整代码,可以输入l 0或者l 1,在展示完10行代码后接着按回车即可继续展示后10行代码,以此类推直到抵达文件内容末尾。

也可以使用l+文件:数字显示指定文件中的对应行相应代码,其余效果与l+数字相同

创建断点与删除断点

gdb中,使用b/break+需要添加断点的行号即可在指定行添加断点

需要查看已经添加断点可以使用info + b,显示的表格即为已经添加的断点信息

gdb中的断点都拥有自己的编号,删除断点时使用del + 断点编号

需要注意,编号的大小依次递增,并且其编号生命周期随着 gdb结束而结束,所以尽管删除断点,删除断点的编号依旧不会被新的断点使用

例如,在代码的16行和20行创建断点,展示断点信息,再删除20行的断点,再在第5行添加断点

  • 创建断点

  • 删除断点和添加新断点

同样,也可以使用break 文件:行号指定文件名中的行位置创建断点,还可以指定函数名b+函数名添加断点,函数名断点会默认跳到函数的第一条语句

启用和禁用断点

gdb中,使用enable+断点编号启动对应行的断点,使用disable+断点编号禁用断点

  • 对于启用的断点来说,使用info b查看断点信息可以在Enb列看到其值为y(代表yes
  • 对于禁用的断点来说,使用info b查看断点信息可以在Enb列看到其值为n(代表no

例如,禁用第5行的断点,再启用

  • 初始状态

  • 禁用状态

  • 启用状态

执行代码

使用r指令运行代码

默认情况下,使用r指令运行代码会直接显示代码运行结果

如果添加了断点,使用r指令运行代码时,会直接运行到断点所在行然后等待(前面的代码会执行,但是断点所在行不会执行),此时会显示当前运行所在行

如果代码已经处于运行状态,再使用r指令会收到提示是否重新运行,按照需求选择即可

例如,在第17行添加断点,执行代码

逐语句和逐过程调试

gdb中,使用n指令进行逐过程调试,在逐过程中,不会执行函数细节,即遇到函数不会进入函数;使用s指令进行逐语句调试,与逐过程相反,在逐语句中,会执行函数细节,但是不会执行库中的函数

逐语句和逐过程调试过程中,显示的行是下一步需要执行的行,而不是已经执行之后的行

逐语句和逐过程不会在多个断点间跳转
断点跳转

gdb中,如果创建了多个断点需要直接从一个断点跳到下一个断点位置并执行两个断点间的代码,可以使用c指令

例如,在第17行和第5行创建两个断点,使用指令从第17行的断点跳转到第5行的断点

显示指定变量以及对应内容

gdb中,使用display + 变量名可以显示指定变量的值,显示的变量会在每一次调试语句中显示对应的内容,显示的变量会一直持续显示除非离开变量所在作用域、显式取消显示或者退出gdb

显示变量必须在对应变量所在作用域,否则会报错为: No symbol "xxx" in current context
需要查看变量的地址,也可以使用 display+&变量名

每一个变量也存在变量,并且变量编号不受作用域的限制,并且也满足线性递增,所以取消显示已经显示的变量,使用undisplay + 变量编号

打印变量的值

使用p+变量名指令可以打印对应变量的值

执行到指定行

gdb中,可以使用until+行号直接跳转到对应行,并且执行起始位置和指定行位置之前的代码。如果until中指定行的代码执行完毕之后没有遇到断点,程序就会直接运行到当前作用域结束,否则跳转到下一个断点处

执行完当前作用域的代码

如果需要快速执行完当前函数作用域(非最外层函数作用域,本示例代码中最外层函数作用域为main函数所在作用域,非最外层函数作用域为add函数所在作用域)的代码,则可以使用finish指令

对长的代码块进行区间调试技巧:将断点打在多个函数所在行,使用断点+finish/until+c依次检查函数是否出现问题
监视变量

所谓监视变量,与前面打印变量的值基本一样,但是不同的是,监视变量在gdb中本质是打一个断点,并且只有在变量的值发生变化时,才会再次自动打印变量以及对应的值

监视变量使用watch+变量名,删除监视变量与删除断点的方式相同,也可以在断点列表中查看到对应的监视变量断点

运行时临时修改变量内容

部分情况下问题已经暴露并且需要改变一下变量内容临时查看是否修改对应变量的内容可以解决问题,此时就可以使用set 变量名=值在调试代码时临时改变指定变量的值

条件断点

gdb中,一共有两种添加条件断点的方式:

  1. 在指定位置新增条件断点:使用b/break+断点行号+if 条件
  2. 在已有断点位置添加条件:使用condition+已有断点编号+条件
条件断点在新增后,会在断点列表显示断点跳转条件

退出gdb调试

使用quit指令退出调试,如果调试的代码还在运行,会询问用户是否结束并退出调试,根据需要选择即可

cgdb增强调试过程

直接使用gdb调试需要一直使用指令查看代码以及断点,为了使调试更加方便,可以使用cgdb代替gdb,但是基本使用方式与gdb完全相同,只是cgdb视觉上更加直观

在CentOS下,使用下面的指令安装cgdb

sudo yum install -y cgdb

使用cgdb调试代码就可以看到下面的界面:

代码行号处的箭头表示下一步需要执行的代码,刚开始并未没有断点或未开始情况下,会指向main函数的第一条语句

如果添加了断点,则断点所在行号会变成红色,例如下面的结果:

尽量不要使用鼠标滚轮拖动代码窗口,防止出现问题

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部