Как прочитать односимвольный ввод с клавиатуры, используя nasm (сборку) под Ubuntu?

Я использую Nasm под Ubuntu. Кстати, мне нужно получить один вводимый символ с клавиатуры пользователя (например, когда программа запрашивает у вас y/n?), Так что при нажатии клавиши и без нажатия клавиши ввода мне нужно прочитать введенный символ. Я много гуглил, но все, что я нашел, было как-то связано с этой строкой (int 21h) которые приводят к "Сегментации неисправности". Пожалуйста, помогите мне понять, как получить один символ или как преодолеть эту ошибку сегментации.

3 ответа

Решение

Это можно сделать из сборки, но это нелегко. Вы не можете использовать int 21h, это системный вызов DOS, и он не доступен в Linux.

Чтобы получить символы из терминала в UNIX-подобных операционных системах (таких как Linux), вы читаете из STDIN (номер файла 0). Обычно системный вызов чтения блокируется, пока пользователь не нажмет ввод. Это называется каноническим режимом. Чтобы прочитать один символ, не дожидаясь нажатия клавиши ввода, необходимо сначала отключить канонический режим. Конечно, вам нужно будет снова включить его, если вы хотите вводить строки позже и до выхода из программы.

Чтобы отключить канонический режим в Linux, вы отправляете IOCTL (IO ControL) в STDIN, используя системный вызов ioctl. Я предполагаю, что вы знаете, как выполнять системные вызовы 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 имеет длину одного байта, а NCCS в настоящее время определено как 19. Смотрите руководство NASM, чтобы узнать, как вы можете удобно определить и зарезервировать пространство для подобных структур.

Поэтому, когда у вас есть текущий термин, вам нужно очистить канонический флаг. Этот флаг находится в поле c_lflag с маской ICANON (0x00000002). Чтобы очистить его, вычислите c_lflag AND (НЕ ICANON). и сохраните результат обратно в поле c_lflag.

Теперь вам нужно уведомить ядро ​​о ваших изменениях в структуре termios. Используйте TCSETS (номер 0x5402) ioctl, с третьим параметром установите адрес вашей структуры termios.

Если все идет хорошо, терминал теперь находится в неканоническом режиме. Вы можете восстановить канонический режим, установив канонический флаг (с помощью ORing c_lflag с ICANON) и снова вызвав TCSETS ioctl. всегда восстанавливайте канонический режим перед выходом

Как я уже сказал, это не легко.

Мне нужно было сделать это недавно, и, вдохновленный прекрасным ответом Каллума, я написал следующее:

termios:        times 36 db 0
stdin:          equ 0
ICANON:         equ 1<<1
ECHO:           equ 1<<3

canonical_off:
        call read_stdin_termios

        ; clear canonical bit in local mode flags
        push rax
        mov eax, ICANON
        not eax
        and [termios+12], eax
        pop rax

        call write_stdin_termios
        ret

echo_off:
        call read_stdin_termios

        ; clear echo bit in local mode flags
        push rax
        mov eax, ECHO
        not eax
        and [termios+12], eax
        pop rax

        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

read_stdin_termios:
        push rax
        push rbx
        push rcx
        push rdx

        mov eax, 36h
        mov ebx, stdin
        mov ecx, 5401h
        mov edx, termios
        int 80h

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret

write_stdin_termios:
        push rax
        push rbx
        push rcx
        push rdx

        mov eax, 36h
        mov ebx, stdin
        mov ecx, 5402h
        mov edx, termios
        int 80h

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret

Затем вы можете сделать:

call canonical_off

Если вы читаете строку текста, вы, вероятно, также хотите сделать:

call echo_off

так что каждый символ не отображается, как он напечатан.

Возможно, есть лучшие способы сделать это, но у меня это работает на 64-битной установке Fedora.

Более подробную информацию можно найти на странице руководства для termios(3)или в termbits.h источник.

Простой способ: для программы в текстовом режиме используйте libncurses для доступа к клавиатуре; для графической программы используйте Gtk +.

Сложный путь. Предполагая, что программа работает в текстовом режиме, вы должны сообщить ядру, что вы хотите вводить один символ, а затем вам придется много делать бухгалтерию и декодировать. Это действительно сложно. Нет эквивалента старого доброго DOS getch() рутина. Вы можете начать изучать, как это сделать, здесь: Terminal I / O. Графические программы еще сложнее; API самого низкого уровня для этого - Xlib.

В любом случае, вы сходите с ума, кодируя все, что есть в сборке; используйте C вместо.

Другие вопросы по тегам