1 内容简介
本文是一篇GDB学习笔记,总结了GDB常用命令,方便以后查阅。
本文结构按照一个标准程序的调试流程来写, 内容依次涉及开始、打断点、变量、调用栈、单步调试、强制返回、并发调试等。
2 GDB简介
差不多从3年前开始认识GDB吧,曾经有一段时间特别反感GDB,反感其简陋的代码阅读方式(仅能使用list命令查看), 更多的时候甚至使用“printf()”嵌入到代码中,查看流程、变量值,也绝不使用GDB!
直到最近几天吧,由于想要学习Nginx,查看启动流程,所以不得不重新捡起GDB,突然间gdbtui跑到我的视野中! 哇塞!真的眼前一亮的感觉,突然感觉到原来她有着还不错的代码查看方式!所以就来系统地学一下,下面做些笔记~
3 常用命令
3.1 调试前准备
一般直接编译产生的程序是无法调试的,需要我们加入调试参数,才可以生成调试信息:
g++ main.cpp -o main -g #一般调试信息
g++ main.cpp -o main -g3 #调试宏
上面的-g
和-g3
就是调试参数了,
-g命令与-o命令一样,是分级的,默认等级为g2不包含宏信息
如果要调试宏,要使用-g3
命令!(调试宏的方法:macro exp SUM(3,5))
3.2 开始调试
好了,准备工作做完,就可以开始调试了,首先指定需要调试的程序:
gdb <-tui> <-command=cfile> <file>
命令就是要在可执行文件file上运行GDB,首先要从文件cfile中读取命令。-tui表示显示源码界面调试!
上面”<>”中的内容都是可选的,cfile里面是GDB命令,gdb一启动,就会自动执行里面的内容啦! 这样就不用每次调试都重新打断点了吆~ 是不是很方便?
- 调试新程序
如果调试未运行的程序,首先向main方法传递参数,然后就可以开始运行了:
set args arg1 arg2 设置main()参数
show args 查看设置好的参数
run(r) <filename> 开始运行程序
info program 来查看程序状态
- 调试已运行的程序
如果要调试已经运行的程序,需要直到程序的PID!
gdb PID process-id 挂接正在运行的程序。
attach process-id 命令来挂接进程的PID。
detach process-id 取消挂接的进程。
run之后,如果不打断点,程序立马就运行完了,什么也看不见呢!所以要先看看下面的内容再run:
3.3 断点的生命周期
设置断点的几种方式
一般断点
break(b) line_number (可以精确定位 文件名:行号)
break(b) function(函数名) 所有重载函数均会被设置为断点
条件断点
break(b) 30 if num_y == 1 用break if 条件语句可以用括号()括起来,也可以不用。
condition 1 num_y == 1 只有当满足条件num_y == 1时,GDB才会在断点1处暂停程序的执行。
临时断点
tbreak num 断点在首次到达该指定行后就不再有效。
查看断点
查看所有断点
info break
info breakpoint
info b
断点命令
commands 1 #breakpoint_number
silent
printf "命令和C中printf函数类似,只是括号是可选的"
end
断点命令算是GDB特有的吧!它让GDB在每次到达某个断点时自动执行一组命令, 从而自动完成某一任务。比如可以自动显示变量值呀。
禁用&删除断点
禁用
disable breakpoint-list
disable (禁用所有现存断点)
enable breakpoint-list (启用断点)
enable once breakpoint-list (在下次引起GDB暂停执行后禁用:)
删除
delete(d) (删除所有)
delete(d) break_point_num
依据位置删除断点:
clear filename:funtion
clear filename:line_number
clear 清除GDB将执行的下一个指令处的断点
可以一次取消/删除多个断点,用空格分隔开,还可以使用 1-5!下面类似的操作也是相同的~
3.4 变量操作
断点停住了,我们就可以看具体变量的数值了!
查看变量
whatis x (查看类型)
print(p) x (查看值)
修改变量
set var x=4 (令x=4)
加入var是因为set是gdb的命令,var的作用是告诉GDB,x不是GDB的参数,而是程序的变量!
3.5 继续运行
断点的作用除了看变量值,更重要的就是看程序的执行方向了! 单步执行,能够看出程序是否在正确的方向上运行!
单步执行:
step(s) n 进入函数执行 n可选,表示执行n行
next(n) n 不进入函数执行, n可选,表示执行n行
继续执行:
continue(c)n 直到遇到另一个断点或者程序结束。可选的数值参数n,要求跳过下面n个断点。
跳出:
finish(fin) 跳出当前的函数。
until(u) 跳出当前循环
小提示:如果嫌弃每次都输入n+Enter执行,很麻烦,也可以直接按Enter,就会默认执行上一次的命令啦!
3.6 监视点的生命周期
当然关键变量的值是影响程序走向的重要原因!对变量进行监视,在变量在值发生变化时,会打印变化前后的值。 能够帮助我们分析程序为什么会在错误的道路上越走越远~
添加监视点
watch <表达式>(变量名或表达式)
deltet <变量>
info registers (监视寄存器)
查看监视点
info b
info watchpoints
删除监视点
deltet(d) n(监视点编号)
注意:监视点和断点是统一组织的,共享编号,所有查看、删除point的方式2着通用!
3.7 自动显示的生命周期
上面变量是在值改变时,会主动提醒我们,是异步式的! 当然我们也可以使用同步方式,主动请求,每次到了断点强制打印变量值!
添加变量
display expr
display/fmt expr
display/fmt addr
查看自动显示
info display
删除自动显示
undisplay nums
delete display nums
失效自动显示
disable display dnums
enable display dnums
expr是一个表达式,fmt表示显示的格式,addr表示内存地址, 当你用display设定好了一个或多个表达式后,只要你的程序被停下来,GDB会自动显示你所设置的这些表达式的值。
3.8 查看源代码
下面就是我最早为什么嫌弃GDB的原因了,还是说一下:
list lineNum 显示lineNum的前后代码
list + 显示当前行前代码
list - 显示当前行后代码
list function 显示某一函数的代码
set listsize count 设置显示代码的行数,默认10行,感觉远远不够,我觉得至少100行,哈哈,视野要开阔
show listsize 显示显示代码的行数
list num1,num2 显示从num1到num2的源代码行
3.9 显示调用栈
为什么要显示调用栈呢?
显示调用顺序,就可以看出程序在何处停止(即断点的位置),以及程序的调用路径,方便理解程序架构。
有3种方式看当前栈:
backtrace(bt) == where == info stack
backtrace 显示所有栈帧。
backtrace N 只显示开头 N 个栈帧。
backtrace -N 只显示最后 N 个栈帧。
backtrace full 不仅显示backtrace,还有显示局部变量。
栈跳转:
up n 向栈的上面移动n层,可以不打n,表示向上移动一层。
down n 向栈的下面移动n层,可以不打n,表示向下移动一层。
当前栈层的信息:
frame (f)
info frame
info f
frame (f) 查看当前栈层的信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。\
3.10 函数操作
强制返回
return expression
使用return命令取消当前函数的执行,并立即返回,如果指定了,那么该表达式的值会被认作函数的返回值。
强制调用
call fun
3.11 并发程序
- 多线程
如果程序是多线程的话,就可以定义断点是否在所有的线程上,或是在某个特定的线程
info threads (查看线程ID)
break num thread threadno
break num thread threadno if …
如果不指定‘thread threadno ’ 断点设在所有线程上面。
- 多进程
多进程执行相同代码,fork之后留在那个进程执行很重要!follow-fork-mode 参数可以解决
set follow-fork-mode parent:fork之后继续调试父进程,子进程不受影响。
set follow-fork-mode child: fork之后调试子进程,父进程不受影响
break 子进程行号
3.12 自定义命令
自定义命令的格式为
define <command>
<code>
end
document <command>
<help text>
end
GDB可自定义宏命令来简化方便调试过程.命令定义在文件 .gdbinit文件中.
在插入较多的断点程序中,可以批量执行,不用每次启动调试都插入很多次断点
4 总结
大部分调试命令,我都亲手试验过,感觉VC有的功能,他基本都有吧,但还是感觉不怎么好用!好尴尬! 但总比不断地“printf()”好用,哈哈,要多联系!