Saltar al contenido

¿Cómo leo la entrada de un solo carácter desde el teclado usando nasm (ensamblaje) en ubuntu?

Solución:

Se puede hacer desde el montaje, pero no es fácil. No puede usar int 21h, es una llamada al sistema DOS y no está disponible en Linux.

Para obtener caracteres del terminal en sistemas operativos similares a UNIX (como Linux), lea de STDIN (número de archivo 0). Normalmente, la llamada al sistema de lectura se bloqueará hasta que el usuario presione Intro. A esto se le llama modo canónico. Para leer un solo carácter sin esperar a que el usuario presione Intro, primero debe deshabilitar el modo canónico. Por supuesto, tendrá que volver a habilitarlo si desea ingresar una línea más adelante y antes de que salga el programa.

Para deshabilitar el modo canónico en Linux, envíe un IOCTL (IO Control) a STDIN, utilizando ioctl syscall. Supongo que sabe cómo hacer llamadas al sistema Linux desde ensamblador.

El syscall ioctl tiene tres parámetros. El primero es el archivo al que se enviará el comando (STDIN), el segundo es el número IOCTL y el tercero suele ser un puntero a una estructura de datos. ioctl devuelve 0 en caso de éxito o un código de error negativo en caso de error.

El primer IOCTL que necesita es TCGETS (número 0x5401) que obtiene los parámetros actuales del terminal en una estructura termios. El tercer parámetro es un puntero a una estructura termios. Desde la fuente del kernel, la estructura de termios se define como:

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 */
};

donde tcflag_t tiene una longitud de 32 bits, cc_t tiene una longitud de un byte y NCCS se define actualmente como 19. Consulte el manual de NASM para saber cómo puede definir y reservar espacio convenientemente para estructuras como esta.

Entonces, una vez que tenga los términos actuales, debe borrar la bandera canónica. Esta bandera está en el campo c_lflag, con máscara ICANON (0x00000002). Para borrarlo, calcule c_lflag AND (NOT ICANON). y vuelva a almacenar el resultado en el campo c_lflag.

Ahora necesita notificar al kernel de sus cambios en la estructura de termios. Utilice TCSETS (número 0x5402) ioctl, con el tercer parámetro establezca la dirección de su estructura termios.

Si todo va bien, la terminal ahora está en modo no canónico. Puede restaurar el modo canónico configurando la bandera canónica (haciendo OR c_lflag con ICANON) y llamando al TCSETS ioctl nuevamente. siempre restaure el modo canónico antes de salir

Como dije, no es fácil.

Necesitaba hacer esto recientemente, e inspirado por la excelente respuesta de Callum, escribí lo siguiente (NASM para x86-64):

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

(Nota del editor: no use int 0x80 en código de 64 bits: ¿Qué sucede si usa la ABI de Linux int 0x80 de 32 bits en código de 64 bits? – se rompería en un ejecutable PIE (donde las direcciones estáticas no están en los 32 bits bajos), o con el búfer termios en la pila. En realidad, funciona en un ejecutable tradicional que no es PIE, y esta versión se puede migrar fácilmente al modo de 32 bits).

Entonces puedes hacer:

call canonical_off

Si está leyendo una línea de texto, probablemente también quiera hacer:

call echo_off

para que no se repita cada carácter a medida que se escribe.

Puede haber mejores formas de hacer esto, pero me funciona en una instalación de Fedora de 64 bits.

Puede encontrar más información en la página de manual de termios(3), o en el termbits.h fuente.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *