使用 GDB 恢复实时数据
我最近遇到个问题,有一个 运行很长时间的程序 ,它的输出被卡在一个C语言 FILE 变量的 buffer 中了,这个程序已经运行两天了,它会把结果直接输出来,但是最后几k的内容要等到程序完成它的清理动作并退出后才能输出来,而这个清理动作耗时可能要花几天(甚至更多),这个问题本身要修复很简单 — 这个清理的动作其实完全是没有必要的 — 但是我不想又要花两天的时间把结果重新再计算一遍。
下面这段代码简单模拟一下这个问题。第一个循环代表了长时间的运算过程,而后面的无限循环代表了那个无尽的清理动作。
#include <stdio.h> int main(void) { /* Compute output. */ for (int i = 0; i < 10; i++) printf("%d/%d ",i,i * i); putchar('\n'); /* "Slow" cleanup operation ... */ for (;;) ; return 0; }
Buffered Output Review
printf
and putchar
这两个 C库函数,都会以某种方式来缓存输出的内容。这意味着不是每次调用这些函数都会实际输出数据。另一方面,POSIX定义的函数 read
和 write
则是不带buffer的系统调用,由于系统调用相对来说比较昂贵,因此一般会用带缓存的输入/输出来将大量针对小 buffer 的系统调用转换成一个针对大buffer的系统调用。
一般来说,若程序的标准输出为终端的话,它是按行来缓存的,毕竟当程序完成了一行的输出内容后,用户很可能立即就想看到输出结果,因此,若你编译该程序后是在终端上直接运行改程序的话,那么很可能在程序陷入无限循环之前就已经把结果输出来了。
$ cc -std=c99 example.c $ ./a.out 0/0 1/1 2/4 3/9 4/16 5/25 6/36 7/49 8/64 9/81
然而若把标准输出重定向到文件或管道中的话,这个输出很有可能就会被缓存起来了,这个缓存大小一般为 4KB,这样一来,不管你等多长时间,改程序的输出始终都为空,真正的输出内容被卡在进程内存中的一个 FILE 对象的 buffer 中了。
$ ./a.out > output.txt
修复这个问题的主流方法是调用 fflush
函数,在开始一段耗时漫长而无输出结果的操作前可以用它来强行将buffer中的内容输出,可惜,我早在两天前并没有想到这一出。
Debugger to the Rescue
幸运的是,有一个东西可以暂停正在运行的程序并帮我们维护程序的状态:那就是调试器。
第一步,找出进程号(上面输入 output.txt 的那个进程)。
$ pgrep a.out 12934
现在让启动 GDB,让它 attach 那个进程,这会暂停程序的运行。
$ gdb ./a.out Reading symbols from ./a.out...(no debugging symbols found)...done. gdb> attach 12934 Attaching to program: /tmp/a.out,process 12934 ... snip ... 0x0000000000400598 in main () gdb>
到了这一步,我就可以手工检查 stdout
的FILE结构,并从中抽取出buffer中的内容了,不过最简单的方法是执行我一开始忘掉的条语句 fflush(stdout)
。
gdb> call fflush(stdout) $1 = 0 gdb> quit Detaching from program: /tmp/a.out,process 12934
程序依旧还在执行,我们已经得到结果了。
$ cat output.txt 0/0 1/1 2/4 3/9 4/16 5/25 6/36 7/49 8/64 9/81
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

上一篇: C语言中的交互式编程
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论