[edit]
This was just kind of a though experiment I had where I wanted to see if I could trick the kernel in to executing an elf from an unnamed pipe with process substitution with /lib64/ld-linux-x86-64.so.2, I knew it was a shot in the dark but I was just hoping to see if anyone could give me an answer as to why it didn't work
$ /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
I figured that it may have been possible due to varying results I was getting.
$ /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
So I was playing around with ASM and noticed that you can't assemble or link output to stdout.
$ 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
Given the code as follows:
.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 $0, %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
Can anyone tell me why it isn't possible to assemble objects or link objects to stdout? Please be as in depth as possible. To see the full process that a compiler goes to in order to generate that code, you can use the following:
$ exec 7< <(gcc -c -xc <(echo $'#include <stdio.h>\n\nint main(){\nprintf("I work\\n");\nreturn 0;\n}') -o /dev/stdout)
If you assemble and link the assembly I provided earlier and want to execute it properly you'll need to call /lib64/ld-linux-x86-64.so.2 /path/to/output otherwise it will just say bad elf interpreter.
# ./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
You can't pipe assembler output to stdout, because since immemorial times (1960s probably) 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)) is required. Otherwise they would need to keep most of the input and output data in memory.
Remember that an object file contains not only data (e.g. machine instructions, read only constant) but also relocation information.
/tmp/lol.s: Fatal error: can't write /dev/stdout: Illegal seek
This illustrates that the as
program needs to seek files (e.g. using lseek(2)).
Perhaps you want to generate machine code in memory. For that use some JIT compilation library like libgccjit or asmjit.
BTW, you might want to understand how gcc
is compiling a simple C program. For that compile it with gcc -v
and notice that some crt0 thing is linked.
If you considered using pipes for performance reasons, use some tmpfs filesystem instead. The files there stay in memory (so are lost at shutdown) and are quick because no disk IO is performed.
You could even generate some C file in such a file system, then ask gcc
to compile it (perhaps as a plugin). See also this.
... If I could trick the kernel in to executing an elf from an unnamed pipe
No, you can't. An 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) internally. In other words, execve
is setting up several memory mappings.
Study the virtual address space of your processes. Read proc(5), then try cat /proc/$$/maps
(and replace $$
with a more interesting pid).
Reading Operating Systems: Three Easy Pieces (freely downloadable) should be interesting to you.