Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

assembly - Reading a single-key input on Linux (without waiting for return) using x86_64 sys_call

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...