Search code examples
assemblyx86posixsystem-callsnonblocking

Reading from non-blocking stdin in NASM


Example code that sets stdin to be non-blocking, then makes a read system call.

section .text
    global  _start

_start:
    mov eax,    55           ; __NR_fcntl
    mov ebx,    0
    mov ecx,    4            ; F_SETFL
    mov edx,    2048         ; O_RDONLY|O_NONBLOCK
    int 0x80
    mov eax,    3            ; __NR_read
    mov ebx,    0
    mov ecx,    buf
    mov edx,    1024
    int 0x80
    mov [br],   eax
    mov eax,    4            ; __NR_write
    mov ebx,    1
    mov ecx,    buf
    mov edx,    [br]
    int 0x80
    mov eax,    1            ; __NR_exit
    mov ebx,    0
    int 0x80

section .bss
    buf resb    1024
    br  resd    1

Expected behavior: program exits without printing anything because I expect read to return 0 in EAX when there's nothing to read (on the terminal).

Current behavior: program prints 4 random bytes as read returns -11 when nothing was passed to stdin.

$ strace ./nonblocking 
execve("./nonblocking", ["./nonblocking"], 0x7fff196fcb40 /* 53 vars */) = 0
strace: [ Process PID=4112759 runs in 32 bit mode. ]
fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK)  = 0
read(0, 0x804a000, 1024)                = -1 EAGAIN (Resource temporarily unavailable)
write(1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4294967285����) = 4096
exit(0)                                 = ?

(Editor's note: \0 prints as zero-width on terminals, but the 4 non-zero bytes the question is talking about are the -11 dword in br, starting 1024 bytes into the output.)


Solution

  • Linux system calls return -errno values on error. read is returning -EAGAIN as documented for non-blocking I/O on a device that has no bytes "ready", thus EAX = -11 which has 4 non-zero bytes in its bit-pattern.

    You're not actually printing 4 bytes, you're passing a huge value to write. It writes until the end of the page. Instead of returning -EFAULT, it returns a count of how many bytes it actually copied from the buffer to the file descriptor, even though it stopped because it hit an unmapped page.

    Writing output to the terminal makes that less visible, unless you look at the strace output. ASCII NUL (\0, a zero byte) prints as zero-width on a standard VT100-style terminal, but in other contexts that's very much not the same as not writing anything. Run
    ./nonblocking | hexdump -C --no-squeezing to see the 4kB of zero bytes you write to stdout.

    BTW, there's no point in storing EAX to memory just to reload it again. Just mov edx, eax.


    You only get EOF on a TTY when the user types control-D. (Assuming "cooked" mode and default stty settings).

    Non-blocking doesn't turn no-data-ready into EOF; that would make it impossible to distinguish actual end of file! (Non-blocking I/O on a regular file will give you EOF at the end of the file, or -EAGAIN if the file hasn't been fetched from disk yet so you'd have to block on I/O.)

    The situations where read returns 0 (meaning EOF) are the same for blocking and non-blocking read.

    If blocking read would sit there waiting for the user to type something (and "submit" it in line-buffered cooked mode with return or control-D), non-blocking read will return -EAGAIN.

    From the Linux read(2) man page:

    EAGAIN
    The file descriptor fd refers to a file other than a socket and has been marked nonblocking (O_NONBLOCK), and the read would block. See open(2) for further details on the O_NONBLOCK flag.