跟踪输出

跟踪输出

从终端运行时,hexdump不会像、、等^D那样对行开头的单个内容做出反应,除非还没有输入:catodbc

prompt% hexdump -C
<control-D>
prompt% hexdump -C
hello
<control-D><control-D> # a single ^D won't do
00000000  68 65 6c 6c 6f 0a                                 |hello.|
00000006

我可以在 fedora 28 和 debian 9 上重现这一点。尽管hexdump是由 Debian 提供的bsdmainutils,但在 bsd 上不会发生这种情况。

这只是一个错误吗?是否还有其他已知的程序会表现出这种行为?

答案1

感谢@JdeBP暗示,我能够创建一个小型测试用例,其功能与hexdump

#include <stdio.h>

int main(void){
        char buf[64]; size_t r;
        for(;;){
                printf("eof=%d, error=%d\n", feof(stdin), ferror(stdin));
                r = fread(buf, 1, sizeof buf, stdin);
                printf("read %zd bytes, eof=%d, error=%d\n",
                        r, feof(stdin), ferror(stdin));
                if(!r) return 0;
        }
}

当在基于 glibc 的系统(典型的 Linux 桌面)上运行时。

prompt$ ./fread-test
eof=0, error=0
<control-D>
read 0 bytes, eof=1, error=0

prompt$ ./fread-test
eof=0, error=0
hello
<control-D>
read 6 bytes, eof=1, error=0
eof=1, error=0
<control-D>
read 0 bytes, eof=1, error=0

当在 bsd、solaris、busybox (uclibc)、android 等上运行时:

prompt$ ./fread-test
eof=0, error=0
hello
<control-D>
read 6 bytes, eof=1, error=0
eof=1, error=0
read 0 bytes, eof=1, error=0

根据我对标准的不专业解释,这看起来像漏洞在 glibc(GNU C 库)中。

关于fread:

对于每个对象,应对 fgetc() 函数进行 size 调用,并将结果按读取顺序存储在恰好覆盖该对象的无符号字符数组中。

关于fgetc:

如果未设置stream指向的输入流的文件结束指示符并且存在下一个字节,fgetc() 函数应获取下一个字节

看起来,即使设置了 eof 指示符,glibc 也会尝试“获取下一个字节”。

确实,它实际上是GNU C 库中的错误,BSD 或 musl C 库中不存在。 早在2005年就已为人所知。 Ulrich Drepper 在 2007 年关闭了错误报告,但没有修复该错误。 2012年就讨论过,其中指出其他 C 库没有也没有这种行为,1999 C 标准对此非常具体,并且 Solaris 甚至为此有一个特殊的机制,当c99用作编译器而不是cc

终于在2018年修复了。 GNU C 库 2.28 版中已修复该问题。当前“稳定”版本的 Debian,版本 9,位于 GNU C 库 2.24 版本上,因此这个错误在被报告 14 年后仍然继续显现。

正如 GNU C 库讨论中所指出的,有可能性编写的软件需要 GNU C 库的怪癖,而不考虑其他 C 库(例如 musl)或其他平台上的行为。然而,在上述多年来的讨论中,并没有确定这样的计划。而一些程序被旧的 GNU C 库破坏,要求用户连续两次发出 EOF 信号,已被识别;包括hexdump这里的其他人patch2018 年在 StackOverflow上

答案2

跟踪输出


read(0, "hello\n", 1024)                = 6
read(0, "", 1024)                       = 0
read(0, "hello\n", 1024)                = 6
read(0, "", 1024)                       = 0
read(0, "", 1024)                       = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 11), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8dd4cef000
write(1, "0000000   h   e   l   l   o  \\n "..., 72) = 72
write(1, "000000c\n", 8)                = 8
exit_group(0)                           = ?
+++ exited with 0 +++

这里我做了 hello «ctrl-d» hello «ctrl-d» «ctrl-d» (第二个 hello 在日志输出中被截断)。所以看起来它是这样编程的。

运行 strace

  • 打开两个终端
  • 在第一种类型中ll /proc/self/fd/1
  • 在第二种类型中strace hexdump -C 2>/dev/pts/«number-at-end-of-output-of-previous-command»

相关内容