更新

2024-07-05 本书已经出版 image 由于本库的草稿是我之前一个人写的,所以质量和正确性都不如经过两位作者和出版社编辑审阅和校正过的书稿。 如果你想阅读更加完善的版本,推荐购买正版书籍。

改变运行和副作用

调试器通常用来观察一个被跟踪的进程的状态。它也可以改变debugee的状态,从改变它原本要执行的正常运行。这个方法创新性的使用是无限的。比如,验证当内存消耗完的时候程序的错误,调试器可以简单地设置函数malloc的返回值为NULL。它是检查一些难以或者昂贵地模拟的特别案例的快速和实惠的方式。

调试器提供了一些改变程序运行的方式。最简单的是设置变量一个新的值。调试器通过调试符号弄清楚一个变量的内存地址,接着通过内核的帮助如ptrace方法覆盖目标进程的内存。下面的命令赋予变量gFlags值5。

(gdb)set var gFlags=5

改变线程的上下文肯定会改变程序的运行。比如,程序计数器,也就是下一条运行的指令,可以设置为另外一个指令地址。这个特性一个常用情形是重新运行一段已经运行过的代码,用来更近地查看发生了什么。如果重新运行的代码包含了一个断点,它会再次被碰到。下面的命令设置当前的线程恢复到文件foo.c第123行运行。

(gdb)jump foo.c:123

上面的命令仅仅改变线程的程序计数器。剩下的线程上下文不变。当前函数的栈帧仍然是线程栈的顶上帧。如果用户在上面的jump命令跳到了另外一个函数的地址,后果是不可预测地,取决于两个函数的参数和局部变量是如何布局的。除非你知道函数调用习惯的所有细节,跳到另外一个函数不是一个好的主意。

当被调试进程已经停止,你可在调试器内调用任何函数。调试器在当前线程的最里层的帧为调用的函数创建一个新的栈帧。注意,调用C++类方式有点特别,因为它“秘密地”把this指针当成了被调用函数的第一个参数。另外一方面,一个C函数更直观。下面的例子调用了函数malloc来分配8字节内存块。调试器打印出返回的内存块地址。

(gdb) print /x malloc(8)
$1 = 0x501010

如果调用的函数有副作用,那么它会暗地里改变程序的行为。比如,下面的条件监测点开启一个拼凑的追踪和日志。每次变量sum改变的时候,gdb命令会调用函数Logme

(gdb)watch sum if Logme(sum) > 0