GNU/Linux >> Linux Esercitazione >  >> Linux

Come leggo l'input di un singolo carattere dalla tastiera usando nasm (assembly) sotto Ubuntu?

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.


Linux
  1. Come disinstallare neovim da Ubuntu

  2. Come disinstallare i plugin rhythmbox da Ubuntu

  3. Come disinstallare Steam da Ubuntu

  4. Come stampare un numero nell'assemblea NASM?

  5. Come leggere solo un singolo carattere nello script di shell

Come eseguire comandi dall'input standard utilizzando Tee e Xargs in Linux

Come reinstallare Ubuntu in modalità Dual Boot o Single Boot

Come aggiornare da 12.04 a 12.10 utilizzando Cd?

Come aggiornare il server Ubuntu a 20.04 dal 18.04

Come disinstallare chromium-browser da Ubuntu

Come posso leggere da /proc/$pid/mem sotto Linux?