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.