4

(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?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
ead
  • 32,758
  • 6
  • 90
  • 153
  • 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) – Michael Petch Jul 14 '16 at 17:07
  • If you review the [man page](http://man7.org/linux/man-pages/man3/exit.3.html) 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 syscall` – Michael Petch Jul 14 '16 at 17:30
  • 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\`?](https://stackoverflow.com/q/36032305). Also [Printf without newline in assembly](https://stackoverflow.com/q/8502945) re: calling fflush(NULL) in asm. – Peter Cordes Mar 27 '22 at 16:25

2 Answers2

5

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
ead
  • 32,758
  • 6
  • 90
  • 153
  • 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](http://stackoverflow.com/tags/x86/info) so we can find it easily and close future questions as duplicates of this. – Peter Cordes Jul 17 '16 at 05:08
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).

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
kirbyfan64sos
  • 10,377
  • 6
  • 54
  • 75
  • 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. – ead Jul 14 '16 at 17:01
  • @ead 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!) – kirbyfan64sos Jul 14 '16 at 17:03
  • 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). – Cody Gray - on strike Jul 14 '16 at 17:07
  • @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`. – Michael Petch Jul 14 '16 at 17:14
  • @MichaelPetch I was not aware, that `-lc` is linked dynamically. Thanks! – ead Jul 14 '16 at 19:33