Si può fare dal montaggio, ma non è facile. Non puoi usare int 21h, è una chiamata di sistema DOS e non è disponibile sotto Linux.
Per ottenere caratteri dal terminale in sistemi operativi simili a UNIX (come Linux), leggi da STDIN (file numero 0). Normalmente, la chiamata di sistema read si bloccherà fino a quando l'utente non preme Invio. Questa è chiamata modalità canonica. Per leggere un singolo carattere senza attendere che l'utente prema Invio, devi prima disabilitare la modalità canonica. Ovviamente, dovrai riattivarlo se desideri l'input di riga in un secondo momento e prima che il programma venga chiuso.
Per disabilitare la modalità canonica su Linux, si invia un IOCTL (IO Control) a STDIN, utilizzando la chiamata di sistema ioctl. Presumo che tu sappia come effettuare chiamate di sistema Linux dall'assembler.
La chiamata di sistema ioctl ha tre parametri. Il primo è il file a cui inviare il comando (STDIN), il secondo è il numero IOCTL e il terzo è tipicamente 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 termios. Il terzo parametro è un puntatore a una struttura termios. Dai sorgenti 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 è lungo un byte e NCCS è attualmente definito come 19. Consulta il manuale NASM per sapere come definire e riservare spazio per strutture come questa.
Quindi, una volta che hai i termios correnti, devi cancellare la bandiera canonica. Questo flag è 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 devi notificare al kernel le tue modifiche alla struttura termios. Usa ioctl TCSETS (numero 0x5402), con il terzo parametro imposta l'indirizzo della tua struttura termios.
Se tutto va bene, il terminale è ora in modalità non canonica. È possibile ripristinare la modalità canonica impostando il flag canonical (tramite ORing c_lflag con ICANON) e chiamando nuovamente ioctl TCSETS. ripristina sempre la modalità canonica prima di uscire
Come ho detto, non è facile.
Avevo bisogno di farlo di recente e, ispirato dall'eccellente risposta di Callum, ho scritto quanto segue (NASM per 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 dell'editore:non usare int 0x80
nel codice a 64 bit:cosa succede se si utilizza l'ABI Linux int 0x80 a 32 bit nel codice a 64 bit? - si interromperebbe in un eseguibile PIE (dove gli indirizzi statici non sono nei 32 bit bassi) o con il buffer termios nello stack. In realtà funziona in un eseguibile tradizionale non-PIE e questa versione può essere facilmente portata in modalità a 32 bit.)
Puoi quindi fare:
call canonical_off
Se stai leggendo una riga di testo, probabilmente vorrai anche fare:
call echo_off
in modo che ogni carattere non venga ripetuto mentre viene digitato.
Potrebbero esserci modi migliori per farlo, ma per me funziona su un'installazione Fedora a 64 bit.
Ulteriori informazioni possono essere trovate nella pagina man di termios(3)
, o nel termbits.h
fonte.