Menyeberang ke dunia RISC: assembly ARM64 (aarch64) dengan register x0-x30 dan w0-w30, instruksi ldr/str load-store, add/sub dengan extended register, conditional branch b.eq dan branch on zero cbz, bl/ret untuk fungsi, calling convention AAPCS64, serta perbandingan langkah demi langkah melawan x86-64.

Sejauh ini seluruh series memakai x86-64. Episode 15 menyeberang ke arsitektur yang menguasai dunia mobile dan kini merambah laptop (Apple Silicon) dan server (AWS Graviton): ARM64 (aarch64). Jangan khawatir — 80% konsep yang kalian pelajari di episode 2–11 berlaku identik: register, stack, calling convention, endianness. Yang berubah adalah dialek dan filosofi instruksinya.
Mengapa penting? Karena di dunia nyata, kalian tidak selalu punya pilihan arsitektur. ARM64 adalah default di hampir semua smartphone, banyak VPS hemat, dan kini laptop. Menulis assembly ARM64 berarti kalian memahami ISA kedua paling penting di dunia.
| Aspek | x86-64 (CISC) | ARM64 (RISC) |
|---|---|---|
| Panjang instruksi | 1–15 byte | Fixed 32-bit |
| Akses memori | Bisa di dalam instruksi (add rax, [rbx]) | Load-store: hanya ldr/str yang menyentuh memori |
| Jumlah register umum | 16 | 31 (x0–x30) |
| Shift/operasi | Instruksi terpisah | Bisa embedded di operand |
| Conditional | Jump berdasar flag saja | Branch (dengan flags) + beberapa kondisi |
Konsekuensi terpenting: di ARM64, kalian harus memuat data ke register dulu, baru mengolahnya. Operasi add [mem], reg seperti di x86 tidak ada.
x0–x30 — register 64-bit.w0–w30 — akses 32-bit bawah dari register yang sama.sp — stack pointer (tidak boleh untuk operasi umum).x30 — link register (LR): tempat alamat kembali hasil bl.xzr — register nol; wzr versi 32-bit (menulis ke sini = buang hasil).Menulis ke wN menghapus 32 bit atas xN (tidak seperti x86 yang hanya menyentuh sebagian untuk 16/8-bit).
ldr x0, [x1] ; x0 = *(uint64_t*)x1
ldr w0, [x1] ; x0 = (uint64_t)*(uint32_t*)x1 (zero-extend)
ldrb w0, [x1] ; baca 1 byte
str x0, [x1] ; *(uint64_t*)x1 = x0
strb w0, [x1] ; tulis 1 byteOffset dan pre/post-index sangat umum:
ldr x0, [x1, #16] ; *(x1 + 16)
ldr x0, [x1, x2, lsl #3] ; *(x1 + x2*8) — elemen array 64-bit
str x0, [sp, #-16]! ; pre-index: sp -= 16; *(sp) = x0
ldr x0, [sp], #16 ; post-index: x0 = *(sp); sp += 16add/sub adalah pola umum RISC: tiga register (dest, src1, src2), dan source kedua bisa di-shift gratis di dalam instruksi:
add x0, x1, x2 ; x0 = x1 + x2
add x0, x1, #42 ; x0 = x1 + 42
sub x0, x1, #16
add x0, x1, x2, lsl #3 ; x0 = x1 + (x2 << 3)
sub x0, x1, x2, lsr #2 ; x0 = x1 - (x2 >> 2)Trik ini menggantikan lea x86 — perkalian array dengan add+shift dalam satu instruksi.
Branch ARM64 bekerja dengan dua cara:
cmp): b.eq, b.ne, b.lt, b.gt, dst.cbz (compare-and-branch-if-zero), cbnz (nonzero), tbz/tbnz (bit test).cmp x0, #0
b.eq done ; if (x0 == 0) goto done
cbnz x0, loop ; if (x0 != 0) goto loop
cbz x0, exit ; if (x0 == 0) goto exitNote
Perhatikan sintaksnya: b.eq dengan titik, bukan je seperti x86. Untuk branch tak bersyarat, cukup b label (setara jmp). Dan cmp di ARM64 selalu cmp xN, #nilai — tidak ada keluarga jump terpisah untuk unsigned (ja/jb) seperti x86; untuk perbandingan unsigned, kalian harus memakai cmp + b.hi/b.lo.
Calling convention ARM64 (AAPCS64) mirip System V x86-64:
x0–x7 (8 argumen integer) lalu stack.x0 (dan x1 untuk 128-bit).bl func — branch-and-link: simpan alamat kembali ke x30, lompat ke func.ret — lompat ke alamat di x30.x19–x28 (+ sp, d8–d15).x0–x18, x30).Karena x30 menyimpan alamat kembali, fungsi yang memanggil fungsi lain wajib menyimpan x30 ke stack (mirip push alamat kembali di x86). Pola prologue khas:
my_func:
stp x29, x30, [sp, #-16]! ; simpan frame pointer + link register
mov x29, sp ; x29 = frame pointer (FP)
; ... badan fungsi (bisa memanggil fungsi lain) ...
ldp x29, x30, [sp], #16 ; pulihkan
retx29 adalah frame pointer (FP) ARM64 — setara rbp. stp/ldp (store/load pair) memuat-menyimpan dua register sekaligus dan wajib aligned 16-byte — alat kerja utama stack ARM64.
.section .data
msg:
.ascii "Hello ARM64!\n"
len = . - msg
.section .text
.global _start
_start:
mov x8, #64 ; syscall write (tabel ARM64)
mov x0, #1 ; fd = stdout
ldr x1, =msg ; alamat buffer
ldr x2, =len ; panjang
svc #0
mov x8, #93 ; syscall exit (ARM64)
mov x0, #0
svc #0aarch64-linux-gnu-as hello_arm64.s -o hello_arm64.o
aarch64-linux-gnu-ld hello_arm64.o -o hello_arm64
qemu-aarch64 ./hello_arm64Important
Nomor syscall ARM64 berbeda total dari x86-64. Di x86-64, write=1 dan exit=60; di ARM64, write=64 dan exit=93. Dan syscall dijalankan dengan svc #0, bukan syscall, dengan nomor di x8. Jangan pernah menganggap tabel syscall portabel antar arsitektur.
Mari bandingkan fungsi add_five (episode 9) antara dua arsitektur:
add_five:
mov rax, rdi
add rax, rsi
add rax, rdx
add rax, rcx
add rax, r8
retadd_five:
add x0, x0, x1
add x0, x0, x2
add x0, x0, x3
add x0, x0, x4
retKedua versi menghasilkan fungsi yang sama persis dari perspektif pemanggil C — bukti bahwa ABI dan konsep assembly bersifat universal, hanya dialeknya yang berbeda.
wN menghapus bit atas xN — perilaku mirip x86 32-bit write.sp di operasi umum — tidak valid untuk kebanyakan instruksi.x30 di fungsi non-leaf — ret melompat ke sampah.lsl/lsr #n hanya sampai 63 di ARM64 (bukan 255 seperti x86).ldr xN, =konstanta (assembler yang menyusunnya).x8 + nomor ARM64) — lihat Callout di atas.Pada episode 15 ini, kalian telah menaklukkan arsitektur RISC paling berpengaruh di dunia.
Inti yang harus dibawa pulang:
ldr/str yang menyentuh memori; instruksi fixed 32-bit.x0–x30 (w0–w30 = 32-bit), sp, xzr, link register x30.add/sub dengan shifted operands menggantikan lea; b.eq/cbz menggantikan je/jrcxz.x0–x7, return x0, callee-saved x19–x28; bl simpan ke x30.svc #0 dengan nomor di x8 — tabelnya berbeda dari x86-64.Di episode 16 selanjutnya, kalian memakai semua yang sudah dipelajari untuk melihat ke dalam program: debugging & reverse engineering dengan gdb (breakpoint, stepi, x/), objdump -d, readelf, strace, hingga pengenalan Ghidra/IDA dan radare2/rizin. Saatnya membalik posisi: dari penulis menjadi pembaca kode mesin.