目录
基本调试
编译时加上 -g,添加调试信息,同时需要关闭优化(O2,O3,…)
带参数调试
gdb --args exe_demo arg1 arg2
- 进入gdb界面,然后设置参数:
gdb exe_demo; set args arg1 arg2
- 在run指令后面直接跟参数就行:
gdb exe_demo; r arg1 arg2
- 查看传递的参数:
show args
断点操作
设置断点
b function
:在函数function的入口处设置断点,C++中可以使用class::function
或function(type,type)
来指定函数名b line_number
:在当前活动源代码文件的line_number处设置断点b filename:line_number
:在源代码文件filename的line_number处设置断点,文件可以使用相对或者绝对路径b filename:function
:在文件filename中的function入口处设置断点。会在所有重载函数或同名静态函数上设置断点b *address
在程序运行的内存地址处停住。- 条件断点:
break break-args if (condition )
break-args
是可以指定断点位置的任何参数,如上所示condition
括号可选,condition
表达式具有布尔值就行可以- 条件可以使用:和有效的C语句差不多:
- 相等/不相等/逻辑:
<, <=, ==, !=, >, >=, &&, ||
- 按位和移位运算符:
&,|, ^, >>, <<
- 算术运算符:
+, -,x, /,%
- 自己的函数,只要它们被链接到程序中
- 库函数,只要该库被链接到程序中——库函数还在哪种情况下可以使用,printf可以使用
- 相等/不相等/逻辑:
- 对正常断点设置条件以将他们转变为条件断点,使用
cond【ition】
,如cond 3 i==3,如果要删除条件但保持该断点,只要输入cond 3即可(这个3是断点的序号,可以用info b查看),在程序运行到断点处设置 - 临时断点
tb【reak】
的命令设置和break相同,有效性只到第一次到达指定行为止 - 如果是虚函数,如下打断点会出现多个:
删除断点
d breakpoint_list
:删除断点使用数值标识符,如delete 2删除第二个断点,delete 2 4删除第2,4个断点d
:删除所有断点clear
:清除GDB将执行的下一个指令处的断点——实测为清除执行所在行的断点clear function
,clear fliename:function
,clear line_number
和clear filename:line_number
,这几个命令的工作方式和break对应的命令相似
禁用断点
enable/disable breakpoint-list
:禁用断点:其中breakpoint_list是使用空格分隔的列表,如enable 15,disable
可以简写为dis
enable/disable
不带任何参数将启用/禁用现有所有断点enable once breakpoint-list
:在断点下次引起GDB暂停执行后被禁用
查看断点
info b【reakpoints】
,各属性如下:
Num
:断点的唯一标识符type
:指出该断点是断点,监视点还是捕获点Disp
(部署):指示断点下次引起GDB暂停程序执行后该断点上会发生什么事情,可能的部署有三种:keep
(保持):默认部署,下次断点到达后不改变断点del
(删除):下次到达后删除断点,使用tbreak创建的任何断点都是这样的断点dis
(禁用):下次到达断点时会禁用该断点,这是使用enable once命令设置的断点
Enb
(启用状态):这个字段说明断点当前是启用还是禁用Address
(地址):内存中设置断点的位置What
(位置):显示断点所在文件和行号
执行
step
和next
都可以单步调试,都有一个可选的参数,如next 3效果和三次next相同;next(单步越过函数)会执行下一行,step(单步进入函数)类似,但在函数调用时,step会进入函数,而next导致程序执行的暂停出现在下次调用函数时c
:程序执行到下一个断点处或程序结束;可以接受一个可选整数参数n,表示要忽略下面n个断点fin
:恢复程序执行,直到恰好在当前栈帧完成之后为止,即退出当前函数,进入调用他的函数u
:跳到某个位置执行,通常用在跳出循环或直接跳到某一行的情况;可以接受源代码中的位置,函数名等作为参数, 会执行到该处u 17
跳到第17行u function
u filename:line_number
u filename:function
检查和设置变量
p
:打印,如果要打印指向结构体指针的各成员,使用p *tmp;p $1:打印已经打印过的变量;p/x var
以十六进制显示,其他常用的格式如c表示字符,s表示字符串,f表示浮点。在p的打印中,类似"\357\377\000\001"代表的是一个字符数组的内容,其中的每一个\元素都是一个八进制表示的ASCII码,如果希望看到更直观的字符形式,可以使用"x/s
"命令来打印字符串,具体参考下面examine
的用法disp x
:GDB在每次暂停时都会打印该变量x(在这个变量的作用域内打印,出了作用域就不打印了)dis disp x
:临时禁用某个显示项enable disp x
:重新启用- 完全删除显示的条目,使用
undisp【lay】 xxx
- 查看变量类型:
whatis X
ptype
:查看类或结构的成员(不是查看值)p/type
命令来查看一个对象的实际类型。例如,如果你有一个名为obj的父类对象,你可以使用以下命令来查看其实际类型:p/type obj
,但是这种方法只能在程序编译时启用了RTTI的情况下使用。在g++中,你可以使用-frtti选项来启用RTTI。info locals
:得到当前栈帧(函数栈)中所有局部变量的值列表examine
(简写:x)查看内存:x/<n/f/u> <地址>
n
是一个整数,表示显示内存的长度,从当前地址向后显示几个地址的内容;- f表示显示格式,可取值如下:
- x:按十六进制显示变量
- d:按十进制显示
- o:按八进制显示
- u:十进制格式显示无符号整型;t:二进制格式显示变量;a:十六进制格式显示变量;i:指令地址格式;c:按字符格式显示变量;f:浮点数格式显示变量
- u表示从当前地址往后请求的字节数,如果不指定的话GDB默认是4个bytes。可以用以下字符代替:
- b:单字节
- h:双字节
- w:四字节
- g:八字节
举例:
(gdb) x/16xw 0x7FFFFFFFE0F8
0x7fffffffe0f8: 0x11f71b00 0x5f731990 0x00000000 0x00000000
0x7fffffffe108: 0xf7de30b3 0x00007fff 0xf7ffc620 0x00007fff
0x7fffffffe118: 0xffffe1f8 0x00007fff 0x00000000 0x00000001
0x7fffffffe128: 0x55555198 0x00005555 0x55555210 0x00005555
(gdb) x/16xd 0x7FFFFFFFE0F8
0x7fffffffe0f8: 6877869162492271360 0
0x7fffffffe108: 140737351921843 140737354122784
0x7fffffffe118: 140737488347640 4294967296
0x7fffffffe128: 93824992235928 93824992236048
0x7fffffffe138: -18285573919020457 93824992235648
0x7fffffffe148: 140737488347632 0
0x7fffffffe158: 0 18285573285014103
0x7fffffffe168: 18268258704878167 0
- p和disp允许指定可选的格式:
监视
值改变时就暂停执行,只能监视存在且在作用域内的变量,一旦变量不存在于调用栈的任何帧中,gdb会自动删除监视点
watch X
:每当X值发生变化gdb都会暂停执行——会自动删除监视点,那怎么监视- 基于条件表达式监视:如
watch (z>28)
,表达式为true时gdb暂停执行 - 查看和删除监视点同断点
上下移动调用栈
frame
命令:当前执行的函数的栈帧编号为0,其父帧被编号为1,以此类推up
:进入上一个父帧(怎样查看上一个栈中的某些变量的信息?)down
:反方向
bt
:会显示整个栈,即当前存在的所有帧的集合
显示源码
文件名可以忽略,表示当前文件
layout
的用法:layout
:用于分割窗口,可以一边查看代码,一边测试。主要有以下几种用法:layout src
:显示源代码窗口layout asm
:显示汇编窗口layout regs
:显示源代码/汇编和寄存器窗口layout split
:显示源代码和汇编窗口layout next
:显示下一个layoutlayout prev
:显示上一个layout
- 窗口显示:
Ctrl + L
:刷新窗口Ctrl + x
,再按1:单窗口模式,显示一个窗口Ctrl + x
,再按2:双窗口模式,显示两个窗口Ctrl + x
,再按a:回到传统模式,即退出layout,回到执行layout之前的调试窗口。
- list的用法:
l【ist】 main.c:main
:显示main.c文件中main函数附近的源码(具体是多附近?)l【ist】 main.c:2,20
:显示main.c中第2到20行的源码- 单独l显示当前代码,显示10行
其他调试信息
- 重新开始调试:
r
断点命令列表
使用commands命令设置命令列表,在断点执行到这里的时候会自动执行commands。格式如下:
- 这样进行输出的时候会打印断点信息如cpp文件,行号等,使用silent避免打印这些,只输出commands中指定输出的内容
- 如果命令列表中的最后一个命令(end之前)是continue,GDB在完成命令列表中的命令后继续自动执行程序
- 怎样取消这个命令?重新定义一个空集合来取消
- commands命令中可以使用if/else之类的逻辑控制,命令列表和条件中断合并使用威力巨大
commands breakpoint-number
silent
commands // 用新行分隔的任何有效GDB命令
. . .
end
define
命令创建宏:可以作为commands中的一条命令- 宏可以使用参数
- 使用
show user
可以看到所有的宏列表
define CMD
/*command*/
end
检查和设置变量
其他命令
printf
:类似C中的printf,格式控制输出call
:调用程序中的函数
检查动态数组
如果是字符串类型还是可以打印出来的,如果是其他类型直接用p x打印不了,一般形式为 *pointer@number_of_elememts;表示打印这个指针的前number_of_elememts个元素
int *a = new int[32];
a[3] = 10;
gdb: p *a@3
$1={0,0,10} # 显示
- GDB还允许适当的时候使用强制类型转换
GDB中的表达式
- GDB变量
- 程序中任何在作用域内的变量
- 任何种类的字符串,数值或字符常量
- 预处理宏
- 条件,函数调用,类型强制转换和所用语言的运算符
在GDB中设置变量
有什么用还没探索出来
set args
命令设置命令行参数info args
:检查当前函数参数
GDB中的变量
- 值历史:诸如
$1, $2
等 - 方便变量——?也没看懂
handle
命令告诉gdb不要停止或发出警告信息
程序崩溃处理
core文件
- 使用
ulimit
命令控制核心文件的创建ulimit -a
:可以查看core文件的生成开关和大小限制所有信息,输出内容中core file size,若为0表示不生成core文件ulimit -c
:ulimit c
:可查看core文件的生成开关,如结果为0,则表示关闭了功能,不会生成core文件ulimit -c n
:n是核心文件的最大大小,以kb为单位;ulimit -c unlimited
不限大小;
- ulimit只在当前目录下生效, 经实测
- core文件的生成路径:
- 默认和可执行文件在同一目录
- 没有生成core:
- ulimit -c确认不为0
cat /proc/sys/kernel/core_pattern
,如果内容为|/usr/share/apport/apport %p %s %c %d %P %E
,需要修改:echo "core-%e-%p-%t" > /proc/sys/kernel/core_pattern
,注意,使用vim可能会提示没有权限。如果上面命令提示没权限修改,使用:echo "core-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:
%p - insert pid into filename 添加pid(进程id)
%u - insert current uid into filename 添加当前uid(用户id)
%g - insert current gid into filename 添加当前gid(用户组id)
%s - insert signal that caused the coredump into the filename 添加导致产生core的信号
%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
%h - insert hostname where the coredump happened into filename 添加主机名
%e - insert coredumping executable name into filename 添加导致产生core的命令名- 还有一种方法(未实测):vim etc/default/apport文件,enabled 设置为0
- 默认名字:core
frame n
:移动到第n个栈帧中- 调试流程:
gdb 【应用程序名称】 core
- 查看崩溃的堆栈:bt(backtrace)
- frame x:进入x栈帧
- 调试会话期间不退出GDB
- vim窗口中直接make可以调用同目录下的makefile
- vim中的autowrite
多线程调试
info threads
:给出当前所有线程的信息,"*"表示当前位于哪个线程;bt
:查看堆栈thread n
,n为info threads中线程前面的编号,切换到n线程break m thread n
:当线程n到达源码第88行时停止break xx.cpp:123 thread all
:在所有线程中相应的行上设置断点break m thread n if x==y
:当钱程n到达源码第m行,并且变量x和y相等时停止执行thread apply ID1 ID2 command
:让线程ID1和ID2执行GDB命令commandthread apply all command
:让所有被调试线程执行GDB命令commandset scheduler-locking off|on|step
:使用step或continue命令调试当前被调试线程的时候其他线程也是同时执行的。off
:不锁定任何线程,也就是所有线程都执行,默认值on
:只有被调试线程执行step
:在单步的时候,除了next过一个函数之外,只有当前线程会被执行
- 怎样知道其他线程在干什么?thread x切换到其他线程然后执行bt查看该线程的栈
不阻塞进程运行
- 背景:一般业务会有心跳机制,使用gdb调试需要先关闭心跳功能,避免长时间断住进程导致复位
- 两种方法可以做到gdb执行命令后快速退出:
gdb -batch -ex <command> -p <pid>
。如gdb -batch -ex “call test_show()” -p 23456- 使用command命令
其他调试
- 调试运行进程:
1. 启动gdb时链接目标进程:gdb -p <PID>
指定目标进程,就可以进入调试状态;c命令继续,不一定是r
2. gdb中链接目标进程:(gdb) attach <PID>
3. 断开链接:detach
- 源文件在多个目录下,dir的用法:
dir
:清除所有自定义源文件搜索路径信息dir xxx
:添加一个源文件路径,如果要添加多个,使用":";gdb调试时有时会出现找不到源代码文件只有行号的情况,用dir指定源码路径,如果指定到具体文件是不行的(亲测)show dir
:显示定义了的源文件的搜索路径
- 文件焦点:GDB相关操作都是在具有焦点的文件上进行操作,这个文件默认是包含main函数的文件,当如下动作发生时,焦点会转移:
- 向不同的源文件应用list命令——list swap就进入到swapper.c文件中(swap是swapper.c中的函数)
- 进入位于不同的源代码文件中的代码
- 当在不同的源代码文件中执行代码时GDB遇到断点——?
- 调试宏:在gcc编译时加上-ggdb3就可以调试宏了
info macro
:查看这个宏在哪些文件中被引用了,已经宏定义是什么样的macro
:查看宏展开的样子
.gdbinit文件
放在用户主目录下,GDB启动时自动加载
- 参考:https://www.cnblogs.com/jiu0821/p/6244324.html,格式遵守:
define <command> ## 调用该宏命令执行
<command>
end
document <command> ## help command可以显示出来
<help text>
end
show user
:可以查看自定义的命令
其他工具
strace
:ltrace
:pstack
:linux下打印输出此进程的堆栈信息,可以输出所有线程的调用关系栈
调试总结
- 某个线程停止(可能是死锁或者消息接收超时):使用
gcore pid
(调试进程的pid号)手动生成core文件,再使用pstack查看堆栈 - 清理屏幕:
shell clear
- 问题总结:
- 如果想一直监视而不是每次进入堆栈就watch一次怎么操作?
- GDB不退出重新编译后run报段错误,是什么原因?
- 怎样用b一次打多个断点?
- 简写汇总:
break:b;delete:d;continue:c;finish:fin;until:u;print:p;backtrace:bt
GDB调试参考博客:https://blog.csdn.net/gongmin856/article/details/79192259 - Linux中共提供了三个函数用于打印调用堆栈:
/*
* 函数说明: 取得当前函数的调用堆栈
* 参数:buffer:用于存储函数地址的数组;size:buffer数组的长度
* 返回值:存储到数组中的函数个数
*/
int backtrace(void **buffer, int size);
/*
* 函数说明:将一组函数地址转换为字符串
* 参数: buffer: 经由backtrace得到的函数地址;size: buffer数组的长度
* 返回值: 函数在系统中对应用字符串
*/
char **backtrace_symbols(void *const *buffer, int size);
/*
* 函数说明:将一组函数地址转换为字符串
* 参数: buffer: 经由backtrace得到的函数地址;size: buffer数组的长度;fd: 输出结果文件描述符
*/
void backtrace_symbols_fd(void *const *buffer, int size, int fd);