GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana cara membaca input karakter tunggal dari keyboard menggunakan nasm (assembly) di bawah ubuntu?

Itu bisa dilakukan dari perakitan, tetapi itu tidak mudah. Anda tidak dapat menggunakan int 21h, itu adalah panggilan sistem DOS dan tidak tersedia di Linux.

Untuk mendapatkan karakter dari terminal di bawah sistem operasi mirip UNIX (seperti Linux), Anda membaca dari STDIN (file nomor 0). Biasanya, panggilan sistem baca akan diblokir hingga pengguna menekan enter. Ini disebut mode kanonik. Untuk membaca satu karakter tanpa menunggu pengguna menekan enter, Anda harus menonaktifkan mode kanonis terlebih dahulu. Tentu saja, Anda harus mengaktifkannya kembali jika menginginkan input baris nanti, dan sebelum program Anda ditutup.

Untuk menonaktifkan mode kanonik di Linux, Anda mengirim IOCTL (IO ControL) ke STDIN, menggunakan ioctl syscall. Saya menganggap Anda tahu cara melakukan panggilan sistem Linux dari assembler.

Syscall ioctl memiliki tiga parameter. Yang pertama adalah file untuk mengirim perintah ke (STDIN), yang kedua adalah nomor IOCTL, dan yang ketiga biasanya berupa penunjuk ke struktur data. ioctl mengembalikan 0 jika berhasil, atau kode kesalahan negatif jika gagal.

IOCTL pertama yang Anda butuhkan adalah TCGETS (nomor 0x5401) yang mendapatkan parameter terminal saat ini dalam struktur termios. Parameter ketiga adalah penunjuk ke struktur termios. Dari sumber kernel, struktur termios didefinisikan sebagai:

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

di mana tcflag_t panjangnya 32 bit, cc_t panjangnya satu byte, dan NCCS saat ini didefinisikan sebagai 19. Lihat manual NASM untuk cara mudah menentukan dan mencadangkan ruang untuk struktur seperti ini.

Jadi, setelah Anda mendapatkan termios saat ini, Anda perlu menghapus bendera kanonis. Bendera ini ada di bidang c_lflag, dengan topeng ICANON (0x00000002). Untuk menghapusnya, hitung c_lflag AND (BUKAN ICANON). dan simpan hasilnya kembali ke bidang c_lflag.

Sekarang Anda perlu memberi tahu kernel tentang perubahan Anda pada struktur termios. Gunakan TCSETS (nomor 0x5402) ioctl, dengan parameter ketiga atur alamat struktur termios Anda.

Jika semuanya berjalan dengan baik, terminal sekarang dalam mode non-kanonik. Anda dapat memulihkan mode kanonis dengan menyetel bendera kanonis (dengan ORing c_lflag dengan ICANON) dan memanggil ioctl TCSETS lagi. selalu pulihkan mode kanonis sebelum Anda keluar

Seperti yang saya katakan, itu tidak mudah.


Saya perlu melakukan ini baru-baru ini, dan terinspirasi oleh jawaban Callum yang luar biasa, saya menulis yang berikut (NASM untuk 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

(Catatan editor:jangan gunakan int 0x80 dalam kode 64-bit:Apa yang terjadi jika Anda menggunakan ABI Linux 0x80 int 32-bit dalam kode 64-bit? - itu akan merusak PIE yang dapat dieksekusi (di mana alamat statis tidak dalam 32 bit rendah), atau dengan buffer termios di tumpukan. Ini benar-benar berfungsi dalam executable non-PIE tradisional, dan versi ini dapat dengan mudah dipindahkan ke mode 32-bit.)

Anda kemudian dapat melakukan:

call canonical_off

Jika Anda membaca sebaris teks, Anda mungkin juga ingin melakukan:

call echo_off

sehingga setiap karakter tidak digaungkan saat diketik.

Mungkin ada cara yang lebih baik untuk melakukan ini, tetapi ini bekerja untuk saya pada instalasi Fedora 64-bit.

Informasi lebih lanjut dapat ditemukan di halaman manual untuk termios(3) , atau di termbits.h sumber.


Linux
  1. Cara menghapus instalan neovim dari Ubuntu

  2. Cara menghapus instalan rhythmbox-plugin dari Ubuntu

  3. Cara menghapus instalan uap dari Ubuntu

  1. Bagaimana saya mendapatkan sed untuk membaca dari input standar?

  2. Bagaimana cara menempel dari buffer dalam mode ex vim?

  3. Bagaimana saya bisa membaca input dari keyboard host saat terhubung melalui SSH?

  1. Linux – Bagaimana Cara Membaca Dari /proc/$pid/mem Di Linux?

  2. Ubuntu – Bagaimana Mencegah Grub Menggunakan/memulai Ui Grafis?

  3. Bagaimana cara membaca dari /proc/$pid/mem di Linux?