DOS has int 21h / AH=08H: Console input without echo.
Is there something similar for Linux? If I need to process the entered value before it is displayed in the terminal.
DOS has int 21h / AH=08H: Console input without echo.
Is there something similar for Linux? If I need to process the entered value before it is displayed in the terminal.
Under Linux, it is the tty that buffers the typed chars before "sending" them to the requesting program.
This is controlled through the terminal mode: raw (no buffering) or cooked (also known respectively as non-canonical and canonical mode).
These modes are actually attributes of the tty, which can be controlled with tcgetattr and tcsetattr.
The code to set the terminal in non-canonical mode without echo can be found, for example, here (more info on the VTIME
and VMIN
control chars can be found here).
That's C, so we need to translate it into assembly.
From the source of tcgetattr
we can see that the tty attributes are retrieved through an IOCTL to stdin with the command TCGETS
(value 0x5401) and, similarly, they are set with an IOCTL with the command TCSETS
(value 0x5402).
The structure read is not the struct termios
but struct __kernel_termios which is basically a shortened version of the former.
The IOCTL must be sent to the stdin
file (file descriptor STDIN_FILENO
of value 0).
Knowing how to implement tcgetattr
and tcsetattr
we only need to get the value of the constants (like ICANON
and similar).
I advise using a compiler (e.g. here) to find the values of the public constants and to check the structure's offsets.
For non-public constants (not visible outside their translation units) we must resort to reading the source (this is not particularly hard, but care must be taken to find the right source).
Below a 64-bit program that invokes the IOCTLs to get-modify-set the TTY attribute in order to enable the raw mode.
Then the program waits for a single char and displays it incremented (e.g. a -> b).
Note that this program has been tested under Linux (5.x) and, as the various constants change values across different clones of Unix, it is not portable.
I used NASM and defined a structure for struct __kernel_termios
, I also used a lot of symbolic constants to make the code more readable. I don't really like using structures in assembly but NASM ones are just a thin macro layer (it's better to get used to them if you aren't already).
Finally, I assume familiarity with 64-bit Linux assembly programming.
BITS 64
GLOBAL _start
;
; System calls numbers
;
%define SYS_READ 0
%define SYS_WRITE 1
%define SYS_IOCTL 16
%define SYS_EXIT 60
;
; __kernel_termios structure
;
%define KERNEL_NCC 19
struc 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 KERNEL_NCC ;control characters
endstruc
;
; IOCTL commands
;
%define TCGETS 0x5401
%define TCSETS 0x5402
;
; TTY local flags
;
%define ECHO 8
%define ICANON 2
;
; TTY control chars
;
%define VMIN 6
%define VTIME 5
;
; Standard file descriptors
;
%define STDIN_FILENO 0
%define STDOUT_FILENO 1
SECTION .bss
;The char read (reserve a DWORD to make termios_data be aligned on DWORDs boundary)
data resd 1
;The TTY attributes
termios_data resb termios_size
SECTION .text
_start:
;
;Get the terminal settings by sending the TCGETS IOCTL to stdin
;
mov edi, STDIN_FILENO ;Send IOCTL to stdin (Less efficient but more readable)
mov esi, TCGETS ;The TCGETS command
lea rdx, [REL termios_data] ;The arg, the buffer where to store the TTY attribs
mov eax, SYS_IOCTL ;Do the syscall
syscall
;
;Set the raw mode by clearing ECHO and ICANON and setting VMIN = 1, VTIME = 0
;
and DWORD [REL termios_data + termios.c_lflag], ~(ICANON | ECHO) ;Clear ECHO and ICANON
mov BYTE [REL termios_data + termios.c_cc + VMIN], 1
mov BYTE [REL termios_data + termios.c_cc + VTIME], 0
;
;Set the terminal settings
;
mov edi, STDIN_FILENO ;Send to stdin (Less efficient but more readable)
mov esi, TCSETS ;Use TCSETS as the command
lea rdx, [REL termios_data] ;Use the same data read (and altered) before
mov eax, SYS_IOCTL ;Do the syscall
syscall
;
;Read a char
;
mov edi, STDIN_FILENO ;Read from stdin (Less efficient but more readable)
lea rsi, [REL data] ;Read into data
mov edx, 1 ;Read only 1 char
mov eax, SYS_READ ;Do the syscall (Less efficient but more readable)
syscall
;
;Increment the char (as an example)
;
inc BYTE [REL data]
;
;Print the char
;
mov edi, STDOUT_FILENO ;Write to stdout
lea rsi, [REL data] ;Write the altered char
mov edx, 1 ;Only 1 char to write
mov eax, SYS_WRITE ;Do the syscall
syscall
;
;Restore the terminal settins (similar to the code above)
;
mov edi, STDIN_FILENO
mov esi, TCGETS
lea rdx, [REL termios_data]
mov eax, SYS_IOCTL
syscall
;Set ECHO and ICANON
or DWORD [REL termios_data + termios.c_lflag], ICANON | ECHO
mov edi, STDIN_FILENO
mov esi, TCSETS
lea rdx, [REL termios_data]
mov eax, SYS_IOCTL
syscall
;
;Exit
;
xor edi, edi
mov eax, SYS_EXIT
syscall