Di topik ini, program assembly pertama yang berperilaku nyata akan dibuat, yaitu menulis satu baris teks ke terminal lalu mengakhiri dirinya. Program yang sama ditulis dua kali, sekali dalam sintaks Intel yang dirakit dengan NASM dan sekali dalam sintaks AT&T yang dirakit dengan GNU as. Kedua executable kemudian dijalankan dan dibandingkan hingga level instruksinya. Hasil akhirnya adalah pemahaman atas notasi kedua sintaks pada program yang cukup kecil untuk dibaca baris per baris.
Program yang dibangun#
Program yang dibangun melakukan dua hal, yaitu menulis teks Halo, dunia! ke terminal dan berhenti dengan kode keluar 0. Kedua hal itu dicapai melalui syscall. Penulisan teks menggunakan syscall write yang bernomor 1, sedangkan pengakhiran program menggunakan syscall exit yang bernomor 60.
Kontrak pemanggilannya seragam. Nomor syscall diletakkan pada register rax, sedangkan argumennya pada rdi, rsi, dan rdx. Untuk write, ketiga argumen itu berisi file descriptor tujuan, alamat teks, dan panjang teks. File descriptor sendiri adalah nomor yang merujuk pada sumber data terbuka dan ditetapkan oleh kernel, sedangkan angka 1 menunjuk standard output atau stdout yang tampil di terminal. Untuk exit, argumennya hanya kode keluar program. Mekanisme syscall secara menyeluruh bukan cakupan topik ini, dan yang diperlukan hanya nomor serta register pembawanya.
Versi Intel dengan NASM#
Sintaks Intel menulis operan dengan urutan tujuan lebih dulu, baru sumbernya, serta menuliskan register tanpa prefiks. Berkas berikut adalah program lengkap versi Intel yang dirakit dengan NASM.
default rel
section .data
pesan db "Halo, dunia!", 10
panjang equ $ - pesan
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
lea rsi, [pesan]
mov rdx, panjang
syscall
mov rax, 60
xor rdi, rdi
syscall
section .note.GNU-stack noalloc noexec nowrite progbitsDirektif default rel pada baris pertama membuat referensi memori ditulis relatif terhadap RIP, yaitu register yang selalu menunjuk instruksi berikutnya. Section .data menyimpan pesan melalui direktif db, sedangkan section .text menyimpan instruksinya. Angka 10 di akhir pesan adalah byte newline agar tulisannya berpindah baris. Panjang pesan dihitung oleh panjang equ $ - pesan, yaitu selisih posisi saat ini terhadap awal pesan, bernilai 13 byte. Simbol _start ditandai global karena menjadi titik masuk yang dicari linker.
Baris inti ada pada lea rsi, [pesan] yang menyalin alamat pesan ke rsi, bukan isinya. Rangkaian mov rax, 1 sampai syscall pertama membentuk penulisan teks, sedangkan mov rax, 60 dan syscall kedua mengakhiri program dengan rdi bernilai 0 hasil dari xor rdi, rdi. Direktif section .note.GNU-stack di bagian bawah menandai bahwa stack program tidak eksekutif, suatu konvensi keamanan yang membuat linker tidak menampilkan peringatan.
Tiga perintah berikut merakit, menautkan, lalu menjalankan berkas tersebut.
nasm -f elf64 halo-intel.asm -o halo-intel.o # rakit berkas objek
ld halo-intel.o -o halo-intel # tautkan menjadi executable
./halo-intel # jalankan program
echo "exit=$?" # kode keluar dari programHalo, dunia!
exit=0Teks Halo, dunia! tampil karena syscall write berhasil, sedangkan exit=0 pada baris kedua adalah kode yang ditinggalkan program dan dibaca shell dari $?. Opsi -f elf64 pada perintah nasm memilih format berkas objek ELF 64-bit yang menjadi format standar Linux x86-64.
Versi AT&T dengan GNU as#
Sintaks AT&T menulis operan dengan urutan sumber lebih dulu, baru tujuannya, serta menandai register dengan prefiks % dan nilai langsung dengan prefiks $. Berkas berikut adalah program yang sama dalam notasi itu, dirakit dengan GNU as.
.section .data
pesan:
.ascii "Halo, dunia!\n"
panjang = . - pesan
.section .text
.globl _start
_start:
movq $1, %rax
movq $1, %rdi
lea pesan(%rip), %rsi
movq $panjang, %rdx
syscall
movq $60, %rax
xorq %rdi, %rdi
syscall
.section .note.GNU-stack,"",@progbitsDirektif AT&T ditulis dengan titik di awal, yaitu .section, .globl, dan .ascii. Direktif .ascii menyimpan byte persis seperti yang ditulis, tanpa karakter penutup, sehingga panjangnya dihitung oleh panjang = . - pesan yang bernilai 13 byte, sedangkan notasi \n di dalam string menyatakan byte newline. Nilai langsung ditulis dengan $, sehingga movq $1, %rax berarti menyalin angka 1 ke rax.
Akhiran q pada movq dan xorq menyatakan ukuran operasi 64-bit, sedangkan pada sintaks Intel ukuran itu mengikuti nama register rax. Alamat pesan diperoleh melalui lea pesan(%rip), %rsi yang setara dengan lea rsi, [pesan] pada versi NASM. Bagian akhir program mengakhiri dirinya melalui syscall exit dengan rdi bernilai 0 hasil dari xorq %rdi, %rdi.
Tiga perintah berikut merakit, menautkan, lalu menjalankan berkas tersebut.
as -o halo-att.o halo-att.s # rakit berkas objek
ld halo-att.o -o halo-att # tautkan menjadi executable
./halo-att # jalankan program
echo "exit=$?" # kode keluar dari programHalo, dunia!
exit=0Keluaran kedua executable sama persis, yaitu teks Halo, dunia! diikuti kode keluar 0, meskipun sumbernya dirakit oleh assembler yang berbeda. Perbedaannya murni terletak pada notasi sumber.
Dua notasi untuk satu program#
Kesetaraan itu dapat dilihat langsung pada instruksinya. Perintah berikut menampilkan isi section .text kedua executable dengan objdump.
objdump -d --no-show-raw-insn halo-intel
objdump -d --no-show-raw-insn halo-atthalo-intel: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <_start>:
401000: mov $0x1,%eax
401005: mov $0x1,%edi
40100a: lea 0xfef(%rip),%rsi # 402000 <pesan>
401011: mov $0xd,%edx
401016: syscall
401018: mov $0x3c,%eax
40101d: xor %rdi,%rdi
401020: syscall
halo-att: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <_start>:
401000: mov $0x1,%rax
401007: mov $0x1,%rdi
40100e: lea 0xfeb(%rip),%rsi # 402000 <pesan>
401015: mov $0xd,%rdx
40101c: syscall
40101e: mov $0x3c,%rax
401025: xor %rdi,%rdi
401028: syscallDelapan instruksi _start pada keduanya memuat daftar operasi yang sama, yaitu menyiapkan nomor syscall dan argumennya, memanggil syscall, lalu mengulang pola itu untuk exit. Perbedaan tampilannya kecil dan tidak mengubah makna. NASM memilih bentuk register 32-bit ketika nilai langsungnya muat dalam 32 bit sehingga tampil mov $0x1,%eax, sedangkan GNU as mengikuti penulisan movq 64-bit dari sumbernya sehingga tampil mov $0x1,%rax.
| Aspek | Intel (NASM) | AT&T (GNU as) |
|---|---|---|
| Urutan operan | tujuan, sumber | sumber, tujuan |
| Register | rax | %rax |
| Nilai langsung | 60 | $60 |
| Ukuran operasi | mengikuti nama register (rax) | akhiran mnemonic (movq) |
| Alamat RIP-relative | lea rsi, [pesan] | lea pesan(%rip), %rsi |
| Direktif data | db, equ | .ascii, = |
| Simbol global | global | .globl |
| Komentar | ; | # |
| Perintah assembler | nasm -f elf64 | as |
Tabel di atas merangkum perbedaan notasi yang muncul pada program kecil ini. Keduanya bukan soal benar atau salah. Dokumentasi resmi Intel dan konvensi pada platform Windows menulis sintaks Intel, sedangkan perkakas bawaan Linux seperti objdump menampilkan sintaks AT&T secara default. Jalur ini menggunakan kedua sintaks secara bergantian agar pembaca tetap terbiasa membaca keduanya.
Ringkasan#
- Program pertama memanggil dua syscall, yaitu
writebernomor 1 untuk menulis teks danexitbernomor 60 untuk mengakhiri dengan kode 0. - Versi NASM ditulis dalam sintaks Intel dengan urutan tujuan, sumber, sedangkan versi GNU as ditulis dalam sintaks AT&T dengan urutan sumber, tujuan.
- Perbedaan notasi lainnya mencakup prefiks
%pada register, prefiks$pada nilai langsung, akhiran ukuran pada mnemonic, dan direktif bertitik. objdumpmemperlihatkan bahwa kedua executable memuat instruksi mesin yang setara meskipun notasi sumbernya berbeda.
Referensi#
- NASM 2.16.03 documentation — NASM Project
— Sintaks Intel dan direktif NASM yang dipakai pada
halo-intel.asm. - GNU as manual — Sourceware
— Sintaks AT&T dan direktif GNU as yang dipakai pada
halo-att.s. - syscall(2) — Linux man-pages
— Konvensi pemanggilan syscall dengan nomor pada
raxdan argumen pada register lain. - ld(1) — Linux man-pages — Peran linker yang menggabungkan berkas objek menjadi executable.