Bagaimana kita beralih dari perakitan ke kode mesin (pembuatan kode)

16

Apakah ada cara mudah untuk memvisualisasikan langkah antara merakit kode ke kode mesin?

Misalnya jika Anda membuka tentang file biner di notepad, Anda akan melihat representasi kode mesin yang diformat secara tekstual. Saya berasumsi bahwa setiap byte (simbol) yang Anda lihat adalah karakter ascii yang sesuai untuk nilai binernya?

Tapi bagaimana kita beralih dari assembly ke binary, apa yang terjadi di balik layar ??

pengguna12979
sumber

Jawaban:

28

Lihatlah dokumentasi kumpulan instruksi, dan Anda akan menemukan entri seperti ini dari mikrokontroler pic untuk setiap instruksi:

contoh instruksi addlw

Baris "encoding" memberi tahu seperti apa instruksi itu dalam biner. Dalam hal ini, selalu dimulai dengan 5 yang, kemudian sedikit tidak peduli (yang dapat berupa satu atau nol), kemudian singkatan "k" untuk literal yang Anda tambahkan.

Beberapa bit pertama disebut "opcode," adalah unik untuk setiap instruksi. CPU pada dasarnya melihat opcode untuk melihat instruksi apa itu, lalu ia tahu untuk mendekode "k" sebagai angka yang akan ditambahkan.

Itu membosankan, tetapi tidak sulit untuk menyandikan dan memecahkan kode. Saya memiliki kelas sarjana di mana kami harus melakukannya dengan ujian langsung.

Untuk benar-benar membuat file yang dapat dieksekusi penuh, Anda juga harus melakukan hal-hal seperti mengalokasikan memori, menghitung offset cabang, dan memasukkannya ke dalam format seperti ELF , tergantung pada sistem operasi Anda.

Karl Bielefeldt
sumber
10

Opcode perakitan memiliki, sebagian besar, korespondensi satu-ke-satu dengan instruksi mesin yang mendasarinya. Jadi yang harus Anda lakukan adalah mengidentifikasi setiap opcode dalam bahasa assembly, memetakannya ke instruksi mesin yang sesuai, dan menulis instruksi mesin ke file, bersama dengan parameter yang sesuai (jika ada). Anda kemudian ulangi proses untuk setiap opcode tambahan dalam file sumber.

Tentu saja, dibutuhkan lebih dari itu untuk membuat file yang dapat dieksekusi yang akan memuat dengan benar dan berjalan pada sistem operasi, dan kebanyakan perakit yang layak memiliki beberapa kemampuan tambahan di luar pemetaan sederhana opcode ke instruksi mesin (seperti makro, misalnya).

Robert Harvey
sumber
7

Hal pertama yang Anda butuhkan adalah sesuatu seperti file ini . Ini adalah database instruksi untuk prosesor x86 yang digunakan oleh assembler NASM (yang saya bantu tulis, meskipun bukan bagian yang benar-benar menerjemahkan instruksi). Mari kita pilih garis arbitrer dari database:

ADD   rm32,imm8    [mi:    hle o32 83 /0 ib,s]      386,LOCK

Apakah ini berarti bahwa itu menggambarkan instruksi ADD. Ada beberapa varian dari instruksi ini, dan yang spesifik yang sedang dijelaskan di sini adalah varian yang mengambil register 32-bit atau alamat memori dan menambahkan nilai 8-bit langsung (yaitu konstanta yang langsung dimasukkan dalam instruksi). Contoh instruksi perakitan yang akan menggunakan versi ini adalah ini:

add eax, 42

Sekarang, Anda perlu mengambil input teks dan menguraikannya menjadi instruksi dan operan individual. Untuk instruksi di atas, ini mungkin akan menghasilkan struktur yang berisi instruksi ADD,, dan array operan (referensi ke register EAXdan nilai 42). Setelah Anda memiliki struktur ini, Anda menjalankan melalui database instruksi dan menemukan baris yang cocok dengan nama instruksi dan jenis operan. Jika Anda tidak menemukan kecocokan, itu adalah kesalahan yang perlu disajikan kepada pengguna ("kombinasi opcode dan operan ilegal" atau yang serupa adalah teks yang biasa).

Setelah kami mendapatkan baris dari database, kami melihat kolom ketiga, yang untuk instruksi ini adalah:

[mi:    hle o32 83 /0 ib,s] 

Ini adalah serangkaian instruksi yang menjelaskan cara membuat instruksi kode mesin yang diperlukan:

  • Ini miadalah deskripsi operan: satu operan modr/m(register atau memori) (yang berarti kita harus menambahkan modr/mbyte ke akhir instruksi, yang akan kita bahas nanti) dan satu lagi merupakan instruksi langsung (yang akan digunakan dalam deskripsi instruksi).
  • Berikutnya adalah hle. Ini mengidentifikasi bagaimana kami menangani awalan "kunci". Kami belum menggunakan "kunci", jadi kami abaikan.
  • Berikutnya adalah o32. Ini memberitahu kita bahwa jika kita merakit kode untuk format output 16-bit, instruksi tersebut membutuhkan awalan override ukuran operan. Jika kami memproduksi output 16-bit, kami akan menghasilkan awalan sekarang ( 0x66), tapi saya akan menganggap kami tidak dan melanjutkan.
  • Berikutnya adalah 83. Ini adalah byte literal dalam heksadesimal. Kami memproduksinya.
  • Berikutnya adalah /0. Ini menentukan beberapa bit tambahan yang akan kita butuhkan dalam modr / m bytem, ​​dan menyebabkan kita menghasilkannya. The modr/mbyte digunakan untuk register encode atau referensi memori tidak langsung. Kami memiliki satu operan seperti itu, sebuah register. Register memiliki nomor, yang ditentukan dalam file data lain :

    eax     REG_EAX         reg32           0
  • Kami memeriksa apakah reg32setuju dengan ukuran instruksi yang diperlukan dari basis data asli (memang). Ini 0adalah nomor register. Sebuah modr/mbyte adalah struktur data yang ditentukan oleh prosesor, yang terlihat seperti ini:

     (most significant bit)
     2 bits       mod    - 00 => indirect, e.g. [eax]
                           01 => indirect plus byte offset
                           10 => indirect plus word offset
                           11 => register
     3 bits       reg    - identifies register
     3 bits       rm     - identifies second register or additional data
     (least significant bit)
  • Karena kami bekerja dengan register, modisiannya adalah 0b11.

  • The reglapangan jumlah register yang kami gunakan,0b000
  • Karena hanya ada satu register dalam instruksi ini, kita perlu mengisi kolom rmdengan sesuatu. Untuk itulah data tambahan ditentukan /0, jadi kami letakkan itu di rmbidang 0b000,.
  • The modr/mOleh karena itu byte 0b11000000atau 0xC0. Kami menampilkan ini.
  • Berikutnya adalah ib,s. Ini menentukan byte langsung yang ditandatangani. Kami melihat operan dan perhatikan kami memiliki nilai langsung yang tersedia. Kami mengonversinya menjadi byte yang ditandatangani dan mengeluarkannya ( 42=> 0x2A).

Oleh karena itu instruksi dirakit lengkap: 0x83 0xC0 0x2A. Kirim ke modul output Anda, bersama dengan catatan bahwa tidak ada byte yang merupakan referensi memori (modul output mungkin perlu tahu jika mereka melakukannya).

Ulangi untuk setiap instruksi. Pantau label sehingga Anda tahu apa yang harus disisipkan saat direferensikan. Tambahkan fasilitas untuk makro dan arahan yang diteruskan ke modul output file objek Anda. Dan ini pada dasarnya cara kerja assembler.

Jules
sumber
1
Terima kasih. Penjelasan yang bagus tetapi tidak seharusnya "0x83 0xC0 0x2A" daripada "0x83 0xB0 0x2A" karena 0b11000000 = 0xC0
Kamran
@ Kamran - $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003... ya, Anda benar. :)
Jules
2

Dalam praktiknya, assembler biasanya tidak menghasilkan secara langsung beberapa biner yang dapat dieksekusi , tetapi beberapa file objek (untuk diumpankan nanti ke linker ). Namun, ada pengecualian (Anda dapat menggunakan beberapa assembler untuk menghasilkan langsung beberapa biner yang dapat dieksekusi; mereka tidak umum).

Pertama, perhatikan bahwa banyak perakit saat ini adalah program perangkat lunak gratis . Jadi unduh dan kompilasi di komputer Anda kode sumber GNU sebagai (bagian dari binutils ) dan nasm . Kemudian pelajari kode sumber mereka. BTW, saya sarankan menggunakan Linux untuk tujuan itu (ini adalah OS yang sangat ramah bagi pengembang dan perangkat lunak ramah).

File objek yang diproduksi oleh assembler berisi terutama segmen kode dan instruksi relokasi . Ini diatur dalam format file yang terdokumentasi dengan baik, yang tergantung pada sistem operasi. Di Linux, format itu (digunakan untuk file objek, pustaka bersama, dump inti, dan executable) adalah ELF . File objek itu kemudian diinput ke linker (yang akhirnya menghasilkan executable). Relokasi ditentukan oleh ABI (mis. AB86 x86-64 ). Baca buku Levine Linker and Loaders untuk lebih lanjut.

Segmen kode dalam file objek tersebut berisi kode mesin berlubang (harus diisi, dengan bantuan informasi relokasi, oleh tautan). Kode mesin (relocatable) yang dihasilkan oleh assembler jelas khusus untuk arsitektur set instruksi . The x86 atau x86-64 (digunakan di sebagian besar prosesor laptop atau desktop) ISA sangat rumit dalam perinciannya. Tetapi subset yang disederhanakan, disebut y86 atau y86-64, telah ditemukan untuk tujuan pengajaran. Baca slide pada mereka. Jawaban lain untuk pertanyaan ini juga menjelaskan sedikit tentang itu. Anda mungkin ingin membaca buku bagus tentang Arsitektur Komputer .

Kebanyakan assembler bekerja dalam dua lintasan , yang kedua memancarkan relokasi atau mengoreksi beberapa output dari lintasan pertama. Mereka menggunakan teknik parsing yang sekarang biasa (jadi baca mungkin The Dragon Book ).

Bagaimana sebuah executable dijalankan oleh kernel OS (mis. Bagaimana execvesystem call bekerja di Linux) adalah pertanyaan yang berbeda (dan kompleks). Biasanya mengatur beberapa ruang alamat virtual (dalam proses melakukan itu mengeksekusi (2) ...) kemudian menginisialisasi ulang keadaan internal proses (termasuk register mode pengguna ). Sebuah linker dinamis -seperti ld-linux.so (8) dari linux- mungkin terlibat pada saat runtime. Baca buku yang bagus, seperti Sistem Operasi: Tiga Potong Mudah . The pengembangan sistem operasi wiki juga memberikan informasi yang berguna.

PS. Pertanyaan Anda sangat luas sehingga Anda perlu membaca beberapa buku tentangnya. Saya telah memberikan beberapa referensi (sangat tidak lengkap). Anda harus menemukan lebih banyak dari mereka.

Basile Starynkevitch
sumber
1
Mengenai format file objek, untuk pemula saya akan merekomendasikan melihat format RDOFF yang diproduksi oleh NASM. Ini sengaja dirancang untuk sesederhana mungkin secara realistis dan masih berfungsi dalam berbagai situasi. Sumber NASM termasuk tautan dan pemuat untuk format. (Pengungkapan penuh - Saya merancang dan menulis semua ini)
Jules