在 Linux 中中断 "console input without echo"
Interrupt "console input without echo" in Linux
DOS 有 int 21h / AH=08H: Console input without echo.
Linux 有类似的东西吗?如果我需要在终端显示之前处理输入的值。
在 Linux 下,是 tty 在将键入的字符“发送”到请求程序之前缓冲它们。
这是通过终端模式控制的:raw (no buffering) or cooked(也分别称为非规范模式和规范模式)。
这些模式其实就是tty的属性,可以用tcgetattr and tcsetattr.
来控制
可以找到将终端设置为不带回显的非规范模式的代码,例如here (more info on the VTIME
and VMIN
control chars can be found here)。
那是C,所以我们需要把它翻译成汇编。
从 tcgetattr
的源代码我们可以看到,tty 属性是通过 IOCTL 使用命令 TCGETS
(值 0x5401)检索到标准输入的,同样,它们是通过 IOCTL 使用命令 TCSETS
(值 0x5402)。
读取的结构不是 struct termios
而是 struct __kernel_termios 这基本上是前者的缩短版本。
IOCTL 必须发送到 stdin
文件(文件描述符 STDIN_FILENO
的值为 0)。
知道如何实现 tcgetattr
和 tcsetattr
我们只需要获取常量的值(如 ICANON
和类似的)。
我建议使用编译器(例如 here)来查找 public 常量的值并检查结构的偏移量。
对于非 public 常量(在其翻译单元之外不可见),我们必须求助于阅读源代码(这不是特别困难,但必须注意找到正确的源代码)。
下面是调用 IOCTL 以获取-修改-设置 TTY 属性以启用原始模式的 64 位程序。
然后程序等待一个字符并递增显示它(例如 a -> b)。
请注意,该程序已经在 Linux (5.x) 下进行了测试,并且由于各种常量会在不同的 Unix 克隆中更改值,因此它不可移植。
我用了NASM,为struct __kernel_termios
定义了一个结构体,我还用了很多符号常量来让代码更易读。我真的不喜欢在汇编中使用结构,但 NASM 只是一个薄的宏层(如果你还没有,最好得到 used to them)。
最后,我假定熟悉 64 位 Linux 汇编编程。
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
DOS 有 int 21h / AH=08H: Console input without echo.
Linux 有类似的东西吗?如果我需要在终端显示之前处理输入的值。
在 Linux 下,是 tty 在将键入的字符“发送”到请求程序之前缓冲它们。
这是通过终端模式控制的:raw (no buffering) or cooked(也分别称为非规范模式和规范模式)。
这些模式其实就是tty的属性,可以用tcgetattr and tcsetattr.
来控制可以找到将终端设置为不带回显的非规范模式的代码,例如here (more info on the VTIME
and VMIN
control chars can be found here)。
那是C,所以我们需要把它翻译成汇编。
从 tcgetattr
的源代码我们可以看到,tty 属性是通过 IOCTL 使用命令 TCGETS
(值 0x5401)检索到标准输入的,同样,它们是通过 IOCTL 使用命令 TCSETS
(值 0x5402)。
读取的结构不是 struct termios
而是 struct __kernel_termios 这基本上是前者的缩短版本。
IOCTL 必须发送到 stdin
文件(文件描述符 STDIN_FILENO
的值为 0)。
知道如何实现 tcgetattr
和 tcsetattr
我们只需要获取常量的值(如 ICANON
和类似的)。
我建议使用编译器(例如 here)来查找 public 常量的值并检查结构的偏移量。
对于非 public 常量(在其翻译单元之外不可见),我们必须求助于阅读源代码(这不是特别困难,但必须注意找到正确的源代码)。
下面是调用 IOCTL 以获取-修改-设置 TTY 属性以启用原始模式的 64 位程序。
然后程序等待一个字符并递增显示它(例如 a -> b)。
请注意,该程序已经在 Linux (5.x) 下进行了测试,并且由于各种常量会在不同的 Unix 克隆中更改值,因此它不可移植。
我用了NASM,为struct __kernel_termios
定义了一个结构体,我还用了很多符号常量来让代码更易读。我真的不喜欢在汇编中使用结构,但 NASM 只是一个薄的宏层(如果你还没有,最好得到 used to them)。
最后,我假定熟悉 64 位 Linux 汇编编程。
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