繁体   English   中英

如何在ubuntu下使用nasm(程序集)从键盘读取单个字符输入?

[英]How do i read single character input from keyboard using nasm (assembly) under ubuntu?

我在 ubuntu 下使用 nasm。 顺便说一下,我需要从用户的键盘上获取单个输入字符(比如当程序要求您输入 y/n 时?)以便按下键而不按 Enter 我需要读取输入的字符。 我用谷歌搜索了很多,但我发现的所有内容都与这条线( int 21h )有关,这导致了“分段错误”。 请帮我弄清楚如何获取单个字符或如何克服这个分段错误。

它可以从组装中完成,但这并不容易。 你不能使用 int 21h,这是一个 DOS 系统调用,它在 Linux 下不可用。

要在类 UNIX 操作系统(例如 Linux)下从终端获取字符,您可以从 STDIN(文件编号 0)读取。 通常, read 系统调用将阻塞,直到用户按下 Enter 键。 这称为规范模式。 要在不等待用户按 Enter 的情况下读取单个字符,您必须首先禁用规范模式。 当然,如果您想稍后在程序退出之前进行行输入,则必须重新启用它。

要在 Linux 上禁用规范模式,请使用 ioctl 系统调用向 STDIN 发送 IOCTL (IO Control)。 我假设您知道如何从汇编程序进行 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 来恢复规范模式。 在退出之前始终恢复规范模式

正如我所说,这并不容易。

我最近需要这样做,并受到 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

(编者注:不要在 64 位代码中使用int 0x80如果在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么? - 它会破坏 PIE 可执行文件(其中静态地址不是在低 32 位),或者在堆栈上使用 termios 缓冲区。它实际上可以在传统的非 PIE 可执行文件中工作,并且这个版本可以很容易地移植到 32 位模式。)

然后你可以这样做:

call canonical_off

如果您正在阅读一行文本,您可能还想这样做:

call echo_off

这样每个字符在输入时都不会被回显。

可能有更好的方法来做到这一点,但它适用于 64 位 Fedora 安装。

更多信息可以在termios(3)的手册页或termbits.h源中找到

简单的方法:对于文本模式程序,使用libncurses访问键盘; 对于图形程序,请使用Gtk+

困难的方法:假设一个文本模式程序,你必须告诉内核你想要单字符输入,然后你必须做大量的簿记和解码。 这真的很复杂。 没有等效的旧 DOS getch()例程。 您可以在此处开始学习如何操作:终端 I/O 图形程序更加复杂; 最低级别的 API 是Xlib

无论哪种方式,你都会疯狂地编码汇编中的任何东西; 改用 C。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM