2

I'm working on a Hello World in Assembly for x86-64.

I have managed to create one that finishes when Enter key is pressed, but I have to finish it when ANY key is pressed.

This is the code for waiting the ENTER Key:

mov rax, 0
mov rdi, 0
mov rdx, 1
syscall

I can't use any int xh or something like that. Only syscalls.

Thanks!

rkhb
  • 14,159
  • 7
  • 32
  • 60
Guolf3377
  • 97
  • 2
  • 11
  • 1
    That's not as easy as it sounds. You will need to change the terminal settings. You might want to look at a [C example](http://stackoverflow.com/a/448982/547981) first. – Jester Aug 25 '15 at 00:05
  • possible duplicate of [How do I wait for a keystroke interrupt with a syscall on Linux?](http://stackoverflow.com/questions/27365528/how-do-i-wait-for-a-keystroke-interrupt-with-a-syscall-on-linux) – Cel Skeggs Sep 07 '15 at 02:13

2 Answers2

3

I've answered a similar question before, and gave C code that would work directly with system calls to do what you wanted.

Here's a translation of that code to nasm, with slight changes to reflect that you're just checking that any key is pressed, not a specific key:

fwait:
    ; fetch the current terminal settings
    mov rax, 16    ; __NR_ioctl
    mov rdi, 0     ; fd: stdin
    mov rsi, 21505 ; cmd: TCGETS
    mov rdx, orig  ; arg: the buffer, orig
    syscall

    ; again, but this time for the 'new' buffer
    mov rax, 16
    mov rdi, 0
    mov rsi, 21505
    mov rdx, new
    syscall

    ; change settings
    and dword [new+0], -1516    ; ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
    and dword [new+4], -2       ; ~OPOST
    and dword [new+12], -32844  ; ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN)
    and dword [new+8], -305     ; ~(CSIZE | PARENB)
    or  dword [new+8], 48        ; CS8

    ; set settings (with ioctl again)
    mov rax, 16    ; __NR_ioctl
    mov rdi, 0     ; fd: stdin
    mov rsi, 21506 ; cmd: TCSETS
    mov rdx, new   ; arg: the buffer, new
    syscall

    ; read a character
    mov rax, 0     ; __NR_read
    mov rdi, 0     ; fd: stdin
    mov rsi, char  ; buf: the temporary buffer, char
    mov rdx, 1     ; count: the length of the buffer, 1
    syscall

    ; reset settings (with ioctl again)
    mov rax, 16    ; __NR_ioctl
    mov rdi, 0     ; fd: stdin
    mov rsi, 21506 ; cmd: TCSETS
    mov rdx, orig  ; arg: the buffer, orig
    syscall

    ret

The basic idea is that you have to edit the terminal settings, read a character, and reset the settings.

Community
  • 1
  • 1
Cel Skeggs
  • 1,827
  • 21
  • 38
  • 1
    You could just copy the buffer instead of calling `TCGETS` twice! The parts you're modifying fit in 2 64-bit registers, so you could just load them and keep them in regs instead of actually copying anywhere. Also, you forgot to include the `orig: resd something` buffer for TCGETS: you have to make sure you reserve enough space so the system-call doesn't overwrite other data. (e.g. another answer that expanded on this code used massive overkill with `times 10000 db 0` to reserve 10kB.) – Peter Cordes Mar 30 '19 at 03:20
1

I did a little adaptation of the code in Cel Skeggs's answer.

This is a complete program that to prints the read keys one at a time, exiting on ENTER.

;; Simple Keyboard Reading in x86_64 assembly, using Linux syscalls
;;
;;        nasm -felf64 -o readKey64.o readKey64.asm
;;        ld readKey64.o -o readKey64
;;        ./readKey64
;;
;; Adaptation of original code from:
;;   https://stackoverflow.com/questions/32193374/wait-for-keypress-assembly-nasm-linux
;--------------------------------------------------------------------------




global _start:


section .data
  orig: times 10000 db 0         ; reserve way more space than we need for TCGETS
  new:  times 10000 db 0
  char: db 0,0,0,0,0

 msg1: db "Reading Keyboard... push ENTER to finish",0ah,0
 msglen     equ $ - msg1
 msg2: db 0ah,"END.",0ah,0
 msg2len: equ $ - msg2

section .text

_start:
    mov rsi,msg1
    mov rax,1
    mov rdi,1
    mov rdx,msglen
    syscall

    ; fetch the current terminal settings
    mov rax, 16    ; __NR_ioctl
    mov rdi, 0     ; fd: stdin
    mov rsi, 21505 ; cmd: TCGETS
    mov rdx, orig  ; arg: the buffer, orig
    syscall

    ; again, but this time for the 'new' buffer
    mov rax, 16
    mov rdi, 0
    mov rsi, 21505
    mov rdx, new
    syscall

    ; change settings
    and dword [new+0], -1516    ; ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
    and dword [new+4], -2       ; ~OPOST
    and dword [new+12], -32844  ; ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN)
    and dword [new+8], -305     ; ~(CSIZE | PARENB)
    or  dword [new+8], 48        ; CS8

    ; set settings (with ioctl again)
    mov rax, 16    ; __NR_ioctl
    mov rdi, 0     ; fd: stdin
    mov rsi, 21506 ; cmd: TCSETS
    mov rdx, new   ; arg: the buffer, new
    syscall
.readchar:
    ; read a character
    mov rax, 0     ; __NR_read
    mov rdi, 0     ; fd: stdin
    mov rsi, char  ; buf: the temporary buffer, char
    mov rdx, 1     ; count: the length of the buffer, 1
    syscall
    mov rax,1      ; __NR_write
    mov rdi,1
    mov rdx,1
    syscall        ; sys_write(1, char, 1)   // RSI is still set to buf

    cmp byte[char],13
    jz .end
    jmp .readchar
.end:

    ; reset settings (with ioctl again)
    mov rax, 16    ; __NR_ioctl
    mov rdi, 0     ; fd: stdin
    mov rsi, 21506 ; cmd: TCSETS
    mov rdx, orig  ; arg: the buffer, orig
    syscall

    mov rsi,msg2
    mov rax,1
    mov rdi,1
    mov rdx,msg2len
    syscall            ; sys_write(1, msg2, msg2len)

    mov rax,60
    mov rdi,0
    syscall            ; sys_exit(0)
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • You should use `jnz .readchar` instead of conditionally jumping over a `jmp`. Also, don't forget to comment your new system calls. – Peter Cordes Mar 30 '19 at 03:24