Pemrograman · Bahasa pemrograman
Tutorial bahasa assembly untuk pemula (x86-64, NASM)
Jawaban singkat
Bahasa assembly adalah bentuk instruksi mesin prosesor yang dapat dibaca manusia: setiap baris biasanya menjadi satu instruksi seperti mov rax, 1 atau add rax, rbx. Anda bekerja langsung dengan register, alamat memori, dan stack, tanpa variabel, tipe, atau perulangan selain yang Anda bangun dari jump. Tutorial ini memakai x86-64 di Linux dengan assembler NASM: tulis file .asm, assemble dengan nasm -f elf64, dan link dengan ld.
Apa itu assembly dan siapa yang memakainya
Setiap keluarga prosesor punya set instruksi sendiri dan karena itu bahasa assembly sendiri. x86-64 (juga disebut AMD64) menjalankan sebagian besar PC desktop dan server; ARM (AArch64) menjalankan sebagian besar ponsel, Mac Apple silicon, dan banyak server. Assembler menerjemahkan assembly menjadi kode mesin, dan linker menggabungkan hasilnya menjadi executable.
Saat ini hanya sedikit program yang ditulis sepenuhnya dalam assembly. Assembly dipakai di kernel sistem operasi dan bootloader, rutin yang kritis performa (kriptografi, codec, library matematika), firmware embedded, pengembangan compiler, riset keamanan, dan reverse engineering. Paling sering, developer membaca assembly untuk memahami apa yang dihasilkan compiler atau untuk men-debug crash.
Memasang NASM dan menjalankan Hello, world
Di Debian atau Ubuntu, pasang tool dengan sudo apt install nasm binutils (binutils menyediakan linker ld). Program ini memanggil kernel Linux langsung lewat system call, sehingga tidak memerlukan library C.
; hello.asm - x86-64 Linux, NASM syntax
section .data
msg db "Hello, world!", 10 ; 10 = newline
msglen equ $ - msg ; length computed by the assembler
section .text
global _start
_start:
mov rax, 1 ; system call number 1 = write
mov rdi, 1 ; file descriptor 1 = stdout
mov rsi, msg ; address of the bytes
mov rdx, msglen ; number of bytes
syscall
mov rax, 60 ; system call number 60 = exit
xor rdi, rdi ; exit code 0
syscallnasm -f elf64 hello.asm -o hello.o
ld hello.o -o hello
./hello
# Hello, world!section .data berisi data yang sudah diinisialisasi, section .text berisi kode, dan _start adalah entry point yang dicari ld. Komentar diawali dengan ;. Tanpa system call exit di akhir, prosesor akan terus mengeksekusi byte apa pun yang mengikutinya dan program akan crash.
Register dan ukuran data
Register adalah lokasi penyimpanan kecil yang sangat cepat di dalam CPU. x86-64 punya 16 register 64-bit serbaguna. Masing-masing juga dapat dipakai dalam bagian yang lebih kecil.
| Register | Peran umum | Bagian yang lebih kecil |
|---|---|---|
rax | Nilai kembalian, nomor system call | eax (32-bit), ax (16), al (8) |
rdi, rsi, rdx | Tiga argumen pertama fungsi atau system call | edi, esi, edx |
rcx, r8, r9 | Argumen keempat sampai keenam fungsi | ecx, r8d, r9d |
rbx, rbp, r12–r15 | Nilai yang harus bertahan melewati pemanggilan fungsi (callee-saved) | ebx, r12d |
rsp | Stack pointer: alamat puncak stack | Jangan dipakai untuk data |
rip | Instruction pointer (hanya berubah oleh jump, call, return) | Tidak dapat ditulis langsung |
Data di memori dideklarasikan dengan db (byte, 8 bit), dw (word, 16), dd (doubleword, 32), dan dq (quadword, 64). Buffer yang belum diinisialisasi ditaruh di section .bss dengan resb, resq, dan seterusnya. Menulis ke register 32-bit seperti eax juga mengosongkan 32 bit atas rax, itulah sebabnya xor eax, eax adalah cara umum untuk mengisi rax dengan nol.
Instruksi dasar dan alur kontrol
Sebagian besar instruksi menerima tujuan lebih dulu dan sumber kedua: add rax, rbx berarti rax = rax + rbx. Tidak ada statement if atau while; Anda membandingkan nilai dengan cmp, yang mengatur flag, lalu memakai conditional jump.
| Instruksi | Arti |
|---|---|
mov rax, rbx | Salin rbx ke rax |
add, sub, imul, inc, dec | Aritmetika integer |
cmp rax, 10 | Bandingkan (menghitung rax - 10 dan hanya mengatur flag) |
je, jne, jl, jg, jle, jge | Lompat jika sama, tidak sama, kurang, lebih besar... (bertanda) |
jmp label | Lompat tanpa syarat |
push, pop | Taruh nilai ke stack, ambil kembali |
call, ret | Panggil fungsi, kembali darinya |
; sum.asm - add 1..10, return the result as the exit code
section .text
global _start
_start:
xor rax, rax ; sum = 0
mov rcx, 1 ; i = 1
.loop:
cmp rcx, 10
jg .done ; if i > 10, leave the loop
add rax, rcx ; sum += i
inc rcx ; i++
jmp .loop
.done:
mov rdi, rax ; exit code = sum
mov rax, 60 ; exit
syscallnasm -f elf64 sum.asm -o sum.o && ld sum.o -o sum
./sum; echo $?
# 55Label yang diawali titik, seperti .loop, bersifat lokal terhadap label normal sebelumnya. jl dan jg memperlakukan nilai sebagai bertanda; jb dan ja adalah versi tak bertanda.
Pengalamatan memori dan array
Tanda kurung siku berarti "memori pada alamat ini". Sebuah alamat dapat menggabungkan register basis, register indeks yang dikalikan 1, 2, 4, atau 8, dan konstanta: [base + index*scale + offset]. Ini membuat akses array cukup dengan satu instruksi.
section .data
nums dq 3, 1, 4, 1, 5 ; five 64-bit integers
count equ ($ - nums) / 8
section .text
global _start
_start:
xor rax, rax ; total = 0
xor rcx, rcx ; index = 0
.next:
add rax, [nums + rcx*8] ; total += nums[index]
inc rcx
cmp rcx, count
jl .next
mov rdi, rax ; exit code 14
mov rax, 60
syscallmov rax, nums memuat alamat, sedangkan mov rax, [nums] memuat nilai yang tersimpan di sana. lea rsi, [rbx + 8] menghitung alamat tanpa membaca memori. Bila ukuran tidak jelas dari register, nyatakan: mov qword [rbx], 0.
Fungsi dan calling convention
Di Linux, fungsi mengikuti System V AMD64 ABI. Argumen integer ditaruh di rdi, rsi, rdx, rcx, r8, r9; nilai kembalian ada di rax. Fungsi harus menjaga rbx, rbp, r12–r15, dan rsp, dan rsp harus kelipatan 16 tepat sebelum call. Mengikuti aturan ini memungkinkan assembly memanggil fungsi C seperti printf, dan memungkinkan C memanggil assembly.
; square.asm - define a function and call printf from the C library
extern printf
global main
section .data
fmt db "%ld squared is %ld", 10, 0
section .text
square: ; long square(long x)
mov rax, rdi
imul rax, rdi
ret
main:
push rbx ; save rbx; also aligns rsp to 16
mov rbx, 7
mov rdi, rbx
call square ; rax = 49
lea rdi, [rel fmt] ; argument 1: format string
mov rsi, rbx ; argument 2
mov rdx, rax ; argument 3
xor eax, eax ; varargs: no vector registers used
call printf wrt ..plt
xor eax, eax ; return 0 from main
pop rbx
ret
section .note.GNU-stack noalloc noexec nowrite progbitsnasm -f elf64 square.asm -o square.o
gcc square.o -o square
./square
# 7 squared is 49Melakukan link dengan gcc menambahkan runtime C, yang memanggil main. [rel fmt] dan wrt ..plt menghasilkan kode position-independent, yang dipakai secara default oleh distribusi Linux modern. Baris terakhir menandai stack sebagai tidak dapat dieksekusi dan menghindari peringatan linker.
Penanganan error
Assembly tidak punya exception. System call Linux yang gagal mengembalikan nomor error negatif di rax (misalnya -2, ENOENT, "no such file"), jadi Anda menguji tandanya setelah setiap pemanggilan. Fungsi library C melaporkan error lewat nilai kembalian dan errno. Kesalahan perangkat keras, seperti membaca alamat yang tidak valid atau pembagian dengan nol, menghentikan program dengan sinyal (segmentation fault atau floating point exception).
mov rax, 2 ; system call 2 = open
lea rdi, [rel path] ; path is a zero-terminated string
xor esi, esi ; flags: O_RDONLY
syscall
test rax, rax
js .open_failed ; negative result = -errno
mov r12, rax ; success: rax is the file descriptorProgram lengkap kecil: mencetak angka dalam desimal
Mencetak angka berarti mengubahnya menjadi digit sendiri. Program ini menghitung 1 + 2 + ... + 100, membagi berulang kali dengan 10 untuk mendapatkan digit dari kanan ke kiri, lalu menuliskannya dengan satu system call.
section .bss
buf resb 32
section .text
global _start
_start:
xor rax, rax
mov rcx, 100
.sum:
add rax, rcx
loop .sum ; rcx -= 1, repeat while rcx != 0
lea rsi, [buf + 31]
mov byte [rsi], 10 ; newline at the end of the buffer
mov rbx, 10
.digit:
xor rdx, rdx ; div uses rdx:rax as the dividend
div rbx ; rax = quotient, rdx = remainder
add dl, '0' ; remainder to ASCII digit
dec rsi
mov [rsi], dl
test rax, rax
jnz .digit
mov rdx, buf + 32
sub rdx, rsi ; length = end - start
mov rax, 1 ; write(1, rsi, rdx)
mov rdi, 1
syscall
mov rax, 60 ; exit(0)
xor rdi, rdi
syscall
; prints 5050Sintaks AT&T vs Intel, dan ARM
Instruksi x86-64 yang sama dapat ditulis dalam dua sintaks. NASM dan sebagian besar dokumentasi Intel dan Microsoft memakai sintaks Intel. GNU assembler (as), output GCC, dan objdump memakai sintaks AT&T secara default; objdump -d -M intel dan gcc -S -masm=intel beralih ke sintaks Intel.
| Aspek | Intel (NASM) | AT&T (GNU as) |
|---|---|---|
| Urutan operand | mov rax, 1 (tujuan lebih dulu) | movq $1, %rax (sumber lebih dulu) |
| Register dan konstanta | rax, 5 | %rax, $5 |
| Operand memori | [rbx + rcx*8 + 16] | 16(%rbx,%rcx,8) |
| Ukuran operand | mov qword [rbx], 0 | movq $0, (%rbx) (sufiks b, w, l, q) |
Prosesor ARM memakai set instruksi yang berbeda. AArch64 punya 31 register 64-bit serbaguna (x0–x30), encoding instruksi 32-bit tetap, dan desain load/store: aritmetika hanya bekerja pada register, dan memori diakses dengan ldr dan str. Di Linux, nomor system call ditaruh di x8 dan pemanggilan dilakukan dengan svc #0.
// AArch64 Linux, GNU as syntax: exit(0)
mov x0, #0 // exit status
mov x8, #93 // system call 93 = exit
svc #0Langkah selanjutnya
- Manual NASM untuk direktif, macro, dan format output.
- Intel 64 and IA-32 Architectures Software Developer's Manuals untuk perilaku persis setiap instruksi.
- Dokumen System V AMD64 ABI untuk calling convention dan tata letak stack.
- Kompilasi fungsi C kecil dengan
gcc -O2 -S -masm=intel(atau compiler explorer online) dan baca assembly yang dihasilkan.
FAQ
Apakah bahasa assembly masih layak dipelajari?
Ya, untuk memahami cara kerja komputer, men-debug crash, membaca output compiler, pekerjaan keamanan, dan pengembangan embedded. Menulis seluruh aplikasi dalam assembly jarang praktis saat ini.
Apakah assembly x86 sama dengan assembly ARM?
Tidak. Keduanya set instruksi yang berbeda dengan register, instruksi, dan encoding yang berbeda. Konsepnya (register, memori, jump, calling convention) dapat dibawa, tetapi kodenya tidak.
Apa perbedaan NASM dan GAS?
NASM adalah assembler mandiri yang memakai sintaks Intel. GAS, GNU assembler, dipakai oleh GCC dan secara default memakai sintaks AT&T, meski juga menerima sintaks Intel dengan .intel_syntax noprefix.
Apakah kode ini berjalan di Windows atau macOS?
Tidak tanpa perubahan. Instruksinya sama di sistem x86-64 mana pun, tetapi nomor system call, format executable, dan calling convention berbeda. Windows memakai calling convention yang berbeda, dan Mac Apple silicon memakai ARM, bukan x86-64.