如何在 ubuntu 下使用 nasm(汇编)从键盘读取单个字符输入?
- 2024-11-04 08:43:00
- admin 原创
- 33
问题描述:
我在 ubuntu 下使用 nasm。顺便说一下,我需要从用户的键盘获取单个输入字符(例如当程序要求您输入 y/n ? 时),因此当按下键而不按回车键时,我需要读取输入的字符。我在 Google 上搜索了很多,但我找到的所有内容都与此行 ( int 21h
) 有关,导致“分段错误”。请帮我弄清楚如何获取单个字符或如何克服这个分段错误。
解决方案 1:
可以用汇编语言来实现,但并不容易。您不能使用 int 21h,这是 DOS 系统调用,在 Linux 下不可用。
要在类 UNIX 操作系统(如 Linux)下从终端获取字符,您需要从 STDIN(文件编号 0)读取。通常,read 系统调用将阻塞,直到用户按下 Enter 键。这称为规范模式。要读取单个字符而不等待用户按下 Enter 键,您必须先禁用规范模式。当然,如果您稍后想要行输入,则必须在程序退出之前重新启用它。
要在 Linux 上禁用规范模式,可以使用 ioctl 系统调用将 IOCTL(IO 控制)发送到 STDIN。我假设您知道如何从汇编程序进行 Linux 系统调用。
ioctl 系统调用有三个参数。第一个是发送命令的文件 (STDIN),第二个是 IOCTL 编号,第三个通常是指向数据结构的指针。ioctl 在成功时返回 0,在失败时返回负错误代码。
您需要的第一个 IOCTL 是 TCGETS(编号 0x5401),它以 termios 结构的形式获取当前终端参数。第三个参数是指向 termios 结构的指针。从内核源代码来看,termios 结构定义如下:
struct termios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc[NCCS]; /* control characters */
};
其中 tcflag_t 长 32 位,cc_t 长 1 字节,NCCS 当前定义为 19。请参阅 NASM 手册以了解如何方便地定义和保留此类结构的空间。
因此,一旦您获得了当前的 termios,就需要清除规范标志。此标志位于 c_lflag 字段中,掩码为 ICANON (0x00000002)。要清除它,请计算 c_lflag AND (NOT ICANON)。并将结果存储回 c_lflag 字段。
现在您需要通知内核您对 termios 结构的更改。使用 TCSETS (编号 0x5402) ioctl,将第三个参数设置为您的 termios 结构的地址。
如果一切顺利,终端现在处于非规范模式。您可以通过设置规范标志(通过将 c_lflag 与 ICANON 进行 ORing)并再次调用 TCSETS ioctl 来恢复规范模式。在退出之前始终恢复规范模式
正如我所说,这并不容易。
解决方案 2:
我最近需要这样做,并且受到 Callum 的出色回答的启发,我写了以下内容(适用于 x86-64 的 NASM):
DEFAULT REL
section .bss
termios: resb 36
stdin_fd: equ 0 ; STDIN_FILENO
ICANON: equ 1<<1
ECHO: equ 1<<3
section .text
canonical_off:
call read_stdin_termios
; clear canonical bit in local mode flags
and dword [termios+12], ~ICANON
call write_stdin_termios
ret
echo_off:
call read_stdin_termios
; clear echo bit in local mode flags
and dword [termios+12], ~ECHO
call write_stdin_termios
ret
canonical_on:
call read_stdin_termios
; set canonical bit in local mode flags
or dword [termios+12], ICANON
call write_stdin_termios
ret
echo_on:
call read_stdin_termios
; set echo bit in local mode flags
or dword [termios+12], ECHO
call write_stdin_termios
ret
; clobbers RAX, RCX, RDX, R8..11 (by int 0x80 in 64-bit mode)
; allowed by x86-64 System V calling convention
read_stdin_termios:
push rbx
mov eax, 36h
mov ebx, stdin_fd
mov ecx, 5401h
mov edx, termios
int 80h ; ioctl(0, 0x5401, termios)
pop rbx
ret
write_stdin_termios:
push rbx
mov eax, 36h
mov ebx, stdin_fd
mov ecx, 5402h
mov edx, termios
int 80h ; ioctl(0, 0x5402, termios)
pop rbx
ret
(编者注:不要int 0x80
在 64 位代码中使用:如果在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么? - 它会在 PIE 可执行文件中中断(其中静态地址不在低 32 位),或者在堆栈上使用 termios 缓冲区。它实际上在传统的非 PIE 可执行文件中工作,并且此版本可以轻松移植到 32 位模式。)
然后你可以执行以下操作:
call canonical_off
如果您正在阅读一行文本,您可能还想执行以下操作:
call echo_off
这样每个字符在输入时就不会被回显。
可能有更好的方法可以做到这一点,但它在 64 位 Fedora 安装上对我来说是有效的。
您可以在手册页termios(3)
或termbits.h
源代码中找到更多信息。
解决方案 3:
简单的方法:对于文本模式程序,使用libncurses来访问键盘;对于图形程序,使用Gtk+。
困难的方式:假设一个文本模式的程序,你必须告诉内核你想要单字符输入,然后你必须做大量的记录和解码。这真的很复杂。没有与旧的 DOS 例程相当的东西getch()
。你可以从这里开始学习如何做到这一点:终端 I/O。图形程序甚至更复杂;最低级别的 API 是Xlib。
无论哪种方式,您都会疯狂地用汇编语言编写代码;请改用 C 语言。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件