Search code examples
linuxassemblyx86file-descriptor

linux x86 assembly language sys_read call should have first argument as 0 (stdin)


I was writing a simple assembly program to read from stdin, (like scanf). Here is my code.


section .bss
num resb 5

section .txt
global _start

_start:

mov eax,3   ;sys_read
mov ebx,0   ;fd 0
mov ecx,num
mov edx,5
int 0x80

mov eax,4   ;sys_write
mov ebx,1   ;fd 1
mov ecx,num
mov edx,5
int 0x80

mov eax,1   ;sys_exit
mov ebx,0   ;return 0
int 0x80

Now this works normally, it reads and prints.

So, I tried changing file descriptor value in sys_read call to 1(stdout), 2(syserr).

Code.


section .bss
num resb 5

section .txt
global _start

_start:

mov eax,3   ;sys_read
mov ebx,1   ;fd 1
mov ecx,num
mov edx,5
int 0x80

mov eax,4   ;sys_write
mov ebx,1   ;fd 1
mov ecx,num
mov edx,5
int 0x80

mov eax,1   ;sys_exit
mov ebx,0   ;return 0
int 0x80

This code also works fine.
My question is , even after changing file descriptor from 0 to 1, Why this code is working normally. sys_read should take 0 as fd.


Solution

  • When you tested it, you were running the program with stdin, stdout, and stderr all connected to your terminal. It just happens that the file description all 3 of those file descriptors referred to was a read/write.

    There's nothing magic about being fd 0 that stops a file descriptor from being read/write.

    I think a shell could separately open the terminal for read-only and for write-only, instead of running programs with all 3 standard file descriptors being duplicates of the same read-write file description. (set up with dup2). But that's not how bash (or the terminal emulator that started bash) is designed.


    Try running your sys_read(1, ...) version with stdin being a pipe or file which is definitely opened only for reading, and stdout being an fd that's opened only for writing.

    $ echo foo | strace ./read1 > foo.out
    execve("./read1", ["./read1"], 0x7fff68953560 /* 52 vars */) = 0
    strace: [ Process PID=31555 runs in 32 bit mode. ]
    read(1, 0x80490ac, 5)                   = -1 EBADF (Bad file descriptor)
    write(1, "\0\0\0\0\0", 5)               = 5
    exit(0)                                 = ?
    +++ exited with 0 +++
    

    So read(1, num, 5) returned -EBADF (bad file descriptor), because fd 1 is a write-only fd, opened by the shell before fork/execve of this process. write(1, ...) still happened because your program doesn't do any error checking. (That's fine; we have tools like strace so we can be lazy when experimenting with system calls).


    But note that redirecting stdin doesn't make any difference; your program never uses fd 0!

    When fd 1 is connected to the tty, reading from it reads from the terminal regardless of input redirection.

    $ echo test | strace ./read1
    execve("./read1", ["./read1"], 0x7ffc3c42d620 /* 52 vars */) = 0
    strace: [ Process PID=31462 runs in 32 bit mode. ]
    read(1,                                   # it blocked here until I pressed return
    "\n", 5)                        = 1
    write(1, "\n\0\0\0\0", 5
    )               = 5
    exit(0)                                 = ?
    +++ exited with 0 +++
    

    In another terminal while read1 was paused waiting for read() to return:

    $ ll /proc/$(pidof read1)/fd 
    total 0
    lr-x------ 1 peter peter 64 Feb 22 18:17 0 -> pipe:[13443590]
    lrwx------ 1 peter peter 64 Feb 22 18:17 1 -> /dev/pts/17
    lrwx------ 1 peter peter 64 Feb 22 18:17 2 -> /dev/pts/17
    lrwx------ 1 peter peter 64 Feb 22 18:17 49 -> socket:[405352]
    lrwx------ 1 peter peter 64 Feb 22 18:17 53 -> socket:[405353]
    

    Note the RWX on fd 1: the permissions on that symlink reflect whether it's a read, write, or read+write fd.