使用 x86_64 sys_call 读取 Linux 上的单键输入(无需等待 return)

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

我想让 Linux 使用 sys_read 从键盘敲击 1 次,但 sys_read 只是等到我按下回车键。如何阅读 1 击键?这是我的代码:

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

Cmp ECX,49
Je Do_C
Jmp Error

我已经尝试使用 BIOS 中断但它失败了(分段错误),我想从键盘捕获数字 1 到 8 输入。

64 位系统调用 linux

man syscall 中的表格在这里提供了很好的概述:

arch/ABI   instruction          syscall #   retval Notes
──────────────────────────────────────────────────────────────────
i386       int [=10=]x80            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    -

我省略了这里不相关的行。在 32 位模式下,参数在 ebxecx 等中传输,系统调用号在 eax 中。在 64 位模式下略有不同:所有寄存器现在都是 64 位宽,因此具有不同的名称。系统调用编号仍在 eax 中,现在变为 rax。但是参数现在是在rdi, rsi,等中传递的。另外,这里使用指令syscall代替int 0x80来触发系统调用。

参数的顺序也可以在手册页中阅读,这里是 man 2 ioctlman 2 read:

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

所以这里int fd的值在rdi,第二个参数在rsi等等

如何摆脱等待换行

首先在内存中创建一个termios结构(在.bss段):

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

然后获取当前终端设置并禁用规范模式:

; 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

现在可以使用sys_read读入击键了:

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

然后检查rax中的return值:它包含读取的字符数。

(,例如,如果您在 bash 中通过 运行 ./a.out <&- 关闭标准输入。使用 strace 打印系统调用的解码跟踪你的程序,所以你不需要在玩具实验中实际编写错误处理。)


参考文献: