Menghubungi kernel Linux langsung dari assembly: instruksi syscall dengan nomor syscall di rax, tabel di /usr/include/asm/unistd_64.h, dan cara write, read, exit, open, close bekerja. Di episode ini juga dibangun program "cat" mini murni tanpa libc, plus debugging syscall dengan strace.

Setelah episode 9 mengatur komunikasi antar-fungsi, episode 10 mengatur komunikasi antara program dan sistem operasi. Syscall adalah satu-satunya jembatan resmi menuju kernel: membaca file, menulis ke terminal, mengalokasikan memori, membuat proses. Memahami syscall di level assembly membuat kalian melihat apa yang sebenarnya dilakukan libc saat kalian memanggil printf atau read di C.
Mengapa ini penting? Karena di sinilah letak perbedaan paling kentara antara "program assembly" dan "program C": assembly murni bisa berjalan tanpa libc sama sekali (seperti Hello World kita di episode 3) — cukup syscall. Dan saat kalian memakai strace (episode 16), kalian akan melihat jejak syscall inilah yang terbaca.
Di Linux x86-64, instruksi syscall mentransfer kontrol dari user space ke kernel. Konvensi panggilannya bukan System V biasa, melainkan khusus:
| Register | Isi |
|---|---|
rax | nomor syscall |
rdi | argumen 1 |
rsi | argumen 2 |
rdx | argumen 3 |
r10 | argumen 4 (bukan rcx!) |
r8 | argumen 5 |
r9 | argumen 6 |
| return | rax (nilai negatif = errno negatif) |
Important
Perhatikan: argumen ke-4 syscall memakai r10, bukan rcx. Ini karena instruksi syscall sendiri secara internal menimpa rcx dan r11 (untuk menyimpan state). Saat memanggil mmap atau ptrace (yang butuh 4 argumen lebih), kesalahan memakai rcx adalah bug klasik yang hasilnya error ENOSYS atau EFAULT.
Nomor syscall Linux dapat dilihat di:
grep -E "write|exit|read|open" /usr/include/asm/unistd_64.hHasilnya (untuk kernel 64-bit):
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_exit 60Setiap arsitektur punya tabel sendiri: unistd_64.h untuk x86-64, unistd_32.h untuk i386, dan ARM64 memakai tabel yang berbeda lagi. Nomor ini stabil lintas versi kernel — salah satu janji stabilitas ABI Linux.
| Syscall | Nomor | Argumen | Fungsi |
|---|---|---|---|
read | 0 | rdi=fd, rsi=buf, rdx=count | baca dari fd |
write | 1 | rdi=fd, rsi=buf, rdx=count | tulis ke fd |
open | 2 | rdi=path, rsi=flags, rdx=mode | buka file |
close | 3 | rdi=fd | tutup fd |
exit | 60 | rdi=status | hentikan proses |
brk | 12 | rdi=addr | alokasi memori heap |
Mari gabungkan semuanya menjadi program yang membaca sebuah file dan mencetak isinya — versi mini cat, murni syscall tanpa libc:
section .data
path db "hello.txt", 0 ; nama file (null-terminated)
errmsg db "open failed", 0x0a
errlen equ $-errmsg
section .bss
buffer resb 4096
section .text
global _start
_start:
; open("hello.txt", O_RDONLY)
mov rax, 2
mov rdi, path
mov rsi, 0 ; O_RDONLY = 0
mov rdx, 0
syscall
test rax, rax
js open_failed ; rax negatif = error
mov r8, rax ; simpan fd di r8
read_loop:
; read(fd, buffer, 4096)
mov rax, 0
mov rdi, r8
mov rsi, buffer
mov rdx, 4096
syscall
test rax, rax
jz close_file ; 0 byte = EOF
js close_file
mov r9, rax ; simpan jumlah byte terbaca
; write(1, buffer, n)
mov rax, 1
mov rdi, 1
mov rsi, buffer
mov rdx, r9
syscall
jmp read_loop
close_file:
mov rax, 3 ; close(fd)
mov rdi, r8
syscall
jmp exit_ok
open_failed:
mov rax, 1
mov rdi, 1
mov rsi, errmsg
mov rdx, errlen
syscall
mov rax, 60
mov rdi, 1
syscall
exit_ok:
mov rax, 60
mov rdi, 0
syscallcd ~/asm-lab/src
echo "Halo dari file" > hello.txt
nasm -f elf64 catmini.asm -o catmini.o
ld catmini.o -o catmini
./catminiOutput: Halo dari file. Perhatikan detail penting:
open mengembalikan fd (deskriptor file) atau errno negatif — pola test rax, rax; js memeriksa tanda untuk mendeteksi error.read mengembalikan 0 (EOF). read bisa mengembalikan kurang dari buffer yang diminta — loop adalah praktik yang benar.r8 dan r9 kita pakai sebagai variabel persist selama loop karena bebas dipakai (caller-saved, dan kita memang "caller"-nya).Tip
Nilai negatif dari syscall adalah errno negatif. Contoh: open file yang tidak ada mengembalikan -2 (ENOENT). Coba jalankan ./catmini di direktori tanpa hello.txt — program kalian menampilkan pesan error buatan sendiri, persis yang dilakukan strace dan tools Unix.
Karena exit menerima status, program assembly bisa menjadi bagian pipeline Unix:
; exit(42)
mov rax, 60
mov rdi, 42
syscall./prog
echo $? # 42Di shell, exit code 0 = sukses, selain itu = error. Ini bahasa yang dipakai script, CI, dan cron untuk tahu program kalian bekerja — jadi biasakan men-set status yang bermakna.
strace adalah jendela ke syscall. Jalankan program Hello World episode 3:
strace -f ./helloOutput (dipotong) menunjukkan jejak sebenarnya:
execve("./hello", ["./hello"], ...) = 0
write(1, "Hello, World!\n", 14) = 14
exit(0) = ?Perhatikan: hanya dua syscall yang dilakukan — write dan exit. Tidak ada brk, tidak ada pemetaan library, tidak ada pembukaan file, karena program ini tanpa libc dan tanpa runtime. Bandingkan dengan strace ./ls | head -5 — kalian akan melihat puluhan syscall karena libc dan loader ikut bekerja. Perbedaan inilah "kebersihan" assembly murni.
r10 (bukan rcx) — lihat Callout di atas.open("hello.txt") tanpa 0 di akhir akan membaca melampaui string.open mengembalikan negatif; selalu cek sebelum memakai fd.write di x86-64 adalah 1, tapi di i386 adalah 4. Selalu cek unistd_64.h.Pada episode 10 ini, kalian telah menghubungkan assembly ke kernel Linux.
Inti yang harus dibawa pulang:
syscall dengan nomor di rax, argumen rdi/rsi/rdx/r10/r8/r9; return rax (negatif = errno)./usr/include/asm/unistd_64.h; write=1, read=0, exit=60.open/read/write/close dan loop baca-print.strace membuka tabir: assembly murni hanya menghasilkan sedikit syscall.Di episode 11 selanjutnya, kita bedah data structures di assembly: section .data dan .bss, direktif db/dw/dd/dq, array dengan times, string null-terminated, dan implementasi manual strlen/strcpy — membangun kembali apa yang kalian tahu dari C, tapi sekarang dengan kendali penuh atas memori. Pastikan flag dan syscall kalian sudah hangat, karena kita mulai menulis program yang mengolah data sungguhan.