Skip to content
Advertisement

Clear input buffer Assembly x86 (NASM)

Edit: This is similar to this: Reset a string variable to print multitple user inputs in a loop (NASM Assembly). But it is not the same issue.

From the other post, I was able to prevent additional characters from being printed. However, I still cannot prevent those additional characters from being read when the program goes back to the point in which it asks the user for input.


I’m creating a program that asks an user for input, and then prints it. Afterwards, it asks the user to enter ‘y’ if they want to print another text, or press anything else to close the program.

My issue is that if the user enters more characters than expected, those extra characters don’t go away, and when the program goes back to ask the user for input, there’s no chance to enter input because the program takes the remaining characters from the last time it received input.

For example:

The user is asked to enter text to print, and they enter: “Heyyyyyyyyyyyyyyyyyyyyyyyyyyyyy”

Leftover is “yyy”

At this point, the program should ask the user to enter ‘y’ to repeat the process, or anything else to close the program.

Output:

  • Heyyyyyyyyyyyyyyyyyyyyyyyyyy

  • Wanna try again? If yes, enter y. If not, enter anything else to close the program

  • Enter your text:

Output: yy

  • Wanna try again? If yes, enter y. If not, enter anything else to close the program.

And only now it asks for user input again

Since “yyy” is still in buffer, the user doesn’t get a chance to actually enter input in this case.

What can I do to fix this issue? I’ve just started to learn about this recently, so I’d appreciate any help.

Thanks.

This is what I’ve done

prompt db "Type your text here.", 0h
retry db "Wanna try again? If yes, enter y. If not, enter anything else to close the program"

section .bss
text resb 50
choice resb 2

section .text
    global _start

_start:

    mov rax, 1      ;Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, 21
    syscall


    mov rax, 0      ;Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, 50
    syscall

    mov r8, rax     ;This is what I added to prevent additional characters
                    ;from being printed

    mov rax, 1      ;Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1      ;Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, 83
    syscall

    mov rax, 0      ;Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 2
    syscall

    mov r8b, [choice] ;If choice is different from y, go to end and close the program. Otherwhise, go back to start.
    cmp byte r8b, 'y'
    jne end
    jmp _start

    end:
    mov rax, 60      
    mov rdi, 0       
    syscall

Advertisement

Answer

The simple way to clear stdin is to check if the 2nd char in choice is the 'n' (0xa). If it isn’t, then characters remain in stdin unread. You already know how to read from stdin, so in that case, just read stdin until the 'n' is read1, e.g.

    mov rax, 0      ;Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 2
    syscall

    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end
    
    cmp byte [choice + 1], 0xa  ; is 2nd char 'n' (if yes done, jump start)
    je _start
    
    empty:          ; chars remain in stdin unread
    mov rax, 0      ; read 1-char from stdin into choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 1
    syscall
    
    cmp byte [choice], 0xa  ; check if char 'n'?
    jne empty               ; if not, repeat
    
    jmp _start

Beyond that, you should determine your prompt lengths when you declare them, e.g.

prompt db "Type your text here. ", 0h
plen equ $-prompt
retry db "Try again (y/n)? ", 0h
rlen equ $-retry

That way you do not have to hardcode lengths in case you change your prompts, e.g.

_start:

    mov rax, 1      ;Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall


    mov rax, 0      ;Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, 50
    syscall

    mov r8, rax

    mov rax, 1      ;Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1      ;Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, rlen
    syscall

If you put it altogether, you can do:

prompt db "Type your text here. ", 0h
plen equ $-prompt
retry db "Try again (y/n)? ", 0h
rlen equ $-retry

section .bss
text resb 50
choice resb 2

section .text
    global _start

_start:

    mov rax, 1      ;Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall


    mov rax, 0      ;Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, 50
    syscall

    mov r8, rax

    mov rax, 1      ;Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1      ;Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, rlen
    syscall

    mov rax, 0      ;Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 2
    syscall

    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end
    
    cmp byte [choice + 1], 0xa  ; is 2nd char 'n' (if yes done, jump start)
    je _start
    
    empty:          ; chars remain in stdin unread
    mov rax, 0      ; read 1-char from stdin into choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, 1
    syscall
    
    cmp byte [choice], 0xa  ; check if char 'n'?
    jne empty               ; if not, repeat
    
    jmp _start

    end:
    mov rax, 60      
    mov rdi, 0       
    syscall

Example Use/Output

$ ./bin/emptystdin
Type your text here. abc
abc
Try again (y/n)? y
Type your text here. def
def
Try again (y/n)? yes please!
Type your text here. geh
geh
Try again (y/n)? yyyyyyyyyyyyyeeeeeeeeeesssssssss!!!!
Type your text here. ijk
ijk
Try again (y/n)? n

Now even a cat stepping on the keyboard at your (y/n)? prompt won’t cause problems. There are probably more elegant ways to handle this that would be more efficient that repetitive reads, with syscall, but this will handle the issue.


Additional Considerations And Error-Checks

As mentioned above, the simplistic reading and checking of a character-at-a-time isn’t a very efficient approach, though it is conceptually the easiest extension without making other changes. @PeterCordes makes a number of good points in the comments below related to approaches that are more efficient and more importantly about error conditions that can arise that should be protected against as well.

For starters when you are looking for information on the individual system call use, Anatomy of a system call, part 1 provides a bit of background on approaching their use supplemented by the Linux manual page, for read man 2 read for details on the parameter types and return type and values.

The original solution above does not address what happens if the user generates a manual end-of-file by pressing Ctrl+d or if a read error actually occurs. It simply addressed the user-input and emptying stdin question asked. With any user-input, before you use the value, you must validate that the input succeeded by checking the return. (not just for the yes/no input, but all inputs). For purposes here, you can consider zero input (manual end-of-file) or a negative return (read error) as a failed input.

To check whether you have at least one valid character of input, you can simply check the return (read returns the number of characters read, sys_read placing that value in rax after the syscall). A zero or negative value indicating no input was received. A check could be:

    cmp rax, 0              ; check for 0 bytes read or error
    jle error

You can write a short diagnostic to the user and then handle the error as wanted, this example simply exits after outputting a diagnostic, e.g.

readerr db 0xa, "eof or read error", 0xa, 0x0
rderrsz equ $-readerr
...
    ; your call to read here
    cmp rax, 0              ; check for 0 bytes read or error
    jle error
    ...
    error:
    mov rax, 1              ; output the readerr string and jmp to end
    mov rdi, 1
    mov rsi, readerr
    mov rdx, rderrsz
    syscall
    
    jmp end

Now moving on to a more efficient manner for emptying stdin. The biggest hindrance indicate in the original answer was the repeated system calls to sys_read to read one character at a time reusing your 2-byte choice buffer. The obvious solution is to make choice bigger, or just use stack space to read more characters each time. (you can look at the comments for a couple of approaches) Here, for example we will increase choice to 128-bytes which in the case of the anticipate "yn" input will only make use of two of those bytes, but in the case of an excessively long input will read 128-bytes at a time until the 'n' is found. For setup you have:

choicesz equ 128
...
section .bss
text resb 50
choice resb 128

Now after you ask for (y/n)? your read would be:

    mov rax, 0              ; Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall

    cmp rax, 0              ; check for 0 bytes read (eof) or error
    jle error
    
    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end                 ; not a 'y', doesn't matter what's in stdin, end

Now there are two conditions to check. First, compare the number of characters read with your buffer size choicesz and if the number of characters read is less than choicesz, no characters remain unread in stdin. Second, if the return equals the buffer size, you may or may not have characters remaining in stdin. You need to check the last character in the buffer to see if it is the 'n' to indicate whether you have read all the input. If the last character is other than the 'n' characters remain unread (unless the user just happened to generate a manual end-of-file at the 128th character) You can check as:

    empty:
    cmp eax, choicesz       ; compare chars read and buffer size
    jb _start               ; buffer not full - nothing remains in stdin
    
    cmp byte [choice + choicesz - 1], 0xa   ; if full - check if last byte n, done
    je _start
    
    mov rax, 0              ; fill choice again from stdin and repeat checks
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall
    
    cmp rax, 0              ; check for 0 bytes read or error
    jle error

    jmp empty

(note: as noted above, there is a further case to cover, not covered here, such as where the user enters valid input, but then generates a manual end-of-file instead of just pressing Enter after the 128th character (or a multiple of 128). There you can’t just look for a 'n' it doesn’t exist, and if there are no more chacters and call sys_read again, it will block wating on input. Conceivably you will need to use a non-blocking read and putback of a single character to break that ambiguity — that is left to you)

A comlete example with the improvements would be:

prompt db "Type your text here. ", 0x0
plen equ $-prompt
retry db "Try again (y/n)? ", 0x0
rlen equ $-retry
textsz equ 50
choicesz equ 128
readerr db 0xa, "eof or read error", 0xa, 0x0
rderrsz equ $-readerr

section .bss
text resb 50
choice resb 128

section .text
    global _start

_start:

    mov rax, 1              ; Just asking the user to enter input
    mov rdi, 1
    mov rsi, prompt
    mov rdx, plen
    syscall

    mov rax, 0              ; Getting input and saving it on var text
    mov rdi, 0
    mov rsi, text
    mov rdx, textsz
    syscall
    
    cmp rax, 0              ; check for 0 bytes read or error
    jle error

    mov r8, rax

    mov rax, 1              ; Printing the user input
    mov rdi, 1
    mov rsi, text
    mov rdx, r8
    syscall

    mov rax, 1              ; Asking if user wants to try again
    mov rdi, 1
    mov rsi, retry
    mov rdx, rlen
    syscall

    mov rax, 0              ; Getting input and saving it on var choice
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall

    cmp rax, 0              ; check for 0 bytes read (eof) or error
    jle error

    cmp byte [choice], 'y'  ; check 1st byte of choice, no need for r8b
    jne end                 ; not a 'y', doesn't matter what's in stdin, end
    
    empty:
    cmp eax, choicesz       ; compare chars read and buffer size
    jb _start               ; buffer not full - nothing remains in stdin
    
    cmp byte [choice + choicesz - 1], 0xa   ; if full - check if last byte n, done
    je _start
    
    mov rax, 0              ; fill choice again from stdin and repeat checks
    mov rdi, 0
    mov rsi, choice
    mov rdx, choicesz
    syscall
    
    cmp rax, 0              ; check for 0 bytes read or error
    jle error

    jmp empty
    
    end:
    mov rax, 60      
    mov rdi, 0       
    syscall
    
    error:
    mov rax, 1              ; output the readerr string and jmp to end
    mov rdi, 1
    mov rsi, readerr
    mov rdx, rderrsz
    syscall
    
    jmp end

There are surely more efficient ways to optimize this, but for purposes of discussion of “How do I empty stdin?”, this second approach with the buffer size used alieviates the repetitive calls to sys_read to read one character at-a-time is a good step forward. “How do it completely optimize the check?” is a whole separate question.

Let me know if you have further questions.

Footnotes:

1. In this circumstance where the user is typing input, the user generates a 'n' by pressing Enter, allowing you to check for the 'n' as the final character in emptying stdin. The user can also generate a manual end-of-file by pressing Ctrl+d so the 'n' isn’t guaranteed. There are many still other ways stdin can be filled, such as redirecting a file as input where there should be a ending 'n' to be POSIX compliant, there too that isn’t a guarantee.

Advertisement