Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
369 views
in Technique[技术] by (71.8m points)

linux - Using printf in assembly leads to empty output when piping, but works on the terminal

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
"
.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?

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...