【问题标题】:Interrupt "console input without echo" in Linux在 Linux 中中断“无回显的控制台输入”
【发布时间】:2021-06-22 03:03:20
【问题描述】:

DOS 有int 21h / AH=08H: Console input without echo

Linux 有类似的东西吗?如果我需要在终端显示之前处理输入的值。

【问题讨论】:

  • 在 Linux 上可以做到这一点。您必须将 termios 配置为不同的模式。或者使用以易于访问的方式提供此类功能的 curses 库。
  • 不是“中断 08h”,而是“(DOS) 中断 21h 服务 08h”。

标签: linux assembly tty


【解决方案1】:

在 Linux 下,tty 在将输入的字符“发送”到请求程序之前对其进行缓冲。
这是通过终端模式控制的:raw (no buffering) or cooked(也分别称为非规范和规范模式)。

这些模式其实是tty的属性,可以用tcgetattrtcsetattr控制。

可以找到将终端设置为不带回显的非规范模式的代码,例如here(有关VTIMEVMIN控制字符的更多信息可以找到here)。

那是C,所以我们需要把它翻译成汇编。 从tcgetattr 的来源我们可以看到,tty 属性是通过 IOCTL 检索到 stdin,使用命令 TCGETS(值 0x5401),类似地,它们使用 IOCTL 设置,使用命令 TCSETS(值 0x5402) )。
读取的结构不是struct termios,而是struct __kernel_termios,它基本上是前者的缩短版本。 IOCTL 必须发送到 stdin 文件(文件描述符 STDIN_FILENO 的值为 0)。
知道如何实现 tcgetattrtcsetattr 我们只需要获取常量的值(如 ICANON 和类似的)。
我建议使用编译器(例如here)来查找公共常量的值并检查结构的偏移量。
对于非公共常量(在其翻译单元之外不可见),我们必须求助于阅读源代码(这不是特别难,但必须注意找到正确的源代码)。

在调用 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

【讨论】:

  • 非常感谢。
  • 1. “使 termios_data 在 DWORDs 边界上对齐”您可以使用 alignb 4 显式对齐以下变量。 2. 如果您希望所有仅 disp32 的内存访问都使用 rip-relative 寻址,则可以使用default rel,而不必为每次访问指定。
  • @ecm 我已经知道了,谢谢。 alignb 4 打字比写 4 而不是 1 要长。我没有使用 DEFAULT REL 没有充分的理由。我只是没想到我会使用这么“多”的内存访问,后来不想删除所有那些REL 以支持DEFAULT REL
  • resd 1 并不意味着对齐;也许您正在考虑 .word 神奇地对齐的经典 MIPS 程序集(如 MARS)?示例:godbolt.org/z/fbe69ns5a 显示 resb 1 / resd 1 以地址 5 结尾,而不是 8。 OTOH,假设此文件的 .bss 的开头可能是 4 字节对齐的。是的,刚刚尝试将两个文件与那些 5 字节的 BSS 部分链接在一起,foo2: 位于地址 8,与地址 5 的 bar1: 不同,因此填充了 ld 以对齐 BSS 部分的开头第二个.o
  • @PeterCordes 我不明白你的意思。如果 x % 4 == 0 则 (x+4) % 4 == 0。.bss 在 NASM 上按 4 字节对齐(请参阅 this),我保留 4 个字节,所以 termios_data 仍然对齐在 4 个字节上。我错过了什么吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-28
  • 1970-01-01
相关资源
最近更新 更多