从终端运行时,hexdump
不会像、、等^D
那样对行开头的单个内容做出反应,除非还没有输入:cat
od
bc
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
这里的其他人patch
2018 年在 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»