cv.cm

Pemrograman · Bahasa pemrograman

Tutorial bahasa assembly untuk pemula (x86-64, NASM)

8 menit baca

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.

Buka Konversi basis

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
    syscall
nasm -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.

RegisterPeran umumBagian yang lebih kecil
raxNilai kembalian, nomor system calleax (32-bit), ax (16), al (8)
rdi, rsi, rdxTiga argumen pertama fungsi atau system calledi, esi, edx
rcx, r8, r9Argumen keempat sampai keenam fungsiecx, r8d, r9d
rbx, rbp, r12–r15Nilai yang harus bertahan melewati pemanggilan fungsi (callee-saved)ebx, r12d
rspStack pointer: alamat puncak stackJangan dipakai untuk data
ripInstruction 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.

InstruksiArti
mov rax, rbxSalin rbx ke rax
add, sub, imul, inc, decAritmetika integer
cmp rax, 10Bandingkan (menghitung rax - 10 dan hanya mengatur flag)
je, jne, jl, jg, jle, jgeLompat jika sama, tidak sama, kurang, lebih besar... (bertanda)
jmp labelLompat tanpa syarat
push, popTaruh nilai ke stack, ambil kembali
call, retPanggil 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
    syscall
nasm -f elf64 sum.asm -o sum.o && ld sum.o -o sum
./sum; echo $?
# 55

Label 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
    syscall

mov 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 progbits
nasm -f elf64 square.asm -o square.o
gcc square.o -o square
./square
# 7 squared is 49

Melakukan 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 descriptor

Program 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 5050

Sintaks 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.

AspekIntel (NASM)AT&T (GNU as)
Urutan operandmov rax, 1 (tujuan lebih dulu)movq $1, %rax (sumber lebih dulu)
Register dan konstantarax, 5%rax, $5
Operand memori[rbx + rcx*8 + 16]16(%rbx,%rcx,8)
Ukuran operandmov qword [rbx], 0movq $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     #0

Langkah 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.

Semua tutorial pemrograman