为什么我不能将汇编器输出通过管道传输到标准输出?

Why can't I pipe assembler output to stdout?

[编辑]
这只是我进行的一个虽然实验,我想看看我是否可以通过 /lib64/ld-linux-x86-64.so.2 进程替换来欺骗内核从未命名的管道中执行 elf,我知道这是一个机会在黑暗中,但我只是希望看看是否有人能给我一个关于为什么它不起作用的答案

$ /lib64/ld-linux-x86-64.so.2 <(gcc -c -xc <(echo $'#include <stdio.h>\n\nint main(){\nprintf("I work\n");\nreturn 0;\n}') -o /dev/stdout)
/tmp/ccf5sMql.s: Assembler messages:
/tmp/ccf5sMql.s: Fatal error: can't write /dev/stdout: Illegal seek
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660
/tmp/ccf5sMql.s: Fatal error: can't close /dev/stdout: Illegal seek
/dev/fd/63: error while loading shared libraries: /dev/fd/63: file too short

我想这可能是可能的,因为我得到的结果不同。

$ /lib64/ld-linux-x86-64.so.2 <(gcc -fPIC -pie -xc <(echo $'#include 
<stdio.h>\n\nint main(){\nprintf("I work\n");\nreturn 0;\n}') -o 
/dev/stdout|cat|perl -ne 'chomp;printf')
/dev/fd/63: error while loading shared libraries: /dev/fd/63: ELF load 
command past end of file
$ /lib64/ld-linux-x86-64.so.2 <(gcc -fPIC -pie -xc <(echo $'#include 
<stdio.h>\n\nint main(){\nprintf("I work\n");\nreturn 0;\n}') -o 
/dev/stdout|cat|perl -0 -ne 'chomp;printf')
/dev/fd/63: error while loading shared libraries: /dev/fd/63: ELF file ABI 
version invalid

所以我在使用 ASM 时发现您不能 assemble 或 link 输出到标准输出。

$ as /tmp/lol.s -o /dev/stdout
/tmp/lol.s: Assembler messages:
/tmp/lol.s: Fatal error: can't write /dev/stdout: Illegal seek
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660
as: BFD version 2.25.1-22.base.el7  assertion fail elf.c:2660


as /tmp/lol.s -o /tmp/test.o
$ ld /tmp/test.o -o what -lc
ld: warning: cannot find entry symbol _start; defaulting to 00000000004002a0


$ exec 9< <(ld /tmp/test.o -o /dev/stdout -lc)
ld: warning: cannot find entry symbol _start; defaulting to 00000000004002a0
ld: final link failed: Illegal seek

给出代码如下:

.file   "63"
.section        .rodata
.LC0:
.string "I work"
.text
.globl  main
.type   main, @function
main:
.LFB0:
.cfi_startproc
pushq   %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
movl    $.LC0, %edi
call    puts
movl    [=13=], %eax
popq    %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size   main, .-main
.ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)"
.section        .note.GNU-stack,"",@progbits
.file   "63"
.section        .rodata

谁能告诉我为什么不能将 assemble 个对象或 link 个对象输出到标准输出?请尽可能深入。要查看编​​译器生成该代码的完整过程,您可以使用以下命令:

$ exec 7< <(gcc -c -xc <(echo $'#include <stdio.h>\n\nint main(){\nprintf("I work\n");\nreturn 0;\n}') -o /dev/stdout)

如果你 assemble 和 link 我之前提供的程序集并且想要正确执行它你需要调用 /lib64/ld-linux-x86-64.so.2 /path/to/output 否则它只会说坏精灵解释器。

# ./what
bash: ./what: /lib/ld64.so.1: bad ELF interpreter: No such file or directory

# /lib64/ld-linux-x86-64.so.2 ./what
I work

您不能将汇编器输出通过管道传输到标准输出,因为自古以来(可能是 1960 年代)assemblers work generally in two passes (and not only on input, but also on output). So ability to seek (both input and output, using lseek(2)) 就需要。否则他们需要将大部分输入和输出数据保存在内存中。

请记住,目标文件不仅包含数据(例如机器指令、只读常量),还包含 relocation 信息。

/tmp/lol.s: Fatal error: can't write /dev/stdout: Illegal seek

这说明 as 程序需要查找文件(例如使用 lseek(2))。

也许您想在内存中生成机器代码。为此使用一些 JIT compilation library like libgccjit or asmjit.

顺便说一句,您可能想了解 gcc 是如何编译一个简单的 C 程序的。为此用 gcc -v 编译它并注意一些 crt0 的东西是链接的。

如果出于性能原因考虑使用管道,请改用某些 tmpfs 文件系统。那里的文件保留在内存中(因此在关机时丢失)并且很快,因为没有执行磁盘 IO。

你甚至可以在这样的文件系统中生成一些 C 文件,然后要求 gcc 编译它(也许作为 plugin). See also this.

... If I could trick the kernel in to executing an elf from an unnamed pipe

不,你不能。一个 ELF executable needs to be seekable too, because the kernel is, at its execve(2) time, setting up​‎​‎​‎​‎ a fresh virtual addresss space, using something close to mmap(2) 内部。换句话说,execve 正在设置多个内存映射。

研究进程的虚拟地址 space。阅读 proc(5),然后尝试 cat /proc/$$/maps(并用更有趣的 pid 替换 $$)。

阅读 Operating Systems: Three Easy Pieces(可免费下载)您应该会感兴趣。