点击蓝字,关注我们
01
概念
GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。
在实际应用中,有两种调试方法:在线调试和离线调试。
离线调试适用于开发测试环境,可以自由启停进程,设置断点;在线调试一般用于现场问题分析,不能随便启停进程,对于技术要求较高。
02
前提条件
2.1 编译
若想执行gdb调试,在Makefile文件中需要增加编译调试选项-g,例如:
gdb dup_file.c –o dum_file_elf –g –lpthread
说明:-g选项的作用是在可执行文件(ELF)中加入源代码的相关信息,比如ELF中第几条机器指令对应源代码的行数。但不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。
-g完整格式是-glevel,其中,level中指定了调试信息中包含了调试信息的多少,默认的是2,level=1最少,level=3最多。
2.2 readelf查看段信息
例如:
readelf -S helloWorld|grep debug
注:helloWorld为文件名,如果没有任何debug信息,则不能被调试。
2.3 file查看strip状况
下面的情况也是不可调试的:
file helloWorld
helloWorld: (省略前面内容) stripped
注:如果最后是stripped,则说明该文件的符号表信息和调试信息已被去除,不能使用gdb调试。但是not stripped的情况并不能说明能够被调试。
03
使用方法
3.1 启动调试
在开发中可以将源码和可执行文件拷贝到某一目录下,使用gdb启动进程进行调试,也可以不拷贝源码和可执行文件,使用NFS挂载到编译环境执行调试;在现场环境中使用ps获取进程的pid,然后gdb –p pid执行在线调试。
离线调试:
gdb 进程名
gdb –tui 进程名
在线调试:
ps –A | grep 进程名
gdb –p pid/gdb attach pid
说明:使用-tui参数可以将调试窗口分为两部分:上面是源码,下面是调试信息,使用Ctrl+n/Ctrl+p或者方向键进行翻页。
带参数调试:
1、启动的时候带上参数
gdb --args xxx 参数
2、启动之后 run 带上参数
# gdb xxx
(gdb)run 参数
3、启动之后 set args 设置参数
# gdb xxx
(gdb) set args 参数
core文件调试
当程序core dump时,可能会产生core文件,它能够很大程序帮助我们定位问题。但前提是系统没有限制core文件的产生。可以使用命令limit -c查看:
$ ulimit -c
如果结果是0,即便程序core dump了也不会有core文件留下。我们需要让core文件能够产生:
ulimit -c unlimied #表示不限制core文件大小
ulimit -c 10 #设置最大大小,单位为块,一块默认为512字节
上面两种方式可选其一。第一种无限制,第二种指定最大产生的大小。
针对生成core文件进行调试,可以采用在线加载和离线加载的方式,如下:
gdb 可执行文件 core文件
3.2 SET命令
命令 | 说明 |
set args 参数值 | 设置进程启动时的参数,等同于:run参数 |
set print系列 | set print pretty on | off:是否格式化输出 set print obj on | off:是否以对象形式打印变量 set print address on | off:是否显示参数地址 set print array on | off:是否显示数组 set print elements 整型值:主要是设置数组的,如果你的数组太大了,那么就可以指定一个来指定数据显示的最大长度,当到达这个长度时,GDB就不再往下显示了。如果设置为0,则表示不限制。 set print union on | off |
set 变量=value | 强制设置某个变量的值 |
set environment name=value | 强制设置环境变量的值 |
set scheduler-locking on | off | 设置为ON,开启单线程调试模式,其余线程均挂起 |
注:有时候使用p打印调试信息不完整或者不便于阅读,可以使用set print elelent 0和setprint pretty on设置。
3.3 handle命令
handle命令
handle SIGUSR1 nostop noprint
handle SIGUSR2 nostop noprint
handle SIGPIPE nostop noprint
handle SIGALARM nostop
handle SIGHUP nostop
handle SIGTERM nostop noprint
注:设置GDB调试时对信号的相关动作。
3.4 设置断点
打断点还是比较有技巧的,虽然有很多打断点的方法,但是实际调试中一般就使用以下几种:
函数打断点:b 函数名
某一行打断点:b 源文件:行号
条件断点:
break 断点 if 条件
continue 断点编号(执行一次表示设定,再次执行表示取消)
continue 断点编号 条件
注:条件断点非常有用,实际调试中往往需要调试特定场景下函数调用关系,此时就需要设置断点触发的条件。
查看断点:info breakpoint/info break/info b
删除断点:delete 断点号/delete(删除所有断点)
禁用/开启断点:disable/enable breakpoint
ignore:
断点条件的一个特殊用法是,程序只有在到达断点一定次数之后才会停止,此时可以使用指令:
ignore 断点编号 次数
ignore 2 10触发断点10次后才会停止,每次触发断点count自动减1
说明:打完断点是不是执行continue就可以等待着运行到断点了呢?不一定,有时候断点处代码的执行需要外部出发,比如web发送特定消息后才可以触发执行,如果一直等待没有消息出发永远执行不到断点处,此时就需要结合自己的业务逻辑,手动设置出发条件。
3.5 执行程序
执行程序的方法有两种:一种是从main函数开始执行逐步分析,一种是执行到断点处。
重新运行:r/run
继续执行:c/continue
单步执行:n/next/next N(执行N次next)
单步进入:step(遇到函数进入函数内部,退出函数时使用finish)
结束函数:finish
强制返回:return(忽略当前未执行的部分,强制返回)
3.6 显示堆栈
(gbd) backstrace/bt
有时候跳转的次数太多,不知道具体调用的层级关系了,可以使用bt查看堆栈,该命令会产生一张列表,包含着运行过程和相关的参数。
3.7 变量操作
设置变量:set 变量=表达式
在调试的时候,有时候需要设置一些假数据查看对应输出,比如根据布尔值查看流程执行情况,此时就需要在执行到指定位置时手动设置一下数据的取值。
监控变量:
watch 变量 (数值改变时暂停运行)
awatch (被访问或改变时暂停运行)
rwatch (被访问时暂停运行)
有时候我们需要观察一个变量的变化过程,比如一个全局变量如何初始化,如何调用的,这就需要使用watch监控变量。
变量类型:
ptype var 变量类型
whatis var 显示一个变量var的类型
打印变量/表达式:
打印变量:p 变量
打印字符/表达式:p “%s”,字符/表达式
格式化输出:p/格式控制符 打印内容
说明:
gdb可支持的变量显示格式有:
x:按16进制格式显示变量
d:按10进制格式显示变量
u:按16进制格式显示无符号整型
o:按8进制格式显示变量
t:按2进制格式显示变量
c:按字符格式显示变量
f:按浮点数格式显示变量
也可以使用x(Examination)来打印需要显示的字符信息,格式如下:
x/格式 地址
格式(可选)一般是NFU:
1、N表示重复次数(表示显示内存的长度,也就是说从当前向后显示几个地址的内容)
2、F表示显示格式
3、U表示单位(b:字节,h:半字[2字节],w:字[4字节,默认],g:双字[8字节])。表示多少个字节作为一个值取出来,如果不指定的话,GDB默认是1个byte,当我们指定了字节长度后,GDB会从指定内存的地址开始,读取指定字节,并把其作为一个值取出来。
参数u可使用下面字符代替:
b:表示单字节
h:表示双字节
w:表示四字节
g:表示八字节
3.8 调试函数
disassemble
可以使用反汇编的指令disassemble去探究究竟在函数中发生了哪些操作,具体如下:
1、disassemble
2、disassemble 程序计数器
3、disassemble 开始地址 结束地址
格式1表示反汇编当前整个函数,格式2表示反汇编计数器所在函数的整个函数,格式3表示反汇编从开始地址到结束地址的部分。
call
强制调用函数:call 表达式
3.9 退出调试
q/quit
在执行到断点后,采用q/quit指令退出。
04
多进程调试
4.1 配置detach-on-fork
该属性决定了gdb是同时调试父子进程,还是在fork了子进程之后,将子进程分离出去。
on:子进程(或者父进程,取决于gdb在初始时,要调试的进程,也就是follow-fork-mode的值)
off:同时调试父子进程,一个进程处于被调试的状态,而另一个则被gdb挂起
设置:set detach-on-fork on/off
follow-fork-mode
该属性决定了gdb在进程调用fork之后的行为。
set follow-fork-mode parent:默认情况下,在调用fork之后,gdb选择跟随(也就是调试)父进程,而子进程则在处于运行的状态(此时父进程处于阻塞的状态)。
set follow-fork-mode child:fork之后gdb选择调试子进程,而父进程处于运行的状态。
4.2 查看进程
查看当前调试的进程:info inferiors
05
多线程调试
5.1查看线程
查看线程:info threads
注:输出信息前面有“*”表示调试的当前线程(一般thread切换线程后查看)。
有的程序会在运行过程中主线程创建多个子线程,所以前后执行info threads显示的线程数是会动态变化的。
5.2 查看线程堆栈
查看所有线程堆栈:thread apply all bt
查看指定线程堆栈:thread apply thread1 thread2... bt
5.3 切换线程
切换线程:thread N
注:通过打印counter,可以看到多个线程都是在运行的,如果想要让其他线程处于停止状态,只有当前调试的线程执行,可以采用set scheduler-locking on。
5.4 阻塞线程
阻塞其他线程,仅调试当前线程工作:
set scheduler-locking [on|off|step]
运行指定线程并允许其他线程并行执行:
thread apply N command
06
总结
对于C语言开发,必须熟练使用gdb进行调试,这可以帮助我们快速定位问题并解决问题,在开发中可以帮助我们及时找到测试出现的问题,在现场问题中如果日志打印不是很充分,日志信息量不够的情况下,gdb调试显得非常重要。
在实际应用中,我们通常是利用gdb分析core文件,这就需要结合寄存器,汇编,内存相关知识综合分析,后面会详细介绍相关分析技巧。
![9180bd8a5660c7c05e13f0ed5596e6ff.gif](https://i-blog.csdnimg.cn/blog_migrate/c032289e8536dca7cfc49a57e4c64cca.gif)
你们点点“分享”,给我充点儿电吧~