E

EdwardWong

V1

2022/09/26阅读:37主题:姹紫

GDB调试

借助gdb调试器可以实现以下几个功能:

  1. 程序启动时,可以按照我们自定义的要求运行程序,例如设置参数和环境变量

  2. 可使被调试程序在指定代码处暂停运行,并查看当前程序的运行状态(例如当前变量的值,函数的执行结果),即支持断点调试

  3. 程序执行过程中,可以改变某个变量的值,还可以改变代码的执行顺序,从而尝试修改程序中出现的逻辑错误

gdb的安装

使用包管理器快速安装

centos系统中,采用sudo yum -y install gdb

ubuntu系统中,采用的sudi apt -y install gdb

源码安装gdb

使用源码安装gdb时一定要提前安装gcc编译器,因为会用到/configuremake命令。

  • step 1 : 官网下载gdb源码包,gdb链接

  • step 2: 使用tar命令解压该文件(以.tar.gz为例)

tar -zxvf gdb-9.2.tar.gz

  • step 3: 进入gdb-9.2的目录文件,创建一个gdb_build_9.2目录并进入,为后续下载并放置安装gdb所需的依赖项做准备。

  • step 4: 执行../configure命令

  • step 5: 执行make命令进行编译, 此执行过程可能比较长

  • step 6: 使用命令sudo make install进行安装

  • step 7: 查看是否安装成功gdb -v

gdb调试c/c++程序

调试的前期准备(编译)

只有在源程序被编译为可执行文件并执行时,gdb才会排上用场。但是只使用gcc命令编译生成的可执行文件,是无法借助gdb调试的。

gcc main.c -o main.exe 这样是没有办法进行gdb的调试的。因为在进行调试的时候需要包含调试的必要信息,例如各行代码所在的行号、程序中所有变量名称的列表(符号表),而上面的main.exe是没有的

因此在进行调试前需要使用gcc -g选项编译文件。 gcc main.c -o main.exe -g

同时gcc编译器还可以加入-O选项支持优化和-g一起参与编译。 gcc编译过程的优化程度可以分为5个等级,即O0~O4,O0表示不优化,O4优化级别最高。

优化,例如省略掉代码中从未使用过的变量,直接将常量表达式用结果代替。这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率。

启动gdb调试器

在生成包含调试信息的main.exe可执行文件的基础上,启动gdb调试器的指令如下:

method 1 调试具有调试信息的可执行文件

gdb main.exe --> 会出现一些免责声明,可以通过--slient选项将信息屏蔽掉, 如gdb main.exe --slient

method 2

gdb+ file或者exec-file命令指定

method 3 调试正在执行的程序

gdb可以调试正在运行的程序。 每一个正在运行的程序都有一个或多个进程来运行,系统为了方便管理当前系统中运行的诸多进程,每个进程都配有唯一的进程号(PID)。

可以通过pidof + 文件名得到当前运行程序的进程号。

pidof main.exe --> 1803

gdb attach pidgdb -p pidgdb 文件名 pid

gdb调试器成功连接到指定进程时,程序会赞同执行,当退出调试让程序继续运行,需要首先通过detach命令使调试器和程序分离,然后执行quit退出调试。

调试执行异常崩溃的程序

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

linux操作系统中,当程序发生崩溃时,系统可以将发生崩溃的内存数据、调用堆栈情况等信息自动记录下来并存储到core文件中。 这叫做核心转储(core dump)。gdbcore文件的分析和调试提供了非常强大的功能支持。

默认情况下,linux系统不开启core dump,可以借助ulimit -culimit -a指令来查看当前其他是否开启。

如果core file size对应的值为0,表示当前系统未开启core dump功能,可以通过ulimit -c unlimited开启,这样当程序发生异常时,系统就会自动生成相应的core文件。**core文件默认生成的位置与该程序所在目录相同**

segement fault 段错误,也就是当前程序访问了不可访问的存储空间,比如访问的不存在的空间,又或者是系统保护的内存空间。

core文件的内容如下

gdb 常见的调试指令

b +行号 --> 打断点

r --> 执行被调试的程序,会自动在第一个断点处暂停执行

c --> 当程序在某一个断点处停止运行后,使用该命令可以继续执行,知道遇到下一个断点或程序结束

n --> 令程序一行一行的执行

p + 变量名 --> 打印指定变量的值

l ---> 显示源程序代码的内容,包括各行代码所在行号

q --> 终止调试

下图列出了gdb调试的一个简单例子:

启动程序命令run

run命令与start命令的区别:

默认情况下,run指令会一直执行程序,直至程序结束,如果程序中手动设置有断点,则run指令会执行程序至第一个断点处。

start指令会执行程序至main()主函数的起始位置,即在main()函数的第一行语句处停止执行,相当于在main()主函数起始位置设置一个断点,然后在使用run指令启动程序。

在进行调试前的准备工作包括以下内容:

  • 启动gdb未指定要调试的目标程序,由于各种原因未找到所指定的目标程序,这种情况下需要手动指定

  • 一些cc++程序需要接受一些参数(程序中用argcargv[]接收)

  • 目标程序在执行过程中,可能需要临时设置PATH环境变量

  • gdb默认将启动时所在的目录作为工作目录,如果程序所在的目录与gdb启动时的工作目录不同时,需要进行调整

  • 输入输出重定向

  1. step 1 对已经启动的gdb调试器,先通过l命令验证其是否已经找到了目标程序文件。

如果没有找到 --> 'No symbol table is loaded, use the file command'

  1. step 2. 对于int main(int argc, char* argv[]) 需要给主函数传递参数。 有以下三种方法可以实现传递参数
  • 未启动gdb调试器时,可以通过--args选项指定

gdb --args main.exe a.txt --> 调试main.exe程序并为其传递a.txt字符串。

  • 已经启动gdb调试器,借助set args命令指定目标调试程序启动所需要的数据。
  • 通过runstart命令启动目标程序并传递所需要的数据

run a.txt 或者start a.txt

  1. step 3 改变工作路径

启动gdb调试器后直接使用cd /tmp/demo

  1. step 4 临时修改环境变量

通过path /tmp.demo,只能临时修改

  1. 输入输出重定向

run > a.txt

只有调试所需的运行环境搭建好后,才可以使用run或者start命令

设置普通断点命令breaktbreak, rbreak

break命令常见的语法格式:

1. break/b location

2. break/b... if cond
  • location的方式
  • cond的方式

cond为某个表达式。 每次程序执行到...位置时都计算cond的值,如果为true,则程序在该位置暂停,反之,程序继续执行.

b 7 if num>10 --> 如果num>10,则运行到第7行停止

tbreakbreak命令相似,区别在于使用tbreak命令打的断点只会作用1次,之后会自动消失。

rbreak命令作用对象是c/c++程序中的函数,它会在指定函数的开头位置打断点。此段点会一直存在,不会自动消失

其中regex是一个正则表达式,程序中的函数名只要满足正则表达式,tbreak命令就会在函数内部的开头位置打断点。

watch命令设置观察断点监视变量值的变化

可以通过watch命令来监控某个变量或者表达式的值,通过值的变化情况判断程序的执行过程是否存在异常或者bug。 借助观察断点只要某个值发生了改变,程序就会停止执行。

命令格式:watch cond

cond就是要监控的变量或表达式

info watchpoints --> 查看当前建立的观察点的数量

info break --> 查看所有设置的断点

使用watch命令时需要注意的几点如下:

当监控的变量是一个局部变量时,一旦局部变量失效,则监控操作也随之失效。

如果监控的是一个指针变量(*p),则watch *pwatch p是有区别的, watch *p是监控p所指数据的变化情况,而watch p是监控p指针本身有没有改变指向。

watch命令同样可以监控数组中元素值的变化情况。 例如watch a只要a数组中存储的数据发生改变,程序就停止执行。

catch命令设置捕捉断点

捕捉断点用于监控程序运行时某一事件的发生,例如程序发生某种异常、某一个动态库被加载,一旦目标事件发生,程序停止执行。

命令如下: catch 监控事件

注意要想使用catch命令,gcc编译器的版本最低为4.8

catch命令捕捉到指定的event事件时,程序暂停执行的位置往往位某个系统库中,这时候应该使用up命令,即可返回event事件的源代码处。

tcatchtbreak相似,只会监控一次。

借助catch命令设置了捕捉断点,只要发生监控事件异常,程序就会暂停执行。由此当程序执行时,其会暂停至libstdc++库中的某个位置,借助up指令可以得知该异常发生在源码的具体位置。

监控c/c++程序动态库的加载和卸载

首先通过ldd命令查看程序运行期间所需的动态库。

设置条件断点condition命令

例如break ...if cond 如果后面的condition条件成立,那么就会出现断点。

同样的对于watch观察断点和catch捕捉断点都可以设置条件断点。

watch expr if cond --> 参数expr表示要观察的变量或表达式,参数cond用于代指某个表达式。

上述创建条件断点break...if/watch...if的方法不适用于捕捉断点,捕捉断点需要借助condition命令为现有捕捉断点增添一个cond表达式,才能使其变为条件断点。

condition命令没有缩写形式,语法如下:

condition bnum expression (为bnum编号的断点添加或修改条件表达式)或者condition bnum(删除bnum编号断点的条件表达式,使其变成普通的无条件断点)。

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

ignore命令

ignore命令也可以使一个断点成为条件断点,但这里的条件并非自定义的表达式,而仅为一个整数,它用来表示该断点失效的次数,也就是说,ignore命令可以使目标断点暂时失去作用,当断点失效的次数超出了指定的次数后,断点的功能会自动恢复。

ignore bnum count

单步调试程序next命令

gdb调试器提供了三种可以实现单步调试程序的方法,即使用nextuntilstep命令。

next,untilstep的区别在于

next在遇到包含调用函数的语句时,无论函数内部包含多少行代码,next指令都会一步执行完毕。

next/n count --> 其中count表示单步执行多少行代码。默认为1行。

step 命令在遇到函数的时候,会进入函数内部,并在函数第一行代码处停止执行

step/s count --> count表示一次执行的行数。

until命令的语法

1. until/u

2. until/u 行号

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

带参数的until命令可以后跟行号,以指示gdb调试器直接执行到指定位置后停止。

finish命令和return命令, jump命令

如果在某个函数中调试了一段时间后,可能不需要在一步步执行到函数返回处,希望直接执行完当前函数,可以使用finish命令,与finish命令类似的还有return命令,他们呢都可以结束当前执行的函数。

finishreturn函数的区别在于:

finish命令会执行函数到正常退出,而return命令是立即结束执行当前函数并返回。另外。return可以指定该函数的返回值。

jump 行号 --> 跳到某一行代码处

jump命令可以略过某些代码,直接跳到行号处的代码继续执行程序。

print命令和display命令

print命令不仅可以在调试过程中输出,还可以修改指定变量或者表达式的值p result=20

  • print命令的高级用法

print [option--][/fmt] expr --> option参数和/fmt或者expr之间,必须使用--分隔。当不指定任何选项时,print/fmt之间不用添加空格

option选项:

fmt选项:

print命令可以支持@::运算符。@运算符用于输出数组中指定区域的元素

print first@len --> first用于指定数组查看区域内的首个元素的值,参数len用于指令自first元素开始查看的元素个数。

int array[5]=[1,2,3,4]

print array[0]@2

当程序中包含多个作用域不同但名称相同的变量或表达式时,可以借助::运算符明确指定要查看的目标变量或表达值。

print file::variable     --> file 用于指定具体的文件名,文件名要用单引号括起来。

print function::variable --> function用于指定具体所在函数的函数名

print main.c::num --> 查看num全局变量的值

print main::num --> 查看num局部变量的值

print不同的地方在于,display在调试阶段会在程序暂停执行时自动帮我们打印出来,对于使用display查看的目标变量及表达式,都会被记录在一张列表上,可以通过info display打印出这张表.

display expr

display/fmt expr --> 以某种格式输出变量或者表达式

## 删除自动显示列表中的变量或表达式

`undisplay num` --> num表示目标变量或者表达式的编号

delete display num

## 禁用/激活变量或表达式

disable/enable display num

禁用和删除断点

  • 查看当前已建好的断点
info breakpoint[n]

info break[n] --> n为某个断点的编号,表示查看指定断点而非全部断点

info watchpoint[n] --> 查看观察断点
  • 删除断点

clear 行号或函数名 --> 如果为函数名时,表示删除位于该函数入口处的所有断点。

delete/d [breakpoints] [num] --> 如果不指明num,则删除当前程序中存在的所有断点。breakpoints参数可有可无,num为指定断点编号。

  • 禁用/激活断点

disable/enable [breakpoints] [num...] --> breakpoints可有可无,num可以有多个参数,每个参数为要禁用断点的编号,如果不指明num,disable会禁用当前程序中所有的断点。

enable [breakpoints][num...] --> 激活用num...参数指定的多个断点,如果不指定num...,表示激活所有禁用的断点

enable [breakpoints] once num... --> 激活以num...为编号的多个断点,但只能使用1次,之后会自动禁用

enable [breakpoints] count num... --> 临时激活以num...为编号的多个断点,断点可以使用count次,之后进入禁用状态

enable [breakpoints] delete num... --> 激活num...为编号的多个断点,但断点只能使用1次,之后会永久删除。

多线程的调试

对于多线程编程的调试,之后会继续补充。

后台执行调试命令

调试命令在执行过程中,是没有办法使用其他gdb调试命令的,因此可以采用后台执行的方式。

command& --> command&之间不需要空格

并非所有调试命令都支持后台执行,gdb支持后台执行的调试命令:

gdb的反向调试

所谓反向调试,指的是临时改变程序的执行方向,反向执行指定行数的代码

反向调试的常用命令:

反向记录需要先记录信息record

下面是反向调试的例子:

gdb编辑edit和搜索源码search

edit [location]

edit [filename]:[location]

可以指定任意的编辑器来编辑文件,进入gdb调试前,执行如下命令export EDITOR=/usr/bin/vim,这样会自动进入vim编辑器,从而对当前调试的程序进行修改。 如果想要永久生效,可以在~/.bashrc修改。

search <regexp>        --> 从当前行开始向前搜索

reverse-search<regexp> --> 向后搜索
``

分类:

后端

标签:

运维部署

作者介绍

E
EdwardWong
V1