Assembly - Perkenalan

Hello, world!

Persiapan

Halo, Selamat datang. Sebelum mulai, mari kita persiapkan alat-alat yang akan digunakan.

Alat-alat yang dibutuhkan:

Membuat program dasar

Buatlah sebuah file dengan nama hello.asm menggunakan text editor kesukaan kalian

Di sini saya menggunakan text editor nano. jalankan

$ nano hello.asm

Tulislah kode dibawah ini

[section .rodata]
        msg db `Hello, world!\n\0`

[section .text]
        global _start
        
_start:
        mov rax, 1
        mov rdi, 1
        mov rsi, msg
        mov rdx, 14
        syscall
        
        mov rax, 60
        mov rdi, 0
        syscall

gambar

Setelah selesai, tekan Ctrl+X, Y, dan enter .

Jalankan

$ nasm -f elf64 hello.asm

Hasi keluaran dari nasm belum bisa dijalankan karena hanya berupa object file saja.

Oleh karena itu jalankan

$ ld hello.o -o hello

, agar linker menghasilkan executable.

Kita sudah berhasil membuat executablenya, sekarang mari kita eksekusi.

$ ./hello

Tada, program yang kita buat menghasilkan output “Hello, world!”

Dasar-dasar

Bagian-bagian program.

Dalam sebuah program, terdapat beberapa bagian/seksi.

Setidaknya ada 4 bagian yang umum

  • .data
  • .rodata
  • .bss
  • .text

Perlu dingat, isi semua bagian di atas (kecuali .text) adalah static

[section .data]

Lokasi data-data / variabel yang sudah diisi / diinisialisasi berada di sini.

section .data bersifat read write, bisa dibaca dan bisa ditulis.

[section .rodata]

Tempat data konstan atau tidak bisa diubah.

Dalam bahasa C/C++, tempat konstanta (const) global atau konstanta statis (const static) berada.

Semua literal string juga berada di sana, ex const char *str = "www\n".

Seperti namanya, section .rodata ini read only, hanya bisa dibaca.

[section .bss]

Alamat memori statis yang dialokasi berada di sini.

Note: unitialized static data secara default akan bernilai 0

section .bss bersifat read write.

[section .text]

Kode-kode yang akan dieksekusi di program berada di sini.

Sifatnya read exec.


Apakah moderasi tiap bagian bisa diubah ?

Ya bisa, tapi untuk sekarang kita tidak akan membahas itu.

System Call

Apa itu system call ?

System call adalah cara program berinteraksi dengan sistem operasi melalui kernel.

Saat kita melakukan syscall, kita menyuruh kernel untuk melakukan suatu fungsi kernel.

Gak Paham ?

Bahasa awamnya, system call adalah fungsi-fungsi sistem yang tersedia di kernel.

Instruksi dasar

NASM menggunakan syntax intel.

Bentuk sintaksnya kurang lebih sebagai berikut

instruksi dest, src


mov (move) adalah instruksi untuk memindahkan data ke dalam memori atau register.

mov x, y sama seperti x = y.


syscall adalah instruksi untuk memanggil system call dari kernel

Calling convention

Perhatikan penggunaan register untuk system call Linux x64 ini.

  • rax digunakan untuk menentukan nomor syscall
  • rdi digunakan untuk memberikan argumen pertama
  • rsi digunakan untuk memberikan argumen kedua
  • rdx digunakan untuk memberikan argumen ketiga
  • r10 digunakan untuk memberikan argumen keempat
  • r8 digunakan untuk memberikan argumen kelima
  • r9 digunakan untuk memberikan argumen keenam
SYSCALL_NR arg0 arg 1 arg2 arg3 arg4 arg5
RAX RDI RSI RDX R10 R8 R9

Kalian juga bisa baca-baca man 2 syscall.

Penjelasan lebih lanjut tentang calling-convention ini akan kita lanjut di bab calling convention.


Fun fact: Calling convention di atas adalah kernelspace System V ABI untuk arsitektur amd64

Yang di mana juga digunakan untuk semua os *nix seperti BSD, Darwin, yang berarsitektur amd64

Penjelasan

Untuk membuat bagian, kita menggunakan keyword section .nama

di mana .nama adalah nama bagian yang kita ingin buat.

[section .rodata]

db merupakan singkatan dari “define byte(s)

Seperti yang kita tahu, ukuran char di Linux x64 adalah 1 byte / 8 bit.

Berarti msg merupakan array of char / byte.

'\n' escape character untuk newline

'\0' untuk karakter NULL.

Jadi

msg db `Hello, world!\n\0`

sama seperti

static const char msg[] = "Hello, world!\n";

Dalam bahasa C.

Note : Dikarenakan msg berada dalam .rodata, maka msg bersifat statis (static) dan konstan (const) .

[section .text]

Sebelum kita lanjut ke pembahasan selanjutnya, saya ingin membuat analogi system call kernel dengan menu restoran.

nomenu digunakan untuk menentukan menu makanan (seperti rax yang menentukan nomor syscall).

ket1 digunakan untuk keterangan pertama / argumen pertama (seperti rdi).

Berikut adalah contoh tabel menu makanan

Nama makanan nomenu ket1
Lemper Jepang 1 jumlah
Tahu Bulat 2 jumlah


Instruksi isi digunakan untuk mengisi.

Instruksi pesan digunakan untuk memanggil pelayan atau menandakan pesanan kita sudah ditulis.


Kita diberikan kertas pesanan sebagai berikut

No menu ket1

Jika kita ingin memesan Lemper jepang dengan jumlah 5 buah maka kita mengisi tabelnya sebagai berikut

No menu ket1
1 5

Jika dituliskan dalam bentuk pseudocode assembly kurang lebih seperti di bawah ini.

isi nomenu, 1
; Mengisi nomenu dengan angka 1
isi ket1, 5
; Mengisi ket1 dengan angka 5
pesan
; Memesan lemper jepang sebanyak 5 buah

Maka pelayan akan menerima pesanan “Lemper Jepang”, berdasarkan nomenu dan menyajikan 5 buah lemper berdasarkan ket1.

Dalam fungsi C/C++, kode di atas sama seperti

lemper_jepang(5);


Oke, kita kembali ke materi.

global _start digunakan agar linker bisa mengakses label _start dari luar.

label _start sama seperti fungsi main dalam bahasa C/C++.

WRITE

Kita akan melakukan syscall write untuk mengeluarkan output “Hello, world!” ke command line.

Prototipe sys_write adalah sebagai berikut

write(int fd, const void *buf, size_t count);

| syscall | rax | rdi | rsi | rdx | r10 | r8 | r9 | | ——— | — | — | — | —- | —- | — | – | | write | 1 | fd | buf | count | - | - | - |

Referensi : man 2 write


mov rax, 1

rax = 1

Memasukkan nilai 1 ke dalam rax.

System call mana yang akan dipanggil ditentukan oleh register ini.

System Call write memiliki nomor 1 atau System Call nomor 1 adalah write.

Sekarang, register rax sudah terisi dengan nilai 1


###

mov rdi, 1

rdi = 1

Memasukkan nomor 1 ke dalam rdi.

Argumen pertama untuk sys_write adalah nomor file descriptor untuk ditulis

Karena kita ingin menampilkan pesan ke stdout maka kita mengisi rdi dengan nilai 1.


Saat suatu program dijalankan, setidaknya ada 3 stream yang secara default sudah terbuka.

stdin untuk menerima input,

stdout menampilkan output,

dan stderr untuk menampilkan pesan diagnosa atau error.

Ketiga stream tadi memilki nomor file descriptor yang tetap.

Berikut adalah tabel 3 standard streams dan nomornya.

fd value
stdin 0
stdout 1
stderr 2

<unistd.h> memilki tiga macro yang mendefinisikan nomor ketiga standard stream.

 #define STDIN_FILENO 0
 #define STDOUT_FILENO 1
 #define STDERR_FILENO 2
  • https://en.wikipedia.org/wiki/File_descriptor


###

mov rsi, msg

rsi = msg Mengisi rsi dengan nilai msg.

Argumen kedua syscall write adalah alamat data yang ingin ditulis.

Jadi sekarang register rsi berisi nilai alamat dari string msg


###

mov rdx, 14

rdx = 14

Mengisi rdx dengan nilai 14.

Argumen ketiga sys_write adalah jumlah bytes yang ingin ditulis

Ukuran msg tanpa karakter NULL adalah 14 bytes.


Kode-kode di atas tadi jika diubah ke dalam bahasa C sama seperti

write(rdi, rsi, rdx)
write(1, msg, 14)

sys_write sudah selesai sekarang kita bisa memanggil kernel dengan instruksi syscall

EXIT

Selanjutnya kita akan melakukan syscall exit untuk menghentikan program

Prototipenya

_exit(int status)
syscall rax rdi rsi rdx r10 r8 r9
sys_exit 60 status - - - - -

Referensi : man 2 exit

mov rax, 60

rax = 60

Mengisi rax dengan nilai 60.

Nomor syscall untuk sys_exit adalah 60.


###

mov rdi, 0

rdi = 0

Mengisi register rdi dengan nilai 0.


Hasilnya

_exit(0)

syscall _exit sudah selesai jadi kita memanggil kernel dengan instruksi syscall untuk melakukan sys_exit.


Pelajaran pertama untuk x64 Linux Assembly sudah selesai >//<, selanjutnya kita akan membahas register dan memori.

Extra

Kalian bisa mengecek syscall yang dilakukan suatu program menggunakan strace.

gambar

Nampak di atas kalau program yang tadi kita buat melakukan sys_write dan sys_exit .


Kalian juga bisa memanggil syscall-syscall di atas menggunakan bahasa C.

#include <unistd.h>

int main(void)
{
        static const char msg[] = "Hello, world!\n";
        write(STDOUT_FILENO, msg, 14);
        _exit(0);
}

Simpan file dengan nama main.s . Compile dengan perintah di bawah ini.

$ cc main.c -O2 -S -masm=intel -fno-pie

Compiler akan memberikan output file assembly dari program yang akan dicompile.

Outputnya akan menghasilkan main.s.

Berikut hasil assembly dari clang.

gambar

Lho kok beda dengan kode diatas, mengapa eax, edi, … bukan rax, rdi, … serta xor edi, edi bukan mov rdi, 0 ?

Compiler mengoptimisasi instruksi assemblynya, kita akan membahas alasannya seiring bertambahnya materi yang kita pelajari.

Untuk tabel syscall linux x64 bisa dilihat di sini

Untuk yang offline bisa dilihat di man syscalls dan nomornya

bagi yang menggunakan glibc ada di sini /usr/include/x86_64-linux-gnu/asm/unistd_64.h

results matching ""

    No results matching ""