1

I want to make Linux just take 1 keystroke from keyboard using sys_read, but sys_read just wait until i pressed enter. How to read 1 keystroke ? this is my code:

Mov EAX,3
Mov EBX,0
Mov ECX,Nada
Mov EDX,1
Int 80h

Cmp ECX,49
Je Do_C
Jmp Error

I already tried using BIOS interrupt but it's failed (Segmentation fault), I want capture number 1 to 8 input from keyboard.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
AFOEK
  • 37
  • 8
  • 4
    The problem is that the terminal by default is in "cooked" mode, which is line buffered, while to get single keystrokes you need it in raw mode; see e.g. https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html; BTW BIOS interrupt don't work when you are running under an OS (especially in protected mode). – Matteo Italia Jul 16 '20 at 14:41
  • You can't use `int 80h` in 64-bit assembly – JCWasmx86 Jul 16 '20 at 14:42
  • 3
    @JCWasmx86 You can, but the results are [usually wrong](https://stackoverflow.com/q/46087730/417501). – fuz Jul 16 '20 at 14:55

1 Answers1

6

Syscalls in 64-bit linux

The tables from man syscall provide a good overview here:

arch/ABI   instruction          syscall #   retval Notes
──────────────────────────────────────────────────────────────────
i386       int $0x80            eax         eax
x86_64     syscall              rax         rax    See below

arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────────
i386          ebx   ecx   edx   esi   edi   ebp   -
x86_64        rdi   rsi   rdx   r10   r8    r9    -

I have omitted the lines that are not relevant here. In 32-bit mode, the parameters were transferred in ebx, ecx, etc and the syscall number is in eax. In 64-bit mode it is a little different: All registers are now 64-bit wide and therefore have a different name. The syscall number is still in eax, which now becomes rax. But the parameters are now passed in rdi, rsi, etc. In addition, the instruction syscall is used here instead of int 0x80 to trigger a syscall.

The order of the parameters can also be read in the man pages, here man 2 ioctl and man 2 read:

int ioctl(int fd, unsigned long request, ...);
ssize_t read(int fd, void *buf, size_t count);

So here the value of int fd is in rdi, the second parameter in rsi etc.

How to get rid of waiting for a newline

Firstly create a termios structure in memory (in .bss section):

termios:
  c_iflag resd 1   ; input mode flags
  c_oflag resd 1   ; output mode flags
  c_cflag resd 1   ; control mode flags
  c_lflag resd 1   ; local mode flags
  c_line  resb 1   ; line discipline
  c_cc    resb 19  ; control characters

Then get the current terminal settings and disable canonical mode:

; Get current settings
mov  eax, 16             ; syscall number: SYS_ioctl
mov  edi, 0              ; fd:      STDIN_FILENO
mov  esi, 0x5401         ; request: TCGETS
mov  rdx, termios        ; request data
syscall

; Modify flags
and byte [c_lflag], 0FDh  ; Clear ICANON to disable canonical mode

; Write termios structure back
mov  eax, 16             ; syscall number: SYS_ioctl
mov  edi, 0              ; fd:      STDIN_FILENO
mov  esi, 0x5402         ; request: TCSETS
mov  rdx, termios        ; request data
syscall

Now you can use sys_read to read in the keystroke:

mov  eax, 0              ; syscall number: SYS_read
mov  edi, 0              ; int    fd:  STDIN_FILENO
mov  rsi, buf            ; void*  buf
mov  rdx, len            ; size_t count
syscall

Afterwards check the return value in rax: It contains the number of characters read.

(Or a -errno code on error, e.g. if you closed stdin by running ./a.out <&- in bash. Use strace to print a decoded trace of the system calls your program makes, so you don't need to actually write error handling in toy experiments.)


References:

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
fcdt
  • 2,371
  • 5
  • 14
  • 26
  • so before i call the sys_read, i must setup sys_ioctl first ? and for the syscall are Int 80h? i'm new in assembly linux... and the character i inputed are in RAX? and where i should put the write termios structure back? Btw thanks for the help... @fcdt – AFOEK Jul 17 '20 at 06:52
  • The `termios` structure is like a config file: You want to disable canonical mode, so you had to read in the current configuration, modify it, and write it back. I added a few lines about syscalls to the post. – fcdt Jul 17 '20 at 08:16
  • Thanks @fcdt for the explanation... so the key i inputed are in RAX... okay then once again thanks – AFOEK Jul 18 '20 at 11:31
  • Be aware that some keys could lead to an input longer than just 1 byte so you have to call `sys_read` again in that cases. I've also found [this site](http://asm.sourceforge.net/articles/rawkb.html) about raw keyboard input in 32-bit linux assembly. – fcdt Jul 18 '20 at 11:36
  • sorry for asking again @fcdt on the ```And byte [c_cflag], $FD``` i got a error when compiling ````key.asm:81: error: symbol `FD' undefined```` what the FD mean ? – AFOEK Jul 19 '20 at 07:39
  • Ups, using FASM normally. That should be `0FDh` or `0xFD` in NASM. – fcdt Jul 19 '20 at 07:40
  • 1
    thanks @fcdt fixed already :), btw on termios struct NASM doesn't recognize rd and rb, but recognize Resb or Resd – AFOEK Jul 19 '20 at 08:08
  • sorry for asking again @fcdt, the code are working but when i run it it is not read my keystroke its automatically input something.... eventually the code are jump to my error label for clear problem here my output: ```nasm -f elf64 -g -F stabs key.asm ld -o KeyPress key.o ./KeyPress Untuk memulai tekan tombol enter: Tekan tombol untuk memainkan satu not: (1,2,3,4,5,6,7,8) Error note not found please contact the app developer !!``` I using make file to run it thanks for the help – AFOEK Jul 21 '20 at 13:49
  • That seems like a new question to me. Only from the output, I can not see what's going on there. – fcdt Jul 21 '20 at 14:12
  • @fcdt Why do you enable `ICANON` in `c_cflag` instead of `c_lflag`? This link gives `ICANON` under `c_lflag` constants: https://man7.org/linux/man-pages/man3/termios.3.html – Viliam Vadocz Oct 14 '21 at 15:59
  • Thanks, that was a typo. `ICANON` is indeed a local flag. – fcdt Oct 15 '21 at 16:44