2010-07-22 13 views
6

Sto usando nasm sotto ubuntu. Tra l'altro ho bisogno di ottenere un singolo carattere di input dalla tastiera dell'utente (come quando un programma ti chiede di y/n?) Così come il tasto premuto e senza premere invio ho bisogno di leggere il carattere inserito. L'ho cercato spesso, ma tutto ciò che ho trovato era in qualche modo collegato a questa riga (int 21h) che genera "Errore di segmentazione". Per favore aiutami a capire come ottenere un singolo personaggio o come superare questo errore di segmentazione.Come leggo l'immissione di un singolo carattere dalla tastiera usando nasm (assembly) sotto ubuntu?

risposta

11

Può essere fatto dal montaggio, ma non è facile. Non è possibile utilizzare int 21h, è una chiamata di sistema DOS e non è disponibile sotto Linux.

Per ottenere caratteri dal terminale sotto sistemi operativi UNIX (come Linux), si legge da STDIN (numero di file 0). Normalmente, la chiamata di sistema in lettura si bloccherà fino a quando l'utente non premerà Invio. Questa è chiamata modalità canonica. Per leggere un singolo carattere senza attendere che l'utente prema Invio, è necessario prima disabilitare la modalità canonica. Ovviamente, dovrai riabilitarlo se desideri inserire la linea in un secondo momento e prima che il programma esca.

Per disabilitare la modalità canonica su Linux, si invia un IOCTL (IO ControL) a STDIN, utilizzando ioctl syscall. Presumo che tu sappia come fare chiamate di sistema Linux da assemblatore.

L'ioctl syscall ha tre parametri. Il primo è il file per inviare il comando a (STDIN), il secondo è il numero IOCTL e il terzo è in genere un puntatore a una struttura dati. ioctl restituisce 0 in caso di successo o un codice di errore negativo in caso di errore.

Il primo IOCTL necessario è TCGETS (numero 0x5401) che ottiene i parametri del terminale corrente in una struttura di termios. Il terzo parametro è un puntatore a una struttura di termios. Dalla sorgente del kernel, la struttura termios è definita come:

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

dove tcflag_t è lungo 32 bit, cc_t è lunga un byte, e NCCS è attualmente definito come 19. Vedere il manuale NASM per come si può comodamente definire e riserva spazio per strutture come questa.

Quindi, una volta ottenuto il termios corrente, è necessario cancellare la bandiera canonica. Questo flag si trova nel campo c_lflag, con maschera ICANON (0x00000002). Per cancellarlo, calcola c_lflag AND (NON ICANON). e memorizza il risultato nel campo c_lflag.

Ora è necessario notificare al kernel le modifiche apportate alla struttura del termios. Utilizzare l'ioctl TCSETS (numero 0x5402), con il terzo parametro impostare l'indirizzo della struttura del termios.

Se tutto va bene, il terminale è ora in modalità non canonica. È possibile ripristinare la modalità canonica impostando il flag canonico (con ORing c_lflag con ICANON) e chiamando di nuovo l'ioctl TCSETS.ripristina sempre la modalità canonica prima di uscire

Come ho detto, non è facile.

0

Il modo semplice: per un programma in modalità testo, utilizzare libncurses per accedere alla tastiera; per un programma grafico, utilizzare Gtk+.

Il modo più difficile: supponendo un programma in modalità testo, devi dire al kernel che vuoi l'input a carattere singolo, e quindi devi fare un sacco di contabilità e decodifica. È davvero complicato Non esiste un equivalente della buona vecchia routine DOS getch(). È possibile start imparare come farlo qui: Terminal I/O. I programmi grafici sono ancora più complicati; l'API di livello più basso è Xlib.

In ogni caso, impazzisci codificando tutto ciò che è in assemblea; usare invece C

+1

Mentre tutto ciò che hai detto è corretto per C, non è davvero una risposta pertinente se l'OP sta cercando di imparare l'assemblaggio. –

+1

Questo perché l'OP * non deve essere programmato in linguaggio assembly *. L'unica buona ragione per codificare a mano qualsiasi cosa nel linguaggio assembly è se si tratta di una subroutine computazionale critica delle prestazioni, o uno dei pochissimi pezzi di basso livello di un kernel del sistema operativo che * non può * essere codificato in altro modo. L'interazione dell'utente non è idonea. Ciò che l'OP sta cercando di fare non è nemmeno un buon * esercizio di apprendimento * sotto Unix. – zwol

+0

Detto questo, non c'è nulla che impedisca all'OP di scrivere un linguaggio assembly che chiama libncurses, anche se mi sembra un profondo spreco di tempo e punti di sanità mentale (ma non sarebbe male come il linguaggio assembly che fa il terminale Unix I/O a mano). – zwol

5

avevo bisogno di fare questo di recente, e ispirato da Callum di excellent answer, ho scritto il seguente:

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 

È quindi possibile fare:

call canonical_off 

Se state leggendo una riga di testo , probabilmente anche tu vuoi fare:

call echo_off 

in modo che ogni carattere non venga echeggiato come è stato digitato.

Ci possono essere modi migliori per farlo, ma funziona per me su un'installazione Fedora a 64 bit.

Ulteriori informazioni sono disponibili nella pagina di manuale per termios(3) o nello termbits.h source.

Problemi correlati