简体   繁体   中英

How to read input from stdin in assembly, character by character

I'd expect the program below to read some characters (up to 9) from stdin , and place them at a specified location in memory.

What actually happens: when I press Enter , if I have less than 9 characters, it simply goes to the next line; this will keep happening until I entered 9 characters. If I enter more than 9, the extra characters will be interpreted as a shell command. Why doesn't it terminate when I press Enter?

Using nasm 2.14.02 on Ubuntu.

  global _start
  section .bss
    buf resb 10
  section .text
    ; Read a word from stdin, terminate it with a 0 and place it at the given address.
    ; - $1, rdi: *buf - where to place read bytes
    ; - $2, rsi: max_count, including the NULL terminator
    ; Returns in rax:
    ; - *buf - address of the first byte where the NULL-terminated string was placed
    ; - 0, if input too big
    read_word: ; (rdi: *buf, rsi: max_count) -> *buf, or 0 if input too big
      mov r8, 0      ; current count
      mov r9, rsi    ; max count
      dec r9         ; one char will be occupied by the terminating 0

      ; read a char into the top of the stack, then pop it into rax
      .read_char:
        push rdi       ; save; will be clobbered by syscall
        mov rax, 0     ; syscall id = 0 (read)
        mov rdi, 0     ; syscall $1, fd = 0 (stdin)
        push 0         ; top of the stack will be used to place read byte
        mov rsi, rsp   ; syscall $2, *buf = rsp (addr where to put read byte)
        mov rdx, 1     ; syscall $3, count (how many bytes to read)
        syscall
        pop rax
        pop rdi

      ; if read character is Enter (aka carriage-return, CR) - null-terminate the string and exit
      cmp rax, 0x0d ; Enter
      je .exit_ok

      ; not enter ⇒ place it in the buffer, and read another one
      mov byte [rdi+r8], al ; copy character into output buffer
      inc r8                ; inc number of collected characters
      cmp r8, r9            ; make sure number doesn't exceed maximum
      je .exit_ok           ; if we have the required number of chars, exit
      jb .read_char         ; if it's not greater, read another char

      .exit_ok: ; add a null to the end of the string and return address of buffer (same as input)
        add r8, 1
        mov byte [rdi+r8], 0
        mov rax, rdi
        ret

      .exit_err: ; return 0 (error)
        mov rax, 0
        ret

  _start:
    mov rdi, buf     ; $1 - *buf
    mov rsi, 10      ; $2 - uint count
    call read_word

    mov rax, 60  ; exit syscall
    mov rdi, 0   ; exit code
    syscall

First, when the user hits Enter, you will see LF ( \n , 0xa ), not CR ( \r , 0xd ). This may explain why your program doesn't exit when you think it should.

As far as why extra characters go to the shell, this is about how the OS does terminal input. It accumulates keystrokes from the terminal into a kernel buffer until Enter is pressed, then makes the whole buffer available to be read by read() . This allows things like backspace to work transparently without requiring the application to explicitly code it, but it does mean that you can't literally read one keystroke at a time, as you're noticing.

If your program exits while the buffer still contains characters, then those characters will be read by the next program which attempts to read from the device, which in your case will be the shell. Most programs that read stdin avoid this by continuing to read and process data until end-of-file is seen ( read() returning 0) which happens for a terminal when the user presses Ctrl-D.

If you really need to process input character-by-character, you need to set the terminal to non-canonical mode , but many things will be different in this case.

The excellent answer above is illustrated by the following code:

  my $e = q(readChar);

  ForEver
   {my ($start, $end) = @_;
    ReadChar;
    Cmp rax, 0xa;
    Jle $end;
    PrintOutRaxAsChar;
    PrintOutRaxAsChar;
   };
  PrintOutNL;

  Assemble keep => $e;

  is_deeply qx(echo "ABCDCBA" | ./$e), <<END;
AABBCCDDCCBBAA
END

from: https://github.com/philiprbrenan/NasmX86#read-a-line-of-characters-from-stdin-and-print-them-out-on-stdout-from-assembly-code-using-nasm---the-netwide-assember-and-perl

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM