一. 程序的两种发布方式
debug & release
-
debug 版本 : 程序本身会被加入更多的调试信息, 以便于调试.
-
release 版本 : 不会添加任何调试信息, 不可调试.
gdb [可执行文件名] 进入调试页面
.
但是我们发现 Linux 会发出 no debugging symbols found (没有找到调试符号) 的信息.
这是因为在 Linux 下 gcc
默认生成的可执行程序是 release 版本的, 是不可被调试的. 如果想生成 debug 版本, 就需要在使用 gcc 生成可执行程序时加上 -g
选项, 生成 debug 版本.
通过 ll
指令可以看到, 对同一份源代码分别生成其 release 版本和 debug 版本的可执行程序, debug 版本发布的可执行程序的大小比 release 版本发布的可执行程序的大小要大一点, 其原因就是以debug 版本发布的可执行程序当中包含了更多的调试信息.
readelf 指令
Linux 有一个很不错的工具叫 readelf
, 它是专门针对 ELF 文件格式的解析器, gdb
所调试的可执行文件就是 ELF 文件, readelf -S [ELF格式文件]
查看 ELF 文件的段表 (Section Table).
readelf -S hello-debug | grep debug
查看可执行文件 hello-debug
中与调试信息有关的段.
readelf -S hello-debug
显示 hello-debug
文件段表.
其中C语言编译过程后执行语句编译所形成的机器代码存放在 .text 段, 已初始化的全局变量和局部静态变量存放在 .data 段, 未初始化的全局变量和局部静态变量存放在 .bss 段.
下图列举了 ELF 的一些其他常见段.
二. gdb 命令汇总
进入gdb & 退出gdb
进入 gdb
gdb [可执行文件]
- 进入gdb
退出 gdb
q(quit)
- 退出gdb
显示
行号显示
l(list) 行号
- 显示所调试可执行文件对应的源文件代码, 每次显示10行
若是直接 l
, 会随机显示出可执行文件对应源文件中的随机 10 行内容.
l 1
, 会从源文件首行开始显示 10 行内容.
多次 Enter
或 l
后即可显示源文件全部内容, 因为 gdb 会记忆上次输入的指令, 且若未给出行号则默认从上次的位置往下显示.
l(list) 函数名
- 显示所调试可执行文件对应的源文件代码, 每次显示10行
l Add
, 显示对应的 10 行内容.
打印变量
p(print) 变量名
- 打印变量值
继续单步调试代码, 再打印变量 i
和 sum
的值, 发生了改变.
但是每次打印显示非常麻烦, 于是我们使用跟踪变量的方式.
跟踪 & 取消跟踪变量
display 变量名
- 跟踪查看一个变量, 每次单步调试都显示变量的值
先 display 的变量在下面, 模拟的是一个压栈的过程.
undisplay 变量名编号
- 取消对先前设置的那些变量的跟踪
查看函数调用 & 栈帧
bt
- 查看各级函数调用及参数
i(info) locals
: 查看当前栈帧当中局部变量的值.
断点
设置断点
b(break) 行号
- 在对应行号处打断点
b(break) 函数名
- 在对应函数函数体第一行处打断点
查看断点信息
info(i) b(break)
- 查看断点的信息
如下为显示断点信息的具体含义.
- Num - 编号
- Type - 类型
- Disp - 状态
- Enb - 是否可用
- Address - 地址
- What - 在此文件的哪个函数的第几行
提示: 若是在调试过程中命中过断点, 则还会显示断点被命中的次数.
删除断点
d 要删除断点的NUM
(不可以d 要删除断点的行号
)
d(delete) break
- 删除所有的断点
此时若继续设置断点, 就可以发现其编号为 4, 而并不是从 1 开始, 这是因为我们没有退出过 gdb, 所以新断点的编号会接着上一次的最大编号继续往下.
开启 & 禁用断点
disable b(breakpoints)
- 使所有断点无效
enable b(breakpoints)
- 使所有断点有效
disable b(breakpoint) NUM
- 使指定断点无效
enable b(breakpoint) NUM
- 使指定断点有效
调试
运行 & 调试
r(run)
- 无断点直接运行; 有断点从第一个断点处开始运行
无断点, 使用 r
指令运行, 直接运行到程序结束.
有断点, 使用 r
指令运行, 会在设置断点处停下来.
逐过程 & 逐语句
n(next)
- 逐过程调试
逐语句调试, 碰到断点停止, 不进入函数内部.
s(step)
- 逐语句调试, 一次走一行代码, 会进入函数内部
进入函数内部, 逐行调试.
修改变量的值
set var 表达式
- 修改变量的值
指定行号跳转
until 行号
- 进行指定位置跳转, 执行完区间代码
强制执行函数
finish
- 在一个函数内部, 执行到当前函数返回
获取到返回值后, 继续打印.
跳转至下一断点
c(continue)
- 从一个断点处, 直接运行至下一个断点处