Linux 调试器 gdb
一. 程序的两种发布方式
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)- 从一个断点处, 直接运行至下一个断点处
