Daftar Isi
- Apa Itu Assembly?
- Mengenal Register
- Membuat Program Hello World
- Mengenal section .data
- Mengenal section .text
- Bagaimana Program Menampilkan Teks?
- Mengakhiri Program
- Memasang NASM
- Merakit Program Assembly
- Mengapa Tidak Langsung Menjalankan File .asm?
- Mulai Mengenal Debugging
- Memasang GDB
- Membuat Object File dengan Informasi Debug
- Menjalankan Program dengan GDB
- Membuat Breakpoint
- Melihat Isi Register
- Menjalankan Instruksi Satu per Satu
- Melihat Isi Memori
- Melihat Instruksi Mesin
- Contoh Alur Debugging Sederhana
- Bagaimana Jika Program Error?
- Assembly Tidak Harus Langsung Dihafalkan
- Penutup
Halo Sobat, kali ini kita akan sedikit turun lebih dekat ke cara kerja komputer, yaitu mengenal bahasa Assembly.
Kalau selama ini kita mungkin lebih sering membuat program menggunakan PHP, C, Python, atau bahasa pemrograman tingkat tinggi lainnya, Assembly menawarkan pengalaman yang sedikit berbeda. Kita bisa melihat dengan lebih jelas bagaimana instruksi diberikan kepada prosesor melalui register, memori, dan instruksi yang sangat sederhana.
Assembly memang terlihat lebih rumit dibandingkan bahasa pemrograman modern. Namun, justru karena instruksinya sederhana, belajar Assembly bisa menjadi cara yang menarik untuk memahami apa yang sebenarnya terjadi di balik sebuah program.
Pada tutorial ini kita akan menggunakan:
- Linux 64-bit
- Prosesor x86-64
- NASM sebagai assembler
- GDB sebagai debugger
Contoh yang digunakan adalah Assembly dengan sintaks NASM. Jadi, jika Sobat menggunakan arsitektur atau sistem operasi berbeda, beberapa bagian kodenya bisa berbeda.
Apa Itu Assembly?
Assembly adalah bahasa pemrograman tingkat rendah yang instruksinya sangat dekat dengan instruksi yang dimengerti oleh prosesor.
Sederhananya, ketika kita menulis:
mov rax, 1 kita sedang memberikan instruksi kepada prosesor untuk memasukkan nilai 1 ke dalam register rax.
Berbeda dengan bahasa seperti PHP yang mempunyai banyak fungsi dan struktur tingkat tinggi, Assembly bekerja dengan instruksi-instruksi kecil seperti mov, add, sub, cmp, jmp, dan sebagainya.
Karena itu, Assembly sangat bergantung pada jenis prosesor yang digunakan. Assembly untuk x86-64 tidak sama dengan Assembly ARM, dan kode untuk Linux juga tidak selalu sama dengan kode untuk Windows.
Mengenal Register
Salah satu hal penting ketika mulai mengenal Assembly adalah register.
Register bisa dibayangkan sebagai tempat penyimpanan kecil yang berada sangat dekat dengan prosesor dan digunakan untuk menyimpan data yang sedang diproses.
Pada contoh x86-64, kita akan menemukan register seperti:
rax— sering digunakan untuk nilai hasil operasi atau menentukan nomor system call.rdi— digunakan sebagai argumen pertama.rsi— digunakan sebagai argumen kedua.rdx— digunakan sebagai argumen ketiga.
Nama dan fungsi register sebenarnya cukup luas. Untuk tahap awal, kita tidak perlu menghafal semuanya. Kita bisa mengenalinya sambil melihat contoh program.
Membuat Program Hello World
Sekarang kita coba membuat program sederhana yang menampilkan tulisan Hello, World!.
Buat sebuah file, misalnya:
hello.asm Kemudian isi dengan kode berikut:
section .data
pesan db 'Hello, World!', 10
panjang equ $ - pesan
section .text
global _start
_start:
; Menampilkan teks ke layar
mov rax, 1
mov rdi, 1
mov rsi, pesan
mov rdx, panjang
syscall
; Mengakhiri program
mov rax, 60
mov rdi, 0
syscall Sobat mungkin langsung melihat bahwa kode tersebut cukup pendek, tetapi setiap baris mempunyai peran tertentu.
Mengenal section .data
Bagian berikut:
section .data
pesan db 'Hello, World!', 10
panjang equ $ - pesan digunakan untuk menyimpan data yang sudah diketahui ketika program dibuat.
Pada contoh tersebut kita membuat sebuah data bernama pesan:
pesan db 'Hello, World!', 10 db merupakan singkatan dari define byte. Kita bisa menggunakannya untuk menyimpan data dalam bentuk byte.
Angka 10 di bagian akhir merupakan karakter newline atau pindah baris.
Kemudian:
panjang equ $ - pesan digunakan untuk menghitung panjang data pesan secara otomatis. Tanda $ pada konteks ini menunjukkan posisi saat ini sehingga kita tidak perlu menghitung sendiri berapa banyak byte yang digunakan oleh teks tersebut.
Mengenal section .text
Berikutnya ada:
section .text Bagian .text digunakan untuk menyimpan instruksi program yang nantinya dijalankan oleh prosesor.
Kemudian kita menentukan titik awal program:
global _start dan:
_start: Label _start menjadi titik awal eksekusi program pada contoh ini.
Bagaimana Program Menampilkan Teks?
Bagian menariknya ada di sini:
mov rax, 1
mov rdi, 1
mov rsi, pesan
mov rdx, panjang
syscall Di Linux x86-64, nomor 1 pada rax digunakan untuk write.
Kita kemudian mengisi beberapa register yang menjadi parameter:
rax = 1→ system call yang digunakan adalahwrite.rdi = 1→ tujuan output adalahstdout.rsi = pesan→ alamat data yang akan ditulis.rdx = panjang→ jumlah byte yang akan ditulis.
Setelah semuanya siap, instruksi:
syscall digunakan untuk meminta kernel Linux menjalankan system call tersebut.
Jadi, Assembly pada contoh ini tidak langsung berbicara kepada layar. Program meminta bantuan kernel Linux melalui system call write.
Mengakhiri Program
Setelah tulisan ditampilkan, program perlu dihentikan.
mov rax, 60
mov rdi, 0
syscall Di Linux x86-64, nomor 60 adalah system call exit.
Sementara rdi = 0 berarti program keluar dengan status 0, yang biasanya digunakan untuk menunjukkan bahwa program selesai tanpa error.
Memasang NASM
Jika NASM belum tersedia di Linux, pada Ubuntu atau Debian kita bisa memasangnya dengan:
sudo apt update
sudo apt install nasm Setelah itu periksa versinya:
nasm -v Jika NASM sudah terpasang, kita bisa mulai merakit file Assembly tadi.
Merakit Program Assembly
Assembly perlu diubah terlebih dahulu menjadi object file menggunakan NASM.
nasm -f elf64 hello.asm -o hello.o Parameter -f elf64 memberitahu NASM bahwa kita ingin membuat object file dengan format ELF 64-bit yang digunakan Linux x86-64.
Sekarang kita mempunyai:
hello.o Object file tersebut kemudian perlu di-link menjadi executable.
ld hello.o -o hello Setelah proses tersebut selesai, kita bisa menjalankan program:
./hello Hasilnya:
Hello, World! Mengapa Tidak Langsung Menjalankan File .asm?
File .asm masih berupa kode sumber yang berisi instruksi dalam bentuk teks yang bisa dibaca manusia.
Prosesnya secara sederhana seperti ini:
hello.asm
↓
NASM
↓
hello.o
↓
ld
↓
hello
↓
prosesor menjalankan program NASM bertugas menerjemahkan instruksi Assembly menjadi kode mesin di dalam object file. Setelah itu linker seperti ld menyusun object file tersebut menjadi executable yang dapat dijalankan Linux.
Mulai Mengenal Debugging
Nah, setelah program berhasil dibuat, bagian yang tidak kalah menarik adalah melihat apa yang sebenarnya terjadi ketika program berjalan.
Untuk itu kita bisa menggunakan GDB.
GDB memungkinkan kita menghentikan program pada titik tertentu, melihat isi register, melihat instruksi yang sedang dijalankan, dan menjalankan program sedikit demi sedikit.
Pada Assembly, kemampuan seperti ini cukup menarik karena kita bisa melihat langsung perubahan register ketika sebuah instruksi dijalankan.
Memasang GDB
Pada Ubuntu atau Debian:
sudo apt install gdb Kemudian periksa:
gdb --version Membuat Object File dengan Informasi Debug
Agar GDB bisa memberikan informasi yang lebih mudah kita ikuti, kita bisa menambahkan informasi debug ketika melakukan assembly.
nasm -f elf64 -g -F dwarf hello.asm -o hello.o Kemudian lakukan linking:
ld hello.o -o hello Dengan informasi debug tersebut, GDB dapat lebih mudah menghubungkan instruksi mesin dengan lokasi pada source Assembly.
Menjalankan Program dengan GDB
Kita bisa membuka program dengan:
gdb ./hello Setelah masuk ke GDB, kita bisa melihat kode Assembly dengan:
layout asm Jika tampilan terminal Sobat tidak mendukung mode tersebut dengan nyaman, kita juga bisa menggunakan perintah biasa:
disassemble _start GDB kemudian akan menampilkan instruksi yang terdapat pada fungsi atau label _start.
Membuat Breakpoint
Salah satu fitur utama debugger adalah breakpoint.
Kita bisa meminta GDB berhenti di _start:
break _start Kemudian jalankan program:
run Program akan berhenti ketika mencapai breakpoint tersebut.
Di sinilah kita bisa mulai melihat isi register sebelum instruksi dijalankan.
Melihat Isi Register
Untuk melihat register:
info registers Atau jika hanya ingin melihat register tertentu:
info registers rax rdi rsi rdx Pada awal program, nilainya mungkin belum seperti yang kita inginkan. Setelah instruksi mov dijalankan, kita bisa melihat nilainya berubah.
Menjalankan Instruksi Satu per Satu
Bagian ini yang menurut saya cukup menyenangkan ketika belajar Assembly.
GDB menyediakan perintah:
stepi atau bentuk singkatnya:
si Perintah tersebut menjalankan satu instruksi mesin kemudian berhenti lagi.
Misalnya kita mempunyai:
mov rax, 1
mov rdi, 1
mov rsi, pesan
mov rdx, panjang
syscall Kita bisa menjalankan si berulang kali sambil melihat register.
Misalnya setelah:
mov rax, 1 kita periksa:
info registers rax dan akan terlihat bahwa rax sekarang berisi nilai 1.
Kemudian jalankan lagi:
si Setelah mov rdi, 1, kita bisa melihat:
info registers rdi Nilainya menjadi 1.
Dengan cara seperti ini, kita bisa mengikuti program sedikit demi sedikit tanpa harus menebak apa yang terjadi.
Melihat Isi Memori
Karena rsi menyimpan alamat data pesan, kita juga bisa melihat isi memori yang ditunjuk oleh register tersebut.
Contohnya:
x/s $rsi Perintah x digunakan untuk memeriksa isi memori, sedangkan /s meminta GDB menampilkannya sebagai string.
Ini cukup membantu ketika kita sedang mencari tahu apakah alamat dan data yang digunakan program sudah benar.
Melihat Instruksi Mesin
Kita juga bisa meminta GDB menampilkan instruksi yang sedang digunakan:
disassemble _start Atau melihat instruksi pada posisi program saat ini:
x/i $rip rip adalah instruction pointer pada x86-64, yaitu register yang menunjukkan lokasi instruksi yang sedang atau akan dieksekusi.
Contoh Alur Debugging Sederhana
Kalau ingin mencoba dari awal, kira-kira alurnya seperti ini.
nasm -f elf64 -g -F dwarf hello.asm -o hello.o
ld hello.o -o hello
gdb ./hello Di dalam GDB:
break _start
run
info registers
si
info registers rax
si
info registers rdi
si
info registers rsi
si
info registers rdx
si Kita bisa mengulanginya sambil memperhatikan perubahan register.
Setelah selesai, keluar dari GDB dengan:
quit Bagaimana Jika Program Error?
Di sinilah debugger menjadi sangat berguna.
Misalnya program tidak menampilkan teks, keluar terlalu cepat, atau bahkan mengalami segmentation fault. Daripada hanya melihat hasil akhirnya, kita bisa menghentikan program dan melihat keadaan internalnya.
Beberapa hal yang biasanya menarik untuk diperiksa:
- Apakah
raxberisi nomor system call yang benar? - Apakah
rdiberisi argumen yang benar? - Apakah
rsimenunjuk ke alamat data yang benar? - Apakah
rdxberisi panjang data yang benar? - Apakah
ripsedang menunjuk ke instruksi yang kita harapkan?
Dengan melihat nilai tersebut secara langsung, sumber masalah biasanya menjadi lebih mudah dicari.
Assembly Tidak Harus Langsung Dihafalkan
Saat pertama kali melihat Assembly, banyaknya register dan instruksi mungkin terlihat cukup membingungkan.
Menurut saya, tidak perlu terburu-buru menghafal semuanya.
Kita bisa mulai dari beberapa instruksi sederhana:
mov
add
sub
cmp
jmp
call
ret
push
pop
syscall Kemudian sambil mencoba program kecil, kita akan mulai terbiasa melihat bagaimana instruksi tersebut bekerja.
Yang menarik dari Assembly justru kita bisa melihat hubungan antara instruksi, register, memori, dan prosesor secara lebih nyata.
Penutup
Jadi, Sobat, Assembly memang termasuk bahasa yang cukup dekat dengan perangkat keras. Karena itu pula, cara menulis programnya terasa berbeda dari bahasa pemrograman yang biasa kita gunakan.
Pada contoh sederhana tadi, kita sudah melihat beberapa konsep penting:
.datauntuk menyimpan data..textuntuk instruksi program.- Register seperti
rax,rdi,rsi, danrdx. syscallsebagai pintu masuk program untuk meminta layanan kernel Linux.- NASM untuk merakit source Assembly menjadi object file.
lduntuk melakukan linking menjadi executable.- GDB untuk melihat dan men-debug program secara langsung.
siuntuk menjalankan instruksi satu per satu.info registersuntuk melihat keadaan register.
Kalau sudah terbiasa dengan konsep dasar tersebut, langkah berikutnya bisa mulai mencoba operasi sederhana seperti penjumlahan, percabangan, perulangan, membaca input dari keyboard, hingga membuat program yang berinteraksi lebih banyak dengan kernel Linux.
Yang penting, nikmati prosesnya. Assembly memang membawa kita sedikit lebih dekat untuk melihat apa yang sebenarnya dilakukan komputer ketika sebuah program dijalankan.