GDB - <<软件调试的艺术>>读书笔记

GDB - <<软件调试的艺术>> 读书笔记

阙荣文 Que's C++ Studio 2017.9.10


0. gcc -g
必须使用 -g 选项编译程序生成"符号表"才能被调试.调试器利用符号表的内容识别函数名,变量名等标识.

1. 栈和帧
程序运行的大致流程: 加载程序文件,找到入口函数,把函数的地址,参数压入堆栈,开始执行该函数,对所有子函数都执行同样的流程.调试器的工作对象就是这个"栈",栈内每个函数调用的内容构成一个"帧".GDB [backtrace/bt] 显示整个栈(所有帧的集合)的内容, [frame n] 切换当前帧. GDB 的很多命令都以当前帧为上下文.比如 [print x] 可以打印变量 x 的值, GDB之所以能认识"x"是因为它可以通过符号表在当前帧内查找到变量名为 x 的变量或者 x 是一个全局变量; [info args] 打印当前帧(函数)的调用参数.
[backtrace/bt] - 如前所述.
[frame n] - 切换到第 n 帧(当前帧的序号为 0, 序号 1 为父帧依次类推)
[up] / [down] - 切换当前帧

2. 运行目标程序
用命令行参数 `gdb <filename>` 或者 gdb 启动后用命令 [file <filename>] 加载目标程序.
[run/r] - 运行目标程序.
[list/l] - 显示焦点文件的源代码,有很多动作会使GDB切换"焦点"文件,比如当前帧切换时焦点文件也会切换, [l filename] 命令列出指定文件的源代码也会使 GDB 焦点切换至该文件, [break] 命令如果不指定文件名,则默认是在焦点文件中添加断点.
[l func] - 显示函数 func 的源代码

3. 断点
GDB 中任何可以让程序暂停的命令都属于"断点",包括通常意义的断点 [break], 观察点 [watch], 捕获点 [catch].GDB 为所有类型的断点统一分配一个从 1 开始的序号. [info] 查看所有断点的序号.

[break/b filename:ln] - 在文件 filename 第 ln 行添加一个断点.如果 filename 没有指定则代指焦点文件.
[b filename:func] - 在函数 func 入口初添加一个断点.
[tbreak/tb] - 添加一次性(临时)断点.
[rbreak gr_func] - 在所有匹配 grep 风格的正则表达式 gr_func 的函数前插入断点.
[silent] - 减少断点触发时 GDB 的输出信息.

条件断点
[break ... if ...] - [b main if argc > 1] if 子句要求的 bool 值非常灵活,既可以用所有 C 风格的逻辑运算符,算术运算符还可以调用所有已经链接至目标程序的库函数和自定义函数.
[condition/cond 断点序号 条件] - 等同于[break if], [cond 3]可以删除断点3的触发条件,使之变为一个无条件断点.
[watch x] - 等价于 "break positon(x) if x changed" 实质上观察点就是一个条件断点,条件可以和 break if 句式一样复杂.观察点中涉及的变量 x 必须存在于当前帧内.

断点命令列表
GDB 把断点作为一个重要的节点,可以附加很多命令至断点,使断点触发时GDB自动运行这些命令.可以理解为关于该断点的"脚本".
[commands 断点序号]
... - 所有 GDB 命令, print cond 等等都可以.
... - 利用这个机制,把 print continue 命令附加至断点可以实现断点时自动打印变量后继续运行的效果
[end]

显示和删除断点
[clear] - 用和 [b] 同样的参数删除断点.
[info breakpoints] - 显示所有断点的信息.(info 命令可以显示很多内容,[help info] 查看详情)
[delete 1 2] - 用序号删除断点.删除1号,2号 2个断点,不指定序号则删除所有
[disable 1 2] - 禁用断点
[enable 1 2] - 启用断点, [enable once 3] 启用一次后禁用.

4. 从断点恢复执行
[next/n] - 单步执行(step over), [n 3] 执行3行后暂停
[step/s] - 单步执行(step in)
[continue/c] - 继续执行直到下一个断点
[until/u] - 继续执行到当前循环体外的第一条指令; [u 17] 继续执行到第17行.
[finish/fin] - 跳出当前函数(step out)

5. 检查和设置变量
打印
[print/p] - 打印变量值, [p *ptr] 还可以打印指针指向的内容(如果 ptr 是一个 struct 型指针则会打印所有域).
[p *x] - 如果 x 是一个动态分配的数组,那么等价于 [p x[0]], [p *x@5] 可以输出数组 x 的前5个值.
[display/disp] - [disp *ptr] 每次暂停(包括断点,n,s等)后打印 ptr 内容.
[printf] - 作为C程序员,无需多作解释.
[p/x y] - 以16进制格式显示 y 的值(当然也可以用 prinf 实现)
[call func(arg)] - 终极办法:在代码中实现 func 函数用于打印任意需要输出的内容,然后由 GDB 命令 [call] 调用之.
[ptype <struct/class>] - 打印 struct/class 的类型定义.
[info locals] - 列出当前帧的所有局部变量.

设置
[set x = 12] - 设置变量 x 的值
[set args 1 52 19 11] - 设置目标程序的命令行参数(当然 [run/r args] 就可以指定参数), [info args] 查看命令行参数.

6. 核心转储文件(core)
当程序崩溃时,shell会把程序调用栈写入磁盘作为一个"核心转储"文件,用 GDB 载入该文件就可以重现错误现场.
`ulimit -c` - 查看当前关于 core 的限制
`ulimit -c n` - 允许生成最大 nKB 的 core 文件
`ulimit -c unlimited` - 不限制大小
`gdb prog core`/`gdb -c core prog`/[core filename] 加载 prog 崩溃时的核心转储文件以恢复现场,之后可以用类似 [backtrace] 打印调用栈的内容等等.

7. 调试多线程程序
[info threads] - 显示所有线程列表, * 线程表示当前线程, [bt] 显示的是当前线程的调用栈.
[thread 3] - 切换到第3号线程的调用栈.
[break 88 thread 3] - 3号线程运行到第88行是触发断点.

8. 调试 GUI/TUI 程序
由于命令行窗口被占用,所以无法使用 printf 风格的调试,只能使用日志或者 GDB.我们在一个窗口运行命令 `tty`得到该窗口的 tty 终端号"/dev/pts/8",接下来在该窗口运行目标程序,然后在另外一个窗口中启动 GDB 并附加到目标进程.
[tty /dev/pts/8] - 附加 GDB 至指定终端.(书中还提到 tty 命令之后还要运行 `sleep 10000` 不明白是什么意思)
这么做的主要目的是使 gdb 和目标程序(target)的输出分开.同样的,先在一个窗口启动 target,然后 `gdb -p PID target` 通过 PID 附加调试 target 效果相同.

9. 其他工具
strace - 打印目标进程的系统调用记录.输出的信息中包括调用参数,返回值等,通过查看这些信息有可能发现一些异常状况.
ltrace - 打印库调用记录.和 strace 一样,它们的一大好处是不需要源代码.
lint 及其衍生工具(splint等) - 静态代码检查器.
mcheck - 检测 DAM (Dynamically Allocated Memory) 错误,包括内存泄漏,非法访问,重复释放.
    使用步骤:
    1. 设置环境变量 MALLOC_TRACE 为有效的文件名, mcheck 将把程序运行过程中检测到的 DAM 问题写入该文件.
    2. #include <mcheck.h> mcheck 工具是 GNU C 库的一部分.
    3. 在程序入口调用 mtrace()
    4. 运行目标程序
    5. 运行 Perl 脚本 mtrace(/usr/bin/mtrace) 分析上述文件.
PS: valgrind 用来检测内存泄漏也相当好使.

10.拾遗
`gdb -p PID prog` / `gdb prog PID` 附加调试进程
gdb -tui 或者启动后 <CTRL>XU 启动 GDB 的 TUI 模式,比普通命令行模式能稍微多显示一些信息.
<cr> 直接重复前一条命令.
[quit/q] 退出 GDB

软件调试正如书名,是一门艺术.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值