5

I have a Linux x86-32 GAS assembly program terminating like this:

movl $1, %eax
movl $0, %ebx # argument for _exit
int $0x80

When I exit like this, the program functions like normally, but if I try to read the stdout output, I get nothing (using i.e. less or wc).

I tried compiling a minimal C program and comparing the strace outputs. The only difference I found was, that GCC made the C program (int main() { printf("donkey\n"); }) implicitely exit with exit_group(0) in the strace output.

I tried modifying my ASM program to exit with call exit instead of the raw syscall. The stdout was now readable as normal.

Test case

.data
douout: .string "monkey\n"
.text
.globl main

main:

pushl $douout
call printf
# Exit
movl $1, %eax
movl $0, %ebx
int $0x80

Compile and run:

$ yasm -g dwarf2 -f elf -p gas t.asm && gcc -g -melf_i386 -o t t.o && ./t | wc -c
0

Expected:

7

EDIT:

I tried calling both tcflush and fflush, and I still have the problem. With fflush I even get a segfault.

0xb7e9e7c9 in _IO_fflush (fp=0x804a018) at iofflush.c:42
42  iofflush.c: No such file or directory.
    in iofflush.c
(gdb) bt
#0  0xb7e9e7c9 in _IO_fflush (fp=0x804a018) at iofflush.c:42
#1  0x08048434 in main () at t.asm:12
(gdb) frame 1
#1  0x08048434 in main () at t.asm:12
12  call fflush
(gdb) list
7   
8   pushl $douout
9   call printf
10  # Exit
11  movl $0, %eax
12  call fflush
13  movl $1, %eax
14  movl $0, %ebx
15  int $0x80

EDIT2:

Okay, it works now everyone. I was using the wrong calling convention that I copied from here: Printf without newline in assembly

The argument for fflush should be on the stack, as usual.

$ cat t.asm 
.data
douout: .string "monkey\n"
.text
.globl main

main:

pushl $douout
call printf
# Exit
pushl $0
call fflush
movl $1, %eax
movl $0, %ebx
int $0x80
$ yasm -g dwarf2 -f elf -p gas t.asm && gcc -g -melf_i386 -o t t.o && ./t | wc -c
7
$

Thanks everyone, especially nos.

Community
  • 1
  • 1
Janus Troelsen
  • 20,267
  • 14
  • 135
  • 196

3 Answers3

3

when you pipe stdout to wc, stdout becomes fully buffered.

_exit terminates the process immediately and does not run atexit() and other cleanup handlers. The runtime will register such handlers to be run on exit that flushes open FILE*, such as stdout. When those handlers does not get executed on exit, buffered data will be lost.

You should see output if you call fflush(stdout) after the printf call, or if you just run the program in a consol without piping the output to another program - in which case stdout will normally be line buffered, so stdout is flushed whenever you write a \n

nos
  • 223,662
  • 58
  • 417
  • 506
  • How would you call ``fflush(stdout)``? According to ``unistd.h`` the fileno is 1, but according to http://stackoverflow.com/questions/8502945/printf-without-newline-in-assembly , it is 0. – Janus Troelsen Mar 18 '12 at 00:58
  • 1
    stdout in this context is the global FILE* named stdout(that by default is connected to file descriptor 1, standard out.). You can't use file descriptors, 0 nor 1, as arguments to functions that expect a FILE*. You can call fflush() with NULL as an argument to flush all open FILE* though. – nos Mar 18 '12 at 01:10
  • But I didn't think there was a NULL in assembly? And in C, NULL == 0. How would it know the difference? – Janus Troelsen Mar 18 '12 at 01:16
  • You wouldn't. You'd just use a 0 with the same size as a pointer. But that does not mean the file descriptor 0, though their values are the same. – nos Mar 18 '12 at 01:18
  • Scratch that, of course it would know the difference since it takes a FILE* and not a file descriptor. – Janus Troelsen Mar 18 '12 at 01:19
  • Yeah, I know they are indistinguishable. I was confusing file descriptors and FILE* and I thought that it took a file descriptor. That's why I said "scratch that", to let you know that I knew how it would know the difference (because the FILE* for stdout isn't NULL, the file descriptor is 0, which usually equals NULL, however). I understand how it works now. Mange tak for hjælpen, kære nordmand :) – Janus Troelsen Mar 18 '12 at 01:32
2

Output sent to standard out is usually buffered. If you call fflush(stdout) before you call _exit you should get your output.

The reason exit works is because that function is guaranteed to close and flush any open streams (such as stdout) before calling _exit itself to actually terminate the program.

SoapBox
  • 20,457
  • 3
  • 51
  • 87
  • But I do get the output if I do not pipe it. Also, ``man _exit`` notes that I can use ``tcflush``. I tried that, and it didn't work. How do I call ``fflush(stdout)``? ``pushl $1; call fflush``? – Janus Troelsen Mar 18 '12 at 00:46
  • I found the stdout fileno in ``unistd.h``. Also, I just tried it with the 1 in stack and in ``%eax``, both of them segfault. – Janus Troelsen Mar 18 '12 at 00:50
  • 1
    Reply to myself: It takes a FILE*, not a file descriptor. – Janus Troelsen Mar 18 '12 at 01:34
1

According to the man page for _exit(2)

Whether it flushes standard I/O buffers and removes temporary files created with tmpfile(3) is implementation-dependent. On the other hand, _exit() does close open file descriptors, and this may cause an unknown delay, waiting for pending output to finish. If the delay is undesired, it may be useful to call functions like tcflush(3) before calling _exit(). Whether any pending I/O is canceled, and which pending I/O may be canceled upon _exit(), is implementation-dependent.

So unless you've flushed stdout, it may get discarded.

Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
  • I tried calling ``tcflush``, it doesn't seem to make a difference. – Janus Troelsen Mar 18 '12 at 00:52
  • 2
    tcflush works at on a terminal device. You need to to call fflush(stdout). (stdout is the global FILE* managed by libc, you cannot use the filedescriptor). If you're havng trouble with that, call fflush(NULL), which will flush all FILE*'s – nos Mar 18 '12 at 01:05
  • 2
    You're not passing the right argument to fflush, as per the usual calling convention, the arguments should be on the stack, not in %eax – nos Mar 18 '12 at 01:16