Using printf in assembly leads to empty output when piping, but works on the terminal
Use call exit
instead of a raw _exit
syscall after using stdio functions like printf. This flushes stdio buffers (write
system call) before making an exit_group
system call).
(Or if your program defines a main
instead of _start
, returning from main
is equivalent to calling exit
. You can't ret
from _start
.) Calling fflush(NULL)
should also work.
As Michael explained, it is OK to link the C-library dynamically. This is also how it is introduced in the "Programming bottom up" book (see chapter 8).
However it is important to call exit
from the C-library in order to end the program and not to bypass it, which was what I wrongly did by calling exit-syscall
. As hinted by Michael, exit does a lot of clean up like flushing streams.
That is what happened: As explained here, the C-library buffers the the standard streams as follows:
- No buffering for standard error.
- If standard out/in is a terminal, it is line-buffered.
- If standard out/in is a not a terminal, it is fully-buffered and thus flush is needed before a raw exit system call.
Which case applies is decided when printf
is called for the first time for a stream.
So if printf_try
is called directly in the terminal, the output of the program can be seen because hello
has \n
at the end (which triggers the flush in the line-buffered mode) and it is a terminal, also the 2. case.
Calling printf_try
via $(./printf_try)
means that the stdout is no longer a terminal (actually I don't know whether is is a temp file or a memory file) and thus the 3. case is in effect - there is need for an explicit flush i.e. call to C-exit
.
The C standard library often contains initialization code for the standard I/O streams — initialization code that you're bypassing by defining your own entry point. Try defining main
instead of _start
:
.globl main
main:
# _start code here.
and then build with gcc try_printf.s -o try_printf
(i.e., without -nostdlib
).