2010-07-22 16 views
6

Estoy usando nasm en ubuntu. Por cierto, necesito obtener un solo carácter de entrada del teclado del usuario (como cuando un programa te pregunta por y/n?) Así que al presionar la tecla y sin presionar Enter, necesito leer el carácter ingresado. Lo busqué en Google mucho, pero todo lo que encontré estaba relacionado de alguna manera con esta línea (int 21h) que resulta en "Falla de Segmentación". Ayúdame a resolver cómo obtener un solo carácter o cómo superar este error de segmentación.¿Cómo leo la entrada de un solo carácter del teclado usando nasm (assembly) debajo de ubuntu?

Respuesta

11

Se puede hacer desde el ensamblaje, 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 tipo 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 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 ingresar la línea más tarde, y antes de que su programa finalice.

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

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

La primera 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 la fuente del kernel, se define la estructura termios 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 es de 32 bits de longitud, cc_t es un byte de longitud, y NCCS está actualmente define como 19. Consulte el manual de NASM cómo puede definir convenientemente y reservar espacio para estructuras como esta.

Una vez que tenga los términos actuales, debe borrar la marca canónica. Esta bandera está en el campo c_lflag, con la máscara ICANON (0x00000002). Para borrarlo, calcule c_lflag Y (NO ICONO). y almacena el resultado nuevamente en el campo c_lflag.

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

Si todo va bien, el terminal ahora está en modo no canónico. Puede restablecer el modo canónico configurando la bandera canónica (mediante ORing c_lflag con ICANON) y llamando de nuevo al TCSETS ioctl.siempre restablece el modo canónico antes de salir

Como dije, no es fácil.

0

La manera fácil: Para un programa de modo de texto, use libncurses para acceder al teclado; para un programa gráfico, use Gtk+.

La manera más difícil: suponiendo que se trata de un programa en modo texto, debe decirle al kernel que desea la entrada de un solo carácter, y luego debe realizar una gran cantidad de contabilidad y decodificación. Es realmente complicado. No existe un equivalente de la buena rutina de DOS getch(). Puede iniciar aprendiendo cómo hacerlo aquí: Terminal I/O. Los programas gráficos son aún más complicados; la API de nivel más bajo para eso es Xlib.

De cualquier manera, te vas a volver loco codificando lo que sea que esté en el ensamblaje; usa C en su lugar.

+1

Si bien todo lo que usted dice es correcto para C, en realidad no es una respuesta relevante si el OP intenta aprender a ensamblar. –

+1

Eso es porque OP * no debe programarse en lenguaje ensamblador *. La única buena razón para codificar manualmente algo en lenguaje ensamblador es si es una subrutina computacional crítica para el rendimiento o una de las pocas piezas de bajo nivel de un kernel de sistema operativo que * no * puede codificarse de otra manera. La interacción del usuario no califica. Lo que el OP intenta hacer no es siquiera un buen * ejercicio de aprendizaje * bajo Unix. – zwol

+0

Habiendo dicho eso, no hay nada que impida que el OP escriba lenguaje ensamblador que llama libncurses, aunque me parece una profunda pérdida de tiempo y cordura (pero no sería tan malo como el lenguaje ensamblador que hace el terminal Unix I/O a mano). – zwol

5

que tenía que hacer esto recientemente, e inspirado por Callum excellent answer, escribí lo siguiente:

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 

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 cada carácter no se repita a medida que se escribe.

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

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

Cuestiones relacionadas