Bab 6: Aristektur Intel X86/AMD64

Ini merupakan arsitektur yang paling populer, dipakai di PC saat ini dan sebagian tablet/ponsel. Arsitektur ini juga sangat rumit karena berkembang dan backward compatible dengan versi sebelumnya. Akan sangat sulit untuk menjelaskan keseluruhan arsitektur ini, anggap saja ini hanya perkenalan, banyak detail spesifik yang akan dibahas ketika membahas sistem operasi tertentu (Linux, Windows, macOS).

Dari sisi reverser, jika hanya ingin mereverse engineer aplikasi biasa (user mode application) ada banyak hal yang bisa dilewati. Jika ingin melakukan reverse engineering sampai level device driver atau kernel, maka ada banyak hal yang perlu dipelajari. Contohnya dalam hal managemen memori: di sisi kernel kita perlu memahami masalah paging, managemen memori, dsb, tapi di sisi aplikasi, ini tidak bisa diubah, hanya bisa dipakai.

Dokumentasi

Dokumentasi seluruh instruksi X86 dan AMD64 dapat dibaca di situs Intel: IntelĀ® 64 and IA-32 Architectures Software Developer Manuals. AMD memiliki sedikit perbedaan dalam instruksi tingkat lanjut, ini bisa dibaca di situs AMD Developer Guides, Manuals & ISA Documents.

Versi

Arsitektur ini berkembang dari 25 tahun yang lalu, dan setiap update sejak 8086 masih kompatibel dengan versi sebelumnya: dari mulai 8086 (mikroprosessor 16 bit), 80286 (masih 16 bit tapi mendukung virtual memory dan memory protection), 80386 (mulai versi 32 bit), sampai chip yang terbaru yang mendukung 64 bit. Sejarah lengkap perkembangan arsitektur x86 bisa dilihat di Wikipedia.

Karena dibuat kompatibel dengan sebelumnya, ilmu mengenai arsitektur lama sebagian besar masih terpakai di arsitektur baru. Misalnya pengetahuan mengenai register yang kompatibel dengan yang lama. Register RAX (64 bit), 32 bit rendahnya ada di register EAX (32 bit, muncul sejak 386), dan 16 bit rendahnya juga ada di register AX (16 bit, ada sejak 8086) dan 8 bit rendahnya ada di register AL (8 bit, ada sejak 8086).

Sebelum Pentium, setiap rilis chip dengan arsitektur x86 menggunakan versi berurut (8086, 80186, 80286, 80386, dan 80486), tapi kemudian Intel memilih menggunakan nama pentium untuk 586, dan seterusnya aristektur 686 menggunakan nama yang berbeda di sisi Intel dan AMD.

Intel berusaha membuat arsitektur baru yang tidak kompatibel dengan seri x86 yang dinamakan Itanium, tapi ini kurang sukses untuk end user walaupun sukses di Enterprise. Sementara itu AMD membuat instruction set AMD64 yang kompatibel dengan x86. Akhirnya Intel bekerjasama dengan AMD dan membuat chip yang kompatibel dengan chip AMD64. Kadang AMD64 ini disebut juga dengan x86_64.

Proses boot

Saat ini proses booting PC sudah cukup rumit dan bisa menjadi satu buku sendiri, jadi saya tidak akan membahas detail proses ini. Secara umum ada dua proses booting: yang lama memakai BIOS (Basic Input Output System) dan yang baru memakai UEFI (Universal Extensible Firmware Interface). Sebagai catatan arti Universal di sini adalah bisa berlaku untuk sistem selain x86.

Pada arsitektur yang memakai BIOS, setelah prosessor menjalankan BIOS, maka kendali ada di tangan BIOS. Semua BIOS menggunakan konvensi yang sama: meload sektor pertama floppy (boot sector) atau harddisk (master boot record/MBR) dan meload ke alamat 0000:7c00. Setelah isi diload ke memori, BIOS akan mengecek apakah dua byte terakhir adalah 055 diikuti 0xAA, jika iya, maka akan dilakukan jump ke 0000:7c00.

Dari titik tersebut, kode dalam boot sector/MBR bisa meload sektor-sektor berikutnya ke memori dan melakukan JUMP ke memori tersebut. Karena ukuran boot sector hanya 512 byte, maka isi boot sector ini biasanya sangat sederhana, hanya memanggil fungi BIOS untuk meload sektor lain dan menampilkan error jika gagal. Bagian yang diload oleh boot sector ini yang biasanya disebut sebagai boot loader.

Bootloader akan melakukan berbagai setup, misalnya pindah ke mode 32 bit atau 64 bit, lalu membaca filesystem dan mencari konfigurasi untuk booting. Setelah itu kernel bisa diload. Contoh bootloader adalah GRUB, LILO, dan NTLDR.

Dari penjelasan di atas bisa dibayangkan repotnya semua langkah ini hanya demi kompatibilitas dengan sistem lama: ada BIOS yang meload satu sektor saja dari disk, lalu satu sektor itu meload bootloader (banyak sektor), dan bootlaoder akan meload sistem operasi. Ini disederhanakan dalam UEFI.

Arsitektur yang memakai UEFI lebih mudah: Firmware UEFI bisa meload file UEFI langsung dari filesystem. File ini adalah executable khusus yang bisa berjalan tanpa sistem operasi. Aplikasi UEFI memanggil kode dalam Firmware UEFI untuk melakukan berbagai aksi misalnya meload sektor dari disk atau bahkan mengakses jaringan.

Selain aplikasi UEFI, kita juga bisa membuat Boot service driver yang merupakan driver agar UEFI bisa mengakses device khusus atau Runtime Driver yang akan tetap ada setelah sistem operasi berjalan.

Jadi di dalam UEFI: firmware akan langsung meload file bootloader, dan bootloader itu hanya perlu meload OS dengan opsi yang diset oleh pengguna.

Sistem operasi

Jumlah sistem operasi untuk Intel x86 ada sangat banyak, beberapa yang terkenal antara lain:

  • DOS
  • Windows
  • Linux dan segala turunannya (ChromeOS, Android, FirefosOS)
  • FreeBSD/NetBSD/OpenBSD
  • Solaris

Banyak yang dulu terkenal tapi sekarang sudah tidak banyak dipakai. misalnya:

  • OS/2 dan eComStation
  • Netware

Dan banyak yang lain yang kurang terkenal tapi masih aktif dikembangkan misalnya:

  • Plan 9
  • GNU/Hurd
  • Haiku
  • KolibriOS
  • ReactOS
  • HelenOS

Masing-masing sistem operasi biasanya memiliki:

  • Format file yang berbeda, walau ada juga format standar seperti ELF yang dipakai di beberapa sistem operasi
  • Memiliki cara yang berbeda untuk memanggil fungsi sistem operasi
  • Memiliki built in library yang berbeda (kecuali jika mengimplementasikan standar tertentu misalnya POSIX)
  • Melakukan managemen memori dengan cara yang berbeda (kecuali jika memang ingin kompatibel, seperti ReactOS yang ingin kompatibel dengan Windows)

Tools

Compiler

Jumlah compiler untuk arsitektur X86/AMD64 tidak terhitung jumlahnya. Hampir semua bahasa pemrograman ada compilernya. Beberapa compiler C yang populer adalah:

  • Clang (Selain C juga mendukung berbagai bahasa)
  • GCC (Selain C juga mendukung berbagai bahasa)
  • MSVC (dari Microsoft)

Disassembler

Disassembler yang populer adalah IDA Pro, tapi selain itu juga ada banyak yang lain, misalnya: radare, sourcerer (untuk 16 bit). Secara umum berbagai debugger juga memiliki fungsi disassembler.

Simulator

Arsitektur x86 bisa disimulasikan dengan emulator CPU Qemu, DOSBOX, dan juga menggunakan hypervisor.

Debugger

Beberapa debugger yang terkenal:

  • GDB: Linux, Windows dan berbagai sistem operasi lain
  • WinDBG: Windows
  • OllyDbg: Windows

Masih banyak juga debugger lain yang sudah lama tidak didukung misalnya SoftICE untuk Windows 9x, DEBUG.COM untuk DOS.

Arsitektur

X86 menggunakan arsitektur Von Neuman. Jumlah register, mode akses memori, dan berbagai fitur lain berkembang selama beberapa puluh tahun.

Register

Register arsitektur X86 berkembang dari sejak 8086, yang memiliki register 16 bit:

 AX : dipecah menjadi AH dan AL
 BX : dipecah menjadi BH dan BL
 CX : dipecah menjadi CH dan CL
 DX : dipecah menjadi DH dan DL
 SI : source index
 DI : destination index
 BP : base pointer
 SP : stack pointer
 IP : program counter

Ada register flags yang tidak bisa diakses langsung dengan menggunakan nama, tapi bisa diakses dengan sedikit trik sperti ini:

 PUSHF
 POP AX 

Dan untuk mengesetnya:

 PUSH AX
 POPF

8086 mendukung segmented addressing dengan register: CS (Code Segment), DS (Data Segment), ES (Extra Segment) dan SS (Stack Segment).

Sejak arsitektur 32 bit (sejak 80386), register-register tersebut diperluas:

EAX, EBX, ECX, EDX: versi 32 bit dari AX, BX, CX, DX, LSB dari versi 32 bit tetap diakses dari AX/BX/CX/DX
ESI, EDI, EBP, ESP, EIP: versi 32 bit dari SI, DI, BP, SP, IP
Segmen baru: FS dan GS

Sejak arsitektur 64 bit, registernya diperluas lagi:

RAX, RBX, RCX, RDX: versi 64 bit dari EAX, EBC, ECX, dan EDX
Register 64 bit general purpose baru: r8 sampai r15
RSI, RDI, RBP, RSP, RIP: versi 64 bit dari ESI, EDI, EBP, ESP, EIP

Register XMM/SSE

Sejak Pentium MMX ada 8 register khusus ditambahkan untuk SIMD (Single Instruction Multiple Data). Register ini adalah xmm0 sampai xmm7, pada mode 64 bit ada 8 register tambahan dari xmm8 sampai xmm15

Mode eksekusi

Arsitektur x86 memiliki berbagai mode eksekusi, 16/32/64 bit. Di mode 16 bit, semua register 32/64 bit tidak bisa diakses, di mode 32 bit, register 64 bit tidak bisa diakses. Ketika boot pertama kali, prosessor akan masuk mode 16 bit. Di prosessor 32 bit, bootloader bisa beralih ke mode 32 bit, dan di prosessor 64 bit, bootloader bisa beralih ke mode 64 bit.

Managemen Memori

Pada arsitektur 16 bit (8086), ini sangat sederhana, siapapun (program apapun) bisa mengakses program lain, termasuk juga milik sistem operasi ataupun BIOS. Karena ukuran register hanya 16 bit, maka teorinya alamat yang bisa diakses hanya 64 kilobyte, tapi 8086 mendukung segmentasi sederhana dengan register CS/DS/ES/SS, agar memori yang bisa diakses sampai dengan 1 megabyte. Alamat memori yang diakses adalah:

16 x SegmentReg + Offset.

Ketika 80286 diperkenalkan, ditambahkan protected mode sehingga memori yang bisa diakses bisa sampai 16 megabyte. Akses memori tidak lagi perkalian segment dengan nilai konstan, tapi merupakan index ke segment descriptor. Ini merupakan tabel yang berisi base address, jadi memori yang diakses menejadi:

SegmentDescriptor[SegmentReg] + Offset

Untuk arsitektur 32 bit dan 64 bit, ditambahkan lagi fitur baru yaitu paging, dengan fitur ini jika memori yang diakses tidak ditemukan, maka akan terjadi page fault, sistem operasi bisa menggunakan ini untuk fitur swap memory. Selain itu di versi 32 bit ada fitur memory protection, dengan ini sistem operasi bisa membatasi memori mana yang boleh diakses oleh suatu program.

Instruksi

Jumlah instruksi arsitektur x86 saat ini sudah sangat banyak, banyak yang redundan, dan banyak juga instruksi sangat spesifik untuk kasus tertentu (misalnya instruksi AES). Encoding instruksi sifatnya variabel length, artinya ada instruksi yang butuh 1 byte, 2 byte, 3 byte, dan maksimum sampai dengan 15 byte.

Karena sifatnya variable length, maka serangkaian byte bisa memiliki banyak arti jika kita melakukan decoding mulai dari titik yang berbeda. Hal ini banyak digunakan oleh malware untuk teknik anti disassembler.

Contoh Program Kecil

Berikut ini beberapa contoh program kecil (dalam assembly) menggunakan NASM.

Contoh program 16 bit DOS

DOS mendukung format file COM (tanpa header sama sekali) dan file EXE (memiliki executable header). Program kecil ini formatnya COM dan menggunakan interrupt DOS (interrupt 21) untuk memanggil fungsi print yang diterminasi dengan karakter dollar ($).

bits 16
org 100h
mov dx,msg
mov ah,9
int 21h
ret
msg db 'Contoh kode COM (DOS), Intel x86 16 bit',0Dh,0Ah,'$'

Informasi mengenai berbagai interrupt di DOS bisa dilihat di Ralph Brown's Interrupt List

Contoh program 32 bit Linux

Di Linux kita bisa memanggil fungsi puts milik libray C atau memanggil langsung fungsi kernel (melakukan syscall) dengan interrupt 0x80.

Contoh berikut ini menggunakan syscall, diambil dari http://asm.sourceforge.net/intro/hello.html

section     .text
global      _start                              ;must be declared for linker (ld)

_start:                                         ;tell linker entry point

    mov     edx,len                             ;message length
    mov     ecx,msg                             ;message to write
    mov     ebx,1                               ;file descriptor (stdout)
    mov     eax,4                               ;system call number (sys_write)
    int     0x80                                ;call kernel

    mov     eax,1                               ;system call number (sys_exit)
    int     0x80                                ;call kernel

section     .data

msg     db  'Hello, world!',0xa                 ;our dear string
len     equ $ - msg                             ;length of our dear string

dan untuk mengcompilenya

nasm -f elf contoh.asm
ld -melf_i386 contoh.o -o contoh

Contoh program 64 bit Linux

Pada mode 64 bit di Linux, untuk mengakses kernel digunakan instruksi syscall dengan nomor syscall yang berbeda

Menggunakan informasi dari http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ saya ubah program sebelumnya menjadi seperti ini:

bits 64
section     .text
global      _start                              ;must be declared for linker (ld)

_start:                                         ;tell linker entry point

    mov     rdx,len                             ;message length
    mov     rsi,msg                             ;message to write
    mov     rdi,1                               ;file descriptor (stdout)
    mov     rax,1                               ;system call number (sys_write)
    syscall

    mov     rax,60                               ;system call number (sys_exit)
    syscall 

section     .data

msg     db  'Hello, world!',0xa                 ;our dear string
len     equ $ - msg                             ;length of our dear string

dan untuk mengcompilenya

nasm -f elf64 contoh64.asm
ld contoh64.o -o contoh64

Calling convention

Ada begitu banyak sistem operasi dan compiler untuk x86 sehingga jumlah calling convention juga ada sangat banyak. Daftar lengkap bisa dilihat di:

https://en.wikipedia.org/wiki/X86_calling_conventions

Beberapa calling convention yang umum akan dibahas lebih lanjut di bab yang menjelaskan masing-masing sistem operasi.

Copyright © 2009-2018 Yohanes Nugroho