谁打印了这个字符串

前段时间在调试时遇到一个问题,运行程序出现错误,但并没有足够的信息来定位错误所在。可喜的是控制台上输出了一些可疑信息,只要找到了哪里打印了这些信息便有可能推断错误的原因。然而由于程序过于庞大,不可能一步一步跟踪调试去查找哪条语句执行后输出了这段字符串。尝试在所有的代码中搜索这段字符串也无功而返。后来突发奇想,能否在输出字符串时设置一个条件断点,只要输出的这段信息就中断,这样就可以在中断后找到何处打印了这些可疑信息,进而解决程序的问题了。
经过大量的搜索之后,在Stack Overflow上找到了答案,并且成功的解决了我的问题。被采用答案的作者Anthony Arnold由于十分喜欢这个问题,所以写了一篇关于它的博文。我也特别喜欢这个问题,之前遇到过多次,但都采用别的方式解决,而只有这个答案最完美。同时它综合运用了很多知识,能够给我们的调试带来不少启发。因为网上也没有再找到其它的解决方案,所以我决定翻译此文,后面对原文再进行其它平台的补充。离开学校之后第一次翻译,不好之处欢迎指正。
目录
调试STDOUT
前几天我遇到一个很有趣的Stack Overflow 问题,提问者希望GDB能够在一个特定的字符串写到stdout时中断程序。
除非你至少掌握了一些下面的知识,否则并不容易得到这个问题的答案:
- 汇编语言
- 操作系统
- C语言标准库
- GDB命令
我想出了一种可行的但是不可移植的解决方案,它依赖于x86指令集和Linux的系统调用write(2),所以本文的讨论限制在特定操作系统内核上的特定架构。
例子
我们使用下面的代码(定义在hello.c中)来演示如何用GDB在"Hello World!\n"写入stdout时中断。(feihu注:代码作了一定的修改,增加了另外一个输出字符串的函数,以更好的演示捕获特定字符串)
#include <stdio.h>
void test()
{
printf("Test\n");
}
int main()
{
printf("Hello World!\n");
test();
return 0;
}用下面的命令编译链接:
# gcc -g -o hello hello.c
用下面的命令调试:
# gdb hello
在write中设置断点
第一步,我们需要找出如何在有数据被写到stdout时中断程序。我们假设你调试代码的作者没有疯,他们采用了所选语言的标准用法来向stdout写数据(比如C语言中的printf(3)),或者他们直接调用系统调用write(2)。
实际上最终printf(3)也调用的是write(2),所以不管采用上面哪种方式都可以。
因此你可以在write(2)系统调用中设置一个断点:
$gdb break write
GDB也许会抱怨它并不知道write函数,但是它可以在将来这个函数有效时再在其中设置这一断点:
Function "write" not defined.
Make breakpoint pending on future shared library load? (y or [n])
这完全没问题,直接输入y即可。
在write中写到STDOUT时设置断点
一旦你能够在write函数中设置断点后,你需要设置一个条件,只有在写到stdout时才中断,这里有一点复杂。看看write(2)的帮助页面:第一个参数fd是要写的文件描述符,在Linux中,stdout的文件描述符是1(也许你使用的平台有所不同)。
于是你的第一反应是这样:
$gdb condition 1 fd == 1
但是很遗憾,这不起作用。(feihu注:会出现这样的错误No symbol "fd" in current context.)除非你非常幸运,否则不可能已经加载了write(2)系统调用的调试symbols,这意味着你不能以参数名来访问传给系统调用的参数。
获取系统调用参数
当然,还有其它的方法可以获取传给系统调用的参数,GDB提供了非常完善的手段让你来访问各种汇编寄存器,在这个问题中,我们感兴趣的是Extended Stack Pointer,也就是esp寄存器。(feihu注:这里仅适用于x86 32位系统,x86 64位系统的解决方案请向下看。)
当一个函数调用发生时,栈中储存了:
- 函数的返回地址
- 指向函数参数的指针
这意味着当调用write函数时,栈的结构可能像下面一样:

这是假设地址占4个字节,根据你自己的机器作相应的调整
所以现在你可以像这样设置条件断点:
$gdb break write if *(int *)($esp + 4) == 1
再一次声明,假设地址占4个字节
注意,$esp能够访问ESP寄存器,它将所有的数据都看成是void *。因为GDB不允许直接将void *转换成int型(这么做是对的),所以你需要先将...
剩余内容已隐藏