GNU/Linux >> Tutoriales Linux >  >> Linux

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

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 de la terminal en sistemas operativos similares a UNIX (como Linux), lee desde STDIN (número de archivo 0). Normalmente, la llamada al sistema de lectura se bloqueará hasta que el usuario presione enter. Esto se llama modo canónico. Para leer un solo carácter sin esperar a que el usuario presione enter, primero debe deshabilitar el modo canónico. Por supuesto, tendrá que volver a habilitarlo si desea una entrada de línea más adelante y antes de que finalice su programa.

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

La llamada al sistema ioctl tiene tres parámetros. El primero es el archivo al que 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 de terminal actuales en una estructura termios. El tercer parámetro es un puntero a una estructura termios. Desde el código fuente del núcleo, la estructura 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 termios 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 (NO ICANON). y almacene el resultado nuevamente en el campo c_lflag.

Ahora necesita notificar al núcleo de sus cambios en la estructura de termios. Use 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 el indicador canónico (haciendo ORing c_lflag con ICANON) y llamando a TCSETS ioctl nuevamente. restaurar siempre 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. De hecho, funciona en un ejecutable tradicional que no es PIE, y esta versión se puede migrar fácilmente al modo de 32 bits).

A continuación, puede 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.

Se puede encontrar más información en la página del manual para termios(3) , o en el termbits.h fuente.


Linux
  1. ¿Cómo agregar una dirección IP en Ubuntu 18.04 usando netplan?

  2. Cómo desinstalar rbenv de Ubuntu

  3. Cómo desinstalar aria2 de Ubuntu

  4. ¿Cómo pegar desde el búfer en modo ex de vim?

  5. ¿Cómo puedo leer la entrada del teclado del host cuando estoy conectado a través de SSH?

Cómo ejecutar comandos desde la entrada estándar usando Tee y Xargs en Linux

Cómo reinstalar Ubuntu en modo de arranque dual o arranque único

¿Cómo actualizar de 12.04 a 12.10 usando Cd?

Cómo actualizar Ubuntu Server a 20.04 desde 18.04

Cómo desinstalar el navegador Chrome de Ubuntu

¿Cómo hago para que sed lea desde la entrada estándar?