
Ya, ya, itu adalah "byte" dan itu dalam bahasa India (bukan India). Saya akan mulai dalam rangka. Baru-baru ini di sini, di HabrΓ©, artikel tentang bytecode mulai muncul. Dan suatu ketika, saya senang menulis sistem Fort. Tentu saja, di assembler. Mereka 16-bit. Saya tidak pernah memprogram pada x86-64. Bahkan dengan 32 tidak bisa bermain. Jadi pikiran itu datang - mengapa tidak? Mengapa tidak mengaduk benteng 64-bit, dan bahkan dengan bytecode? Ya, dan di Linux, di mana saya juga tidak menulis sistem apa pun.
Saya memiliki server rumah dengan Linux. Secara umum, saya sedikit Google dan menemukan bahwa assembler di Linux disebut GAS, dan perintah sebagai. Saya terhubung melalui SSH ke server, mengetik - ya! Saya sudah menginstalnya. Masih butuh tautan, ketik ld - ya! Jadi, dan cobalah untuk menulis sesuatu yang menarik di assembler. Tanpa peradaban, hanya hutan, seperti orang India asli :) Tanpa lingkungan pengembangan, hanya baris perintah dan Komandan Tengah Malam. Editornya adalah Nano, yang tergantung pada F4 saya di mc. Bagaimana nyanyian kelompok "Nol"? Orang India sejati hanya membutuhkan satu hal ... Apa lagi yang dibutuhkan orang India sejati? Tentu saja, seorang debugger. Kami mengetik gdb - is! Nah, tekan Shift + F4, dan pergi!
Arsitektur
Sebagai permulaan, mari kita putuskan arsitektur. Dengan kedalaman bit sudah ditentukan, 64 bit. Dalam implementasi Fort klasik, segmen data dan kode adalah sama. Tapi, kami akan mencoba melakukannya dengan benar. Kami hanya akan memiliki kode di segmen kode, data di segmen data. Sebagai hasilnya, kami mendapatkan kernel untuk platform dan kode byte sepenuhnya platform-independen.
Mari kita coba untuk membuat mesin byte stack tercepat (tetapi tanpa JIT). Jadi, kita akan memiliki tabel yang berisi 256 alamat - satu untuk setiap perintah byte. Kurang dari apa pun - pemeriksaan tambahan, ini adalah 1-2 instruksi prosesor. Dan kita perlu cepat, tanpa kompromi.
Tumpukan
Biasanya, dalam implementasi Fort, tumpukan kembali prosesor (* SP) digunakan sebagai tumpukan data, dan sistem pengembalian tumpukan diterapkan dengan menggunakan cara lain. Memang, mesin kami akan ditumpuk, dan pekerjaan utamanya adalah pada tumpukan data. Karena itu, mari kita lakukan hal yang sama - RSP akan menjadi tumpukan data. Nah, biarkan stack kembali menjadi RBP, yang juga, secara default, berfungsi dengan segmen stack. Dengan demikian, kita akan memiliki tiga segmen memori: segmen kode, segmen data dan segmen tumpukan (akan memiliki tumpukan data dan tumpukan kembali).
Daftar
Saya masuk ke deskripsi register x86-64, dan oops! Ada sebanyak 8 register tujuan umum tambahan (R8 - R16), dibandingkan dengan mode 32 atau 16 bit ...
Sudah memutuskan bahwa mereka akan membutuhkan RSP dan RBP. Masih membutuhkan pointer (counter) dari perintah bytecode. Dari operasi register ini, hanya pembacaan memori yang diperlukan. Register utama (RAX, RBX, RCX, RDX, RSI, RDI) lebih fleksibel, universal, dengan mereka ada banyak perintah khusus. Mereka akan berguna bagi kita untuk berbagai tugas, dan untuk konter instruksi bytecode kita mengambil salah satu register baru untukku, biarlah R8.
Mari kita mulai
Saya tidak punya pengalaman pemrograman di Linux dalam bahasa assembly. Karena itu, sebagai permulaan, kami akan menemukan "Halo, dunia" yang telah selesai untuk memahami bagaimana program memulai dan menampilkan teks. Tanpa diduga bagi saya, saya menemukan opsi dengan sintaks aneh di mana bahkan sumber dan penerima diatur ulang. Ternyata, ini adalah sintaks AT&T, dan sebagian besar ditulis di bawah GAS. Tetapi opsi sintaks lain didukung, itu disebut sintaks Intel. Berpikir, saya memutuskan untuk menggunakannya sama saja. Nah, tulis di awal noprefix .intel_syntax.
Kompilasi dan jalankan "Halo, dunia" untuk memastikan semuanya berfungsi. Dengan membaca bantuan dan percobaan, saya mulai menggunakan perintah berikut untuk mengkompilasi:
$ as fort.asm -o fort.o -g -ahlsm >list.txt
Di sini, sakelar -o menunjukkan file hasil, sakelar -g menginstruksikan untuk menghasilkan informasi debug, dan sakelar -ahlsm menetapkan format daftar. Dan saya menyimpan hasilnya dalam daftar, di dalamnya Anda dapat melihat banyak hal berguna. Saya akui, pada awal pekerjaan saya tidak melakukan listing, dan bahkan tidak menentukan saklar -g. Saya mulai menggunakan saklar -g setelah penggunaan pertama debugger, dan mulai melakukan daftar setelah makro muncul dalam kode :)
Setelah itu, kami menggunakan tautan, tetapi tidak ada tempat yang lebih sederhana:
$ ld forth.o -o forth
Lari!
$ ./forth
Hello, world!
Itu bekerja.
Ini adalah pertama sebagainya.asme (sebenarnya itu adalah 'Hellow, dunia!', Tentu saja) .intel_syntax noprefix .section .data msg: .ascii "Hello, world!\n" len = . - msg # len .section .text .global _start # _start: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, OFFSET FLAT:msg # mov edx, len # int 0x80 # mov eax, 1 # β 1 β sys_exit xor ebx, ebx # 0 int 0x80 #
By the way, saya kemudian menemukan bahwa di x86-64 lebih tepat menggunakan syscall untuk panggilan sistem, daripada int 0x80. Panggilan 0x80 dianggap usang untuk arsitektur ini, meskipun didukung.
Awal telah dibuat, dan sekarang ...
Ayo pergi!
Bahwa akan ada setidaknya beberapa spesifik, kita akan menulis kode perintah satu byte. Biarkan itu menjadi kata Fort "0", menempatkan 0 di atas tumpukan:
bcmd_num0: push 0 jmp _next
Pada saat perintah ini dieksekusi, R8 sudah menunjuk ke perintah byte berikutnya. Penting untuk membacanya, menambah R8, menentukan alamat yang dapat dieksekusi dengan kode perintah byte, dan mentransfer kontrol ke sana.
Tapi ... berapa kedalaman bit dari tabel alamat byte-perintah? Kemudian saya harus cukup menggali ke dalam sistem perintah x86-64 baru untuk saya. Sayangnya, saya tidak menemukan perintah yang memungkinkan Anda pergi ke offset dalam memori. Jadi, baik menghitung alamat, atau alamat akan siap - 64 bit. Tidak ada waktu bagi kami untuk menghitung, yang berarti - 64 bit. Dalam hal ini, ukuran tabel akan 256 * 8 = 4096 byte. Akhirnya, enkode panggilan _next:
_next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] # bcmd - -
Lumayan, menurut saya ... Hanya ada tiga instruksi prosesor, ketika beralih dari satu perintah byte ke yang lain.
Sebenarnya, perintah-perintah ini tidak mudah bagi saya. Saya harus mempelajari sistem perintah 0x86-64 lagi dan menemukan perintah MOVZX baru untuk saya. Bahkan, perintah ini mengubah nilai 8, 16, atau 32 bit menjadi register 64-bit. Ada dua varian dari perintah ini: unsigned, di mana digit yang lebih tinggi diisi dengan nol, dan yang ditandatangani adalah MOVSX. Dalam versi yang ditandatangani, tanda itu mengembang, yaitu, untuk bilangan positif, nol akan menuju ke angka yang lebih tinggi, dan untuk yang negatif, yang. Opsi ini juga berguna bagi kita untuk perintah byte byte.
Omong-omong, apakah opsi ini yang tercepat? Mungkin seseorang akan menyarankan lebih cepat?
Nah, sekarang kita memiliki mesin byte yang dapat dijalankan melalui urutan perintah byte dan menjalankannya. Penting untuk mengujinya dalam praktik, untuk memaksa mengeksekusi setidaknya satu tim. Tapi yang mana? Nol di tumpukan? Tapi di sini Anda bahkan tidak tahu hasilnya, jika Anda tidak melihat tumpukan di bawah debugger ... Tetapi jika program dimulai, itu dapat diselesaikan :)
Kami menulis perintah selamat tinggal yang menyelesaikan program dan menulis tentang hal itu, terutama karena kami memiliki "Hellow, dunia!".
bcmd_bye: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 0 # 0 int 0x80 #
Satu-satunya yang tersisa adalah membuat tabel alamat byte-perintah, menginisialisasi register, dan memulai mesin byte. Jadi ... ada 256 nilai dalam tabel, dan ada dua perintah. Apa yang ada di sel lain?
Sisanya akan memiliki kode operasi yang tidak valid. Tetapi, Anda tidak dapat melakukan pemeriksaan padanya, ini adalah tim tambahan, kami memiliki tiga tim sekarang, dan dengan cek itu akan menjadi lima. Jadi, kita akan membuat perintah rintisan seperti itu - tim yang buruk. Pertama, kita mengisi seluruh tabel untuk itu, dan kemudian kita mulai menempati sel dengan perintah yang berguna. Biarkan tim yang buruk memiliki kode 0x00, tim selamat tinggal akan memiliki 0x01, dan '0' akan memiliki kode 0x02, begitu kode itu sudah ditulis. Tim yang buruk sejauh ini akan melakukan hal yang sama dengan selamat tinggal, hanya dengan kode dan teks penyelesaian yang berbeda (saya akan meletakkannya di spoiler, hampir sama dengan selamat tinggal):
bcmd_bad bcmd_bad: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 1 # 1 int 0x80 #
Sekarang gambarkan daftar alamat. Untuk kenyamanan, kita akan menempatkan delapan di setiap baris, akan ada 16 baris. Tabelnya cukup besar:
Tabel Alamat Perintah Byte bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad
Kami menulis isi dari program byte. Untuk melakukan ini, tetapkan kode perintah ke variabel assembler. Kami akan memiliki perjanjian berikut:
- Alamat untuk mengeksekusi perintah byte akan dimulai pada bcmd_
- Kode perintah itu sendiri akan disimpan dalam variabel yang dimulai dengan b_
Dengan demikian, isi dari program byte akan seperti ini:
start: .byte b_bye
Nyatakan ukuran tumpukan data sebagai stack_size. Biarkan sejauh ini 1024. Pada inisialisasi, kita akan melakukan RBP = RSP - stack_size.
Sebenarnya, kami mendapatkan kode program seperti itu (sebagainya.asm) .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_bad = 0x00 bcmd_bad: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 0 # 0 int 0x80 # b_num0 = 0x02 bcmd_num0: push 0 jmp _next
Kompilasi, jalankan:
$ as fort.asm -o fort.o -g -ahlsm >list.txt
$ ld forth.o -o forth
$ ./forth
bye!
Itu berhasil! Program bytecode pertama kami dari satu byte diluncurkan :)
Tentu saja, ini akan terjadi jika semuanya dilakukan dengan benar. Dan jika tidak, hasilnya mungkin seperti ini:
$ ./forth
Tentu saja, opsi lain dimungkinkan, tetapi saya telah menemukan ini paling sering. Dan kami membutuhkan debugger.
Lirik DebuggerSeperti yang sudah disebutkan, saya menggunakan GDB. Ini adalah debugger yang sangat kuat, tetapi dengan antarmuka baris perintah. Menjalankannya sangat sederhana:
$ gdb ./forth GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./forth...done. (gdb)
Selanjutnya, dengan memasukkan perintah, kami melakukan debug. Saya punya cukup waktu untuk menemukan beberapa perintah yang diperlukan dan belajar cara menggunakannya untuk debugging. Inilah mereka:
b <label> - atur breakpoint
l <label> - lihat kode sumber
r - mulai atau mulai ulang program
ir - lihat status register prosesor
s - step
Omong-omong, ingat bahwa Anda perlu mengkompilasi program dengan -g switch? Jika tidak, tag dan kode sumber tidak akan tersedia. Dalam hal ini, akan mungkin untuk melakukan debug hanya dengan kode yang dibongkar dan menggunakan alamat dalam memori. Kami, tentu saja, adalah orang India, tetapi tidak pada tingkat yang sama ...
Tetapi entah bagaimana programnya tidak banyak. Kami hanya mengatakan "Halo" padanya, dan dia segera mengatakan "Sampai jumpa!". Mari kita buat "Halo dunia!" pada bytecode. Untuk melakukan ini, letakkan alamat dan panjang string pada stack, kemudian jalankan perintah yang menampilkan string, dan kemudian perintah bye. Untuk melakukan semua ini, diperlukan perintah baru: ketik untuk menampilkan string, dan menyala untuk meletakkan alamat dan panjang string. Pertama kita tulis ketik, biarkan kodenya 0x80. Kami, sekali lagi, membutuhkan kode itu dengan panggilan sys_write:
b_type = 0x80 bcmd_type: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next
Di sini kita mengambil alamat dan panjang string dari tumpukan data menggunakan perintah POP. Memanggil int 0x80 dapat mengubah register R8, jadi kami menyimpannya. Kami tidak melakukan ini sebelumnya karena program ini berakhir. Isi register ini tidak peduli. Sekarang ini adalah perintah byte reguler, setelah itu kode byte terus dieksekusi, dan Anda perlu berperilaku sendiri.
Sekarang mari kita tuliskan lit. Ini akan menjadi tim pertama kami dengan parameter. Setelah byte dengan kode untuk perintah ini, akan ada byte yang berisi nomor yang akan diletakkan di stack. Pertanyaan segera muncul - kedalaman bit apa yang dibutuhkan di sini? Untuk memasukkan nomor apa pun, Anda perlu 64 bit. Tapi, setiap kali perintah akan menempati 9 byte, apa yang akan menempatkan satu angka? Jadi kita kehilangan kekompakan, salah satu sifat utama bytecode, dan kode benteng juga ...
Solusinya sederhana - kami akan membuat beberapa perintah untuk kedalaman bit yang berbeda. Ini akan menjadi lit8, lit16, lit32 dan lit64. Untuk angka kecil kita akan menggunakan lit8 dan lit16, untuk angka yang lebih besar - lit32 dan lit64. Jumlah kecil paling sering digunakan, dan bagi mereka akan ada perintah terpendek, yang membutuhkan dua byte. Tidak buruk! .. Kami akan membuat kode dari perintah ini 0x08 - 0x0B.
b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next
Di sini kita menggunakan perintah MOVSX - ini adalah versi ikon dari perintah MOVZX yang sudah kita kenal. R8 kami memiliki penghitung perintah byte. Kami memuat nilai ukuran yang diinginkan di atasnya, memindahkannya ke perintah berikutnya, dan menempatkan nilai dikonversi ke 64 bit ke tumpukan.
Jangan lupa untuk menambahkan alamat tim baru di tabel ke posisi yang diinginkan.Itu semua siap untuk menulis program pertama Anda "Halo, dunia!" pada bytecode kami. Mari bekerja dengan kompiler! :)
start: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_bye
Kami menggunakan dua perintah lit berbeda: lit64, yang akan meletakkan alamat string pada stack, dan lit8, yang dengannya kami meletakkan panjangnya di stack. Selanjutnya, kita menjalankan dua perintah byte lagi: ketik dan bye.
Kompilasi, jalankan:
$ as fort.asm -o fort.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth Hello, world! bye!
Hasilkan bytecode kami! Ini adalah hasil yang seharusnya jika semuanya normal.
Sumber lengkap .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x10 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x20 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x30 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_bad = 0x00 bcmd_bad: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 0 # 0 int 0x80 # b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_type = 0x80 bcmd_type: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next
Tetapi kemungkinannya masih sangat primitif, Anda tidak dapat membuat suatu kondisi, sebuah siklus.
Bagaimana tidak mungkin? Anda bisa, semuanya ada di tangan kita! Mari kita lakukan baris ini dalam loop 10 kali. Ini akan membutuhkan perintah cabang kondisional, serta sedikit aritmatika tumpukan: perintah yang mengurangi nilai pada tumpukan dengan 1 (pada benteng β1-β) dan perintah duplikasi simpul (βdupβ).
Dengan aritmatika, semuanya sederhana, saya bahkan tidak akan berkomentar:
b_dup = 0x18 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next
Sekarang lompatan bersyarat. Sebagai permulaan, mari kita buat tugas lebih sederhana - transisi tanpa syarat. Jelas bahwa Anda hanya perlu mengubah nilai register R8. Hal pertama yang terlintas dalam pikiran adalah perintah byte, diikuti oleh parameter - alamat transisi adalah 64 bit. Sekali lagi sembilan byte. Apakah kita memerlukan sembilan byte ini? Transisi biasanya terjadi pada jarak pendek, seringkali dalam beberapa ratus byte. Jadi, kita akan menggunakan bukan alamatnya, tetapi offsetnya!
Agak dalam? Dalam banyak kasus, 8 bit (127 maju / mundur) sudah cukup, tetapi kadang-kadang ini tidak cukup. Oleh karena itu, kami akan melakukan hal yang sama dengan perintah yang menyala, kami akan membuat dua opsi - 8 dan 16 digit, kode perintah akan menjadi 0x10 dan 0x11:
b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next
Sekarang transisi bersyarat mudah diimplementasikan. Jika tumpukannya 0, buka _next, dan jika tidak, buka perintah branch!
b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next
Sekarang kita memiliki segalanya untuk membuat lingkaran: start: .byte b_lit8 .byte 10 # # m0: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye
Dua perintah pertama - kita meletakkan penghitung loop pada stack. Selanjutnya, cetak string Halo. Kemudian kita kurangi 1 dari penghitung, duplikat dan lakukan (atau tidak melakukan) transisi. Perintah duplikasi diperlukan karena perintah cabang bersyarat mengambil nilai dari atas tumpukan. Transisi di sini adalah delapan-bit, karena jaraknya hanya beberapa byte.Kami menempatkan alamat perintah baru dalam sebuah tabel, kompilasi dan eksekusi.Saya akan meletakkannya di spoiler, jika tidak program kami menjadi verbose) $ as fort.asm -o fort.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! bye!
Yah, kita sudah bisa melakukan kondisi dan siklus!Sumber lengkap .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x10 .quad bcmd_dup, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x20 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x30 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit8 .byte 10 # # m0: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_bad = 0x00 bcmd_bad: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 0 # 0 int 0x80 # b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_type = 0x80 bcmd_type: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next b_dup = 0x18 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next
Tetapi sampai mesin byte yang lengkap hilang fungsi lain yang sangat penting. Kami tidak dapat memanggil orang lain dari bytecode. Kami tidak memiliki apa yang disebut rutinitas, prosedur, dll. Dan di benteng, tanpa ini, kita tidak bisa menggunakan kata-kata selain kata-kata inti dalam beberapa kata.Kami membawa pekerjaan sampai akhir. Di sini untuk pertama kalinya kami membutuhkan setumpuk pengembalian. Diperlukan dua perintah - perintah panggilan dan perintah kembali (panggilan dan keluar).Perintah panggilan, pada prinsipnya, melakukan hal yang sama seperti cabang - mentransfer kontrol ke bytecode lain. Tapi, tidak seperti cabang, Anda masih perlu menyimpan alamat pengirim di tumpukan kembali sehingga Anda bisa kembali dan melanjutkan eksekusi. Ada perbedaan lain - panggilan tersebut dapat terjadi pada jarak yang jauh lebih besar. Oleh karena itu, kami membuat perintah panggilan dalam rupa cabang, tetapi dalam tiga versi - 8, 16 dan 32 bit. b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next
Seperti yang Anda lihat, di sini, tidak seperti transisi, 3 tim ditambahkan. Salah satunya mengatur ulang R8 ke perintah byte berikutnya, dan dua sisanya menyimpan nilai yang diterima dalam tumpukan kembali. Ngomong-ngomong, di sini saya mencoba untuk tidak meletakkan instruksi prosesor yang saling bergantung satu sama lain, sehingga conveyor prosesor dapat menjalankan perintah secara paralel. Tapi saya tidak tahu berapa banyak ini memberikan efek. Jika diinginkan, maka Anda dapat memeriksa tes.Harus diingat bahwa pembentukan argumen untuk perintah panggilan agak berbeda dari untuk cabang. Untuk cabang, offset dihitung sebagai perbedaan antara alamat cabang dan alamat byte mengikuti perintah byte. Dan untuk perintah panggilan, ini adalah perbedaan antara alamat lompat dan alamat perintah berikutnya. Mengapa ini dibutuhkan?
Ini menghasilkan lebih sedikit instruksi prosesor.Sekarang perintah kembali. Sebenarnya, tugasnya hanya mengembalikan R8 dari stack kembali dan mentransfer kontrol ke mesin byte lebih lanjut: b_exit = 0x1F bcmd_exit: mov r8, [rbp] add rbp, 8 jmp _next
Perintah-perintah ini akan digunakan sangat sering, dan mereka perlu dioptimalkan secara maksimal. Perintah byte keluar menempati tiga instruksi mesin. Apakah mungkin untuk mengurangi sesuatu di sini? Ternyata kamu bisa! Anda cukup menghapus perintah transisi :)Untuk melakukan ini, letakkan di atas titik masuk mesin byte _next: b_exit = 0x1F bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8]
By the way, perintah yang paling penting dan sering digunakan (misalnya, seperti panggilan) perlu ditempatkan lebih dekat ke mesin byte sehingga kompiler dapat membentuk perintah lompatan pendek. Ini terlihat jelas dalam daftar. Berikut ini sebuah contoh. 262 0084 490FBE00 bcmd_lit8: movsx rax, byte ptr [r8] 263 0088 49FFC0 inc r8 264 008b 50 push rax 265 008c EB90 jmp _next 266 267 b_lit16 = 0x09 268 008e 490FBF00 bcmd_lit16: movsx rax, word ptr [r8] 269 0092 4983C002 add r8, 2 270 0096 50 push rax 271 0097 EB85 jmp _next 272 273 b_lit32 = 0x0A 274 0099 496300 bcmd_lit32: movsx rax, dword ptr [r8] 275 009c 4983C004 add r8, 4 276 00a0 50 push rax 277 00a1 E978FFFF jmp _next 277 FF 278
Di sini, pada baris 265 dan 271, perintah jmp masing-masing mengambil 2 byte, dan pada baris 277, perintah yang sama sudah dikompilasi menjadi 5 byte, karena jarak lompatan melebihi panjang perintah pendek.Oleh karena itu, perintah byte seperti bad, bye, type diatur ulang lebih lanjut, dan seperti panggilan, cabang, lit lebih dekat. Sayangnya, tidak banyak yang dapat ditampung dalam transisi 127 byte.Kami menambahkan perintah baru ke tabel alamat perintah sesuai dengan kode mereka.Jadi, kami sekarang memiliki tantangan dan pengembalian, kami akan menguji mereka! Untuk melakukan ini, pilih jalur cetak dalam prosedur terpisah, dan kami akan memanggilnya secara berulang dua kali. Dan jumlah pengulangan siklus dikurangi menjadi tiga. start: .byte b_lit8 .byte 3 # # m0: .byte b_call16 .word sub_hello - . - 2 .byte b_call16 .word sub_hello - . - 2 .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye sub_hello: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_exit
Call8 dapat digunakan di sini, tetapi saya memutuskan untuk menggunakan call16 sebagai yang paling mungkin digunakan. Nilai 2 dikurangi karena keanehan menghitung alamat untuk perintah byte panggilan yang saya tulis. Untuk call8, 1 akan dikurangkan di sini, untuk call32, masing-masing, 4. Kamimengkompilasi dan memanggil: $ as forth.asm -o forth.o -g -ahlsm>list.txt $ ld forth.o -o forth $ ./forth Hello, world! Bad byte code!
Ups ... seperti kata mereka, ada yang salah :) Yah, kami meluncurkan GDB dan melihat apa yang terjadi di sana. Saya segera menetapkan breakpoint pada bcmd_exit, karena jelas bahwa panggilan sub_hello sudah lewat, dan badan prosedur sedang mengeksekusi ... diluncurkan ... dan program tidak mencapai breakpoint. Segera ada kecurigaan kode perintah byte. Dan, memang, alasannya ada di dalam dirinya. b_exit Saya menetapkan nilai 0x1f, dan alamat itu sendiri ditempatkan di nomor sel tabel 0x17. Baiklah, maka saya akan memperbaiki nilai b_exit ke 0x17 dan coba lagi: $ as forth.asm -o forth.o -g -ahlsm>list.txt $ ld forth.o -o forth $ ./forth Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! bye!
Tepat enam kali sapaan, dan sekali selamat tinggal. Seperti yang seharusnya :)Sumber lengkap .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_call8, bcmd_call16, bcmd_call32, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_exit # 0x10 .quad bcmd_dup, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x20 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x30 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit8 .byte 3 # # m0: .byte b_call16 .word sub_hello - . - 2 .byte b_call16 .word sub_hello - . - 2 .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye sub_hello: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_exit .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next b_exit = 0x17 bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_dup = 0x18 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next b_bad = 0x00 bcmd_bad: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # β 1 β sys_exit mov ebx, 0 # 0 int 0x80 # b_type = 0x80 bcmd_type: mov eax, 4 # β 4 β sys_write mov ebx, 1 # β 1 β stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next
Apa hasilnya
Kami melakukan dan menguji mesin byte byte 64-bit yang lengkap dan cukup cepat. Dalam kecepatan, mungkin mesin byte ini akan menjadi salah satu yang tercepat di kelasnya (mesin stack byte tanpa JIT). Dia tahu bagaimana mengeksekusi perintah secara berurutan, melakukan lompatan bersyarat dan tanpa syarat, prosedur panggilan, dan kembali darinya. Pada saat yang sama, bytecode yang digunakan cukup kompak. Pada dasarnya, perintah byte membutuhkan 1-3 byte, lebih banyak sangat jarang (hanya sejumlah besar, dan panggilan prosedur yang sangat jauh). Satu set kecil perintah byte juga dibuat sketsa, yang mudah diperluas. Misalkan semua perintah dasar untuk bekerja dengan stack (drop, swap, over, root, dll dapat ditulis dalam 20 menit, jumlah yang sama akan masuk ke perintah integer aritmatika).Poin penting lainnya. Bytecode, tidak seperti kode benteng dijahit langsung klasik, tidak mengandung instruksi mesin, sehingga dapat ditransfer tanpa kompilasi ulang ke platform lain. Cukup dengan menulis ulang kernel sekali ke sistem instruksi prosesor baru, dan ini dapat dilakukan dengan sangat cepat.Versi mesin byte saat ini tidak spesifik untuk bahasa tertentu. Tetapi saya ingin membuat implementasi bahasa Fort di atasnya karena saya memiliki pengalaman dengannya, dan kompiler untuk itu dapat dilakukan dengan sangat cepat.Jika ada minat dalam hal ini, berdasarkan mesin ini, pada artikel selanjutnya, saya akan melakukan input-output dari string dan angka, kamus benteng, dan penerjemah. Anda dapat "menyentuh" ββtim dengan tangan Anda. Nah, pada artikel ketiga kita akan membuat kompiler, dan kita mendapatkan sistem benteng yang hampir lengkap. Maka dimungkinkan untuk menulis dan menyusun beberapa algoritma standar dan membandingkan kinerjanya dengan bahasa dan sistem lain. Anda dapat menggunakan, misalnya, saringan Eratosthenes, dan sejenisnya.Sangat menarik untuk bereksperimen dengan opsi. Sebagai contoh, buat tabel perintah 16-bit, dan lihat bagaimana ini akan mempengaruhi kinerja. Anda juga dapat mengubah titik entri _next menjadi makro, dalam hal ini kode mesin dari setiap byte perintah akan bertambah ukurannya dengan dua perintah (minus transisi dan ditambah tiga perintah dari _next). Artinya, pada akhirnya tidak akan ada transisi ke _next, tetapi isi dari titik _next itu sendiri (ini adalah 14 byte). Sangat menarik untuk mengetahui bagaimana ini akan mempengaruhi kinerja. Anda juga dapat mencoba melakukan optimasi menggunakan register. Misalnya, loop standar dengan penghitung di benteng menyimpan penghitung di tumpukan kembali. Anda dapat membuat versi register dan juga mengujinya.Anda juga dapat membuat kompilator ekspresi yang ditulis dalam bentuk klasik (misalnya, A = 5 + (B + C * 4)).Secara umum, ada ruang untuk eksperimen! :)
Lanjutan: Mesin-byte untuk benteng (dan tidak hanya) di Native American (bagian 2)