Using printf in assembly leads to empty output when piping, but works on the terminal
Asked Answered
R

2

5

(No output even on the terminal when the output doesn't include a newline has the same cause.)


I try to use printf from my assembler code, this is a minimal example which should just print hello to stdout:

.section  .rodata
hello:
    .ascii  "hello\n\0"
.section .text
    .globl _start        
_start:
    movq $hello, %rdi     # first parameter
    xorl %eax, %eax       # 0 - number of used vector registers
    call printf        
#exit   
    movq $60, %rax
    movq $0, %rdi
    syscall

I build it with

gcc -nostdlib try_printf.s -o try_printf -lc

and when I run it, it seems to work: the string hello is printed out and the exit status is 0:

XXX$ ./try_printf
hello
XXX$ echo $?
0
XXX$

But when I try to capture the text, it is obvious, that something is not working properly:

XXX$ output=$(./try_printf) 
XXX$ echo $output

XXX$ 

The variable output should have the value hello, but is empty.

What is wrong with my usage of printf?

Ringworm answered 14/7, 2016 at 16:30 Comment(3)
Using syscall to exit doesn't flush the output buffers used by the C runtime. Replace the exit syscall with a call exit (exit is part of the C library as well)Mckenzie
If you review the man page for exit (exit(3)) you'll find this in the description All open stdio(3) streams are flushed and closed. Files created by tmpfile(3) are removed. . This isn't guaranteed when using movq $60, %rax movq $0, %rdi syscallMckenzie
A C version of basically same question (with output not ending with newline so it fails even on a terminal): Why is this simple code working with `exit` and is not working with `_exit`?. Also Printf without newline in assembly re: calling fflush(NULL) in asm.Hilmahilt
R
6

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:

  1. No buffering for standard error.
  2. If standard out/in is a terminal, it is line-buffered.
  3. 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.

Ringworm answered 16/7, 2016 at 21:52 Comment(1)
Thanks for writing up a nice canonical answer to the exit vs. _exit and stdio buffering question. Added to the x86 tag wiki FAQ section so we can find it easily and close future questions as duplicates of this.Hilmahilt
M
2

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).

Min answered 14/7, 2016 at 16:54 Comment(5)
Thanks for the suggestion, but I would like to use printf without the C-runtime and think it should be possible. But you are right, my code seems to miss some initialization/finalization usually done by the C-runtime.Ringworm
@Ringworm Unfortunately, it's kind of a given. You're linking with the C standard library anyway, which is the majority of the runtime's size. Take a shot and see how it goes. (You could always use the write syscall directly!)Min
You cannot use printf (or other C runtime functions) without linking the C runtime. Even if you could, it would obviously be a bad idea. The only way to work around this would be to call operating-system API functions instead (which are just wrappers around C runtime functionality linked into the OS).Jezabella
@CodyGray : He is linking the C library with -lc . On Linux you can get away with it if he isn't building a static executable. When linking dynamically the C libraries shared object will have its initialization routine called when loaded by the dynamic linker. That has the side effect of setting things up to the point that printf can work. But I do agree if your going to call C library, don't circumvent the C runtime initialization. Use main function and remove the -nostdlib.Mckenzie
@MichaelPetch I was not aware, that -lc is linked dynamically. Thanks!Ringworm

© 2022 - 2024 — McMap. All rights reserved.