Pengenalan Kode Assembly dan Cara Membuat serta Debug Program Assembly

Daftar Isi

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 adalah write.
  • rdi = 1 → tujuan output adalah stdout.
  • 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 rax berisi nomor system call yang benar?
  • Apakah rdi berisi argumen yang benar?
  • Apakah rsi menunjuk ke alamat data yang benar?
  • Apakah rdx berisi panjang data yang benar?
  • Apakah rip sedang 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:

  • .data untuk menyimpan data.
  • .text untuk instruksi program.
  • Register seperti rax, rdi, rsi, dan rdx.
  • syscall sebagai pintu masuk program untuk meminta layanan kernel Linux.
  • NASM untuk merakit source Assembly menjadi object file.
  • ld untuk melakukan linking menjadi executable.
  • GDB untuk melihat dan men-debug program secara langsung.
  • si untuk menjalankan instruksi satu per satu.
  • info registers untuk 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.

Tinggalkan Balasan

Informasi kontak Anda tidak akan dipublikasikan. Kolom yang wajib diisi ditandai *. Notifikasi akan dikirim ke telegram untuk mempercepat tanggapan komentar