【问题标题】:Reading a single-key input on Linux (without waiting for return) using x86_64 sys_call使用 x86_64 sys_call 在 Linux 上读取单键输入(无需等待返回)
【发布时间】:2020-07-16 14:37:22
【问题描述】:

我想让 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。

【问题讨论】:

  • 问题是终端默认处于“cooked”模式,即行缓冲,而要获得单次击键,您需要在原始模式下;参见例如viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html; BTW BIOS 中断在操作系统下运行时不起作用(尤其是在保护模式下)。
  • 你不能在 64 位汇编中使用int 80h
  • @JCWasmx86 可以,但结果是usually wrong

标签: linux assembly x86-64 nasm tty


【解决方案1】:

64 位 linux 中的系统调用

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

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    -

我省略了这里不相关的行。在 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中的返回值:它包含读取的字符数。

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


参考资料:

【讨论】:

  • 所以在调用 sys_read 之前,我必须先设置 sys_ioctl 吗?系统调用是 Int 80h?我是汇编 linux 的新手……我输入的字符在 RAX 中?我应该把写termios结构放回哪里?顺便说一句,感谢您的帮助...@fcdt
  • termios 结构就像一个配置文件:你想禁用规范模式,所以你必须读入当前配置,修改它,然后再写回去。我在帖子中添加了几行关于系统调用的内容。
  • 感谢@fcdt 的解释...所以我输入的密钥在 RAX 中...好的,再次感谢
  • 请注意,某些键可能导致输入长度超过 1 个字节,因此在这种情况下您必须再次调用 sys_read。我还发现了 this site 关于 32 位 linux 程序集中的原始键盘输入。
  • 感谢@fcdt 已修复 :),顺便说一下 termios struct NASM 不识别 rd 和 rb,但识别 Resb 或 Resd
猜你喜欢
  • 1970-01-01
  • 2011-04-10
  • 2020-01-26
  • 2012-03-06
  • 2015-02-19
  • 1970-01-01
  • 2013-05-01
  • 2011-12-04
  • 1970-01-01
相关资源
最近更新 更多