GNU/Linux >> Tutoriels Linux >  >> Linux

Comment lire une entrée de caractère unique à partir du clavier à l'aide de nasm (assemblage) sous Ubuntu?

Cela peut être fait à partir de l'assemblage, mais ce n'est pas facile. Vous ne pouvez pas utiliser int 21h, c'est un appel système DOS et il n'est pas disponible sous Linux.

Pour obtenir des caractères du terminal sous des systèmes d'exploitation de type UNIX (tels que Linux), vous lisez à partir de STDIN (fichier numéro 0). Normalement, l'appel système de lecture sera bloqué jusqu'à ce que l'utilisateur appuie sur Entrée. C'est ce qu'on appelle le mode canonique. Pour lire un seul caractère sans attendre que l'utilisateur appuie sur Entrée, vous devez d'abord désactiver le mode canonique. Bien sûr, vous devrez le réactiver si vous voulez une entrée de ligne plus tard, et avant que votre programme ne se termine.

Pour désactiver le mode canonique sous Linux, vous envoyez un IOCTL (IO Control) à STDIN, en utilisant l'appel système ioctl. Je suppose que vous savez comment effectuer des appels système Linux à partir de l'assembleur.

L'appel système ioctl a trois paramètres. Le premier est le fichier auquel envoyer la commande (STDIN), le second est le numéro IOCTL et le troisième est généralement un pointeur vers une structure de données. ioctl renvoie 0 en cas de succès ou un code d'erreur négatif en cas d'échec.

Le premier IOCTL dont vous avez besoin est TCGETS (numéro 0x5401) qui récupère les paramètres actuels du terminal dans une structure termios. Le troisième paramètre est un pointeur vers une structure termios. À partir des sources du noyau, la structure termios est définie comme :

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

où tcflag_t a une longueur de 32 bits, cc_t a une longueur d'un octet et NCCS est actuellement défini comme 19. Consultez le manuel NASM pour savoir comment vous pouvez facilement définir et réserver de l'espace pour des structures comme celle-ci.

Donc, une fois que vous avez les termios actuels, vous devez effacer le drapeau canonique. Ce drapeau est dans le champ c_lflag, avec le masque ICANON (0x00000002). Pour l'effacer, calculez c_lflag AND (NOT ICANON). et stocker le résultat dans le champ c_lflag.

Vous devez maintenant informer le noyau de vos modifications apportées à la structure termios. Utilisez l'ioctl TCSETS (numéro 0x5402), le troisième paramètre définissant l'adresse de votre structure termios.

Si tout se passe bien, le terminal est maintenant en mode non canonique. Vous pouvez restaurer le mode canonique en définissant l'indicateur canonique (en associant c_lflag à ICANON) et en appelant à nouveau l'ioctl TCSETS. toujours restaurer le mode canonique avant de quitter

Comme je l'ai dit, ce n'est pas facile.


J'avais besoin de le faire récemment, et inspiré par l'excellente réponse de Callum, j'ai écrit ce qui suit (NASM pour 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

(Note de l'éditeur :n'utilisez pas int 0x80 en code 64 bits :que se passe-t-il si vous utilisez l'ABI Linux int 0x80 32 bits en code 64 bits ? - il se briserait dans un exécutable PIE (où les adresses statiques ne sont pas dans les 32 bits inférieurs), ou avec le tampon termios sur la pile. Il fonctionne en fait dans un exécutable traditionnel non-PIE, et cette version peut être facilement portée en mode 32 bits.)

Vous pouvez alors faire :

call canonical_off

Si vous lisez une ligne de texte, vous voudrez probablement aussi :

call echo_off

afin que chaque caractère ne soit pas répété lors de sa saisie.

Il existe peut-être de meilleures façons de procéder, mais cela fonctionne pour moi sur une installation Fedora 64 bits.

Plus d'informations peuvent être trouvées dans la page de manuel pour termios(3) , ou dans le termbits.h source.


Linux
  1. Comment désinstaller rbenv d'Ubuntu

  2. Comment imprimer un numéro en assembleur NASM ?

  3. Comment lire un seul caractère dans un script shell

  4. Comment puis-je faire en sorte que sed lise à partir de l'entrée standard?

  5. Comment lire depuis /proc/$pid/mem sous Linux ?

Comment exécuter des commandes à partir d'une entrée standard à l'aide de Tee et Xargs sous Linux

Comment réinstaller Ubuntu en mode Dual Boot ou Single Boot

Linux – Comment lire depuis /proc/$pid/mem sous Linux ?

Comment mettre à niveau de 12.04 à 12.10 à l'aide d'un CD ?

Comment mettre à niveau Ubuntu Server vers 20.04 à partir de 18.04

Comment désinstaller le navigateur chrome d'Ubuntu