首发于 linux开发
GDB调试学习(以C++项目为例)

GDB调试学习(以C++项目为例)

背景

之前在做csapp课程实验的时候用到了部分linux环境下gcc调试的命令,现在由于实习要进行cpp开发,所以更需要系统的学习一下调试,之所以选择gdb,主要是因为 gdb功能强大,可以调试ada, c, c++, asm, minimal, d, fortran, objective-c, go, java,pascal 多种语言。

这里还抛出一个问题:为什么我们在已有非常好用的IDE调试的情况下还要去学习gdb调试?gdb调试更适用于什么样的场景?这是我目前还没有得到答案的,欢迎大家讨论。

启动调试

gdb test.cc

真正启动gdb调试的就是上面这个命令,但是你可能会遇到以下几个问题:

① gdb调试的对象是 编译后的可执行文件 而不是.cc文件

如果你操作的对象不是可执行文件,会给出提示:not in executable format: File truncated;

在linux中,可以通过”ll“ 命令查看目录下所有文件的详细信息,可执行文件后面会带有*

nlu_test为可执行文件,而1不是

没有生成可执行的文件一般都是 编译的问题 ,建议仔细查看编译的过程日志来分析,还有一个可能的原因是你 项目的可执行文件输出目录和读取目录不对应 ,可以检查下输出的路径

② 使用 GDB 调试某个可执行文件,该文件中必须包含必要的调试信息(比如各行代码所在的行号、包含程序中所有变量名称的列表(又称为符号表)等)

对于这种问题,一般会给出提示:no debugging symbols found,最常见的原因是 文件编译没有加 -g

解决方案: 对于文件的编译,需要在编译命令最后加上-g ,比如:

gcc main.cc -o main.exe -g

③ 检查文件是否能被调试的两种其他方法:

方法一: not stripped表示可以调试,stripped表示不可调试

file nlu_test 

方法二: readelf查看段信息,如果不包含任何debug就是有问题的

readelf -S nlu_test | grep debug

运行调试

run <要读取的输入/参数>

PS:可以在run的时候带参数,也可以在gdb的时候就提前set args,这取决于项目需求和自己的习惯

调试已经在运行的程序:先根据进程名查找编号再针对编号进行调试

$ ps -ef|grep 进程名
$ gdb
(gdb) attach <上面查询到的进程编号>

断点设置

常用操作

info breakpoints  # 查看已经设置的所有断点信息
b 9               # 根据行号设置断点
b test.cc:9       # 根据行号设置断点
b file_load       # 根据函数名设置断点
tb 9              # 根据行号设置临时断点
tb test.cc:9      # 根据行号设置临时断点
watch a           # 根据表达式(a)变化设置断点
disable           # 禁用所有断点
disable bnum      # 禁用标号为bnum的断点
enable            # 启用所有断点
enable bnum       # 启用标号为bnum的断点
enable delete bnum      # 启动标号为bnum的断点,并且在此之后删除该断点
clear             # 删除当前行所有breakpoints
clear function    # 删除函数名为function处的断点
clear filename:function # 删除文件filename中函数function处的断点
clear lineNum     # 删除行号为lineNum处的断点
clear filename:lineNum # 删除文件filename中行号为lineNum处的断点
delete            # 删除所有breakpoints,watchpoints和catchpoints
delete bnum       # 删除断点号为bnum的断点

跳过多次设置断点(适合循环)

ignore 1 30       # 跳过断点编号为1的断点30次

根据条件设置断点

b test.cc:9 if b == 0 # 如果b等于0就在第9行停住

根据规则设置断点

rbreak regex*    # 对所有调用regex函数的地方都打上断点 

需要注意的是,此处打断点只有一个标号,但是会有多个断点,*有点像是断点指针的意思

变量查看

p a           # 查看变量a的值
p test.cc:a   # 查看特定文件下的a的值(防止命令重复)
p *point      # 打印指针指向的内容
p *point@10   # 如果打印的是一个数组,可以@后面跟长度
display a     # 只要有断点,就会打印a的值
info display  # 查看.....
info register # 查看寄存器的内容

按照特定格式打印变量

  • x 按十六进制格式显示变量
  • d 按十进制格式显示变量
  • u 按十六进制格式显示无符号整型
  • o 按八进制格式显示变量
  • t 按二进制格式显示变量
  • a 按十六进制格式显示变量
  • c 按字符格式显示变量
  • f 按浮点数格式显示变量

单步调试

n       # 单步调试
n 2     # 2次单步调试
s       # 单步进入——会进入到内层的函数中
finish  # 如果没有函数源码,需要跳过该函数执行,可使用finish命令,继续后面的执行
c       # 继续到下一个断点
u 29    # 直接断点到29行
skip function swap  # 跳过所有的swap函数
info skip  # 查看所有的skip

源码的查看

set listsize 20 # 设置源码一次列出的行数