Register argumen dan balikan

rdi, rsi, rdx, rcx, r8, dan r9 untuk enam argumen pertama serta rax sebagai register balikan.

Daftar isi

Topik ini membedah jalur argumen dan jalur balikan pada System V AMD64 ABI secara langsung, mulai dari enam register pertama, argumen yang melebihi enam lewat stack, sampai bentuk balikan yang lebih besar dari satu register. Setiap aturan dibuktikan dengan fungsi assembly yang dipanggil dari C, sehingga pembacaan offset stack dan isi register dapat diperiksa ulang dari keluaran program. Enam register tersebut melayani argumen integer dan pointer, sedangkan argumen pecahan memiliki jalur xmm sendiri yang tidak menggunakan register daftar ini.

Enam argumen pertama menumpang register#

Kontrak menetapkan rdi, rsi, rdx, rcx, r8, dan r9 sebagai penampung enam argumen bilangan bulat pertama secara berurutan. Fungsi yang membutuhkan lebih dari enam argumen tidak memperpanjang daftar register, melainkan memindahkan sisanya ke stack. Contoh berikut menghitung penjumlahan enam nilai untuk memperlihatkan urutan register tersebut dari sisi yang dipanggil.

c utama.c
// tidak-dijalankan
#include <stdio.h>

long jumlah_enam(long a, long b, long c, long d, long e, long f);

int main(void) {
    long hasil = jumlah_enam(10, 20, 30, 40, 50, 60);
    printf("jumlah_enam(10, 20, 30, 40, 50, 60) = %ld\n", hasil);
    return 0;
}
nasm jumlah_enam.asm
default rel

global jumlah_enam

section .text
jumlah_enam:
    mov rax, rdi        ; argumen 1
    add rax, rsi        ; + argumen 2
    add rax, rdx        ; + argumen 3
    add rax, rcx        ; + argumen 4
    add rax, r8         ; + argumen 5
    add rax, r9         ; + argumen 6
    ret
bash
gcc -c utama.c -o utama.o
nasm -f elf64 jumlah_enam.asm -o jumlah_enam.o
gcc utama.o jumlah_enam.o -o prog
./prog
text Keluaran
jumlah_enam(10, 20, 30, 40, 50, 60) = 210

Setiap add membaca satu register argumen, dan urutannya mengikuti urutan argumen pada deklarasi C. Untuk fungsi yang seluruh argumennya berupa bilangan bulat, penempatannya selalu persis seperti kontrak, yaitu argumen ke-n berada di register ke-n. Register r10 dan r11 sengaja tidak tersentuh karena keduanya bukan register argumen, melainkan register kerja bebas yang juga berada dalam kelompok caller-saved.

Ukuran register mengikuti ukuran argumennya. Argumen int selebar 32 bit dikirim lewat edi, dan karena penulisan ke register 32-bit otomatis mengosongkan 32 bit atasnya, membaca rdi tetap menghasilkan nilai yang benar. Adapun argumen long selebar 64 bit menggunakan register penuhnya. Contoh-contoh pada topik ini menggunakan long sehingga setiap register terisi penuh dan tidak ada tafsir tambahan pada pembacaannya.

Argumen ketujuh dan seterusnya lewat stack#

Argumen setelah yang keenam diletakkan di stack. Pemanggil mendorongnya sebelum instruksi call, dan kontrak menetapkan arah pendorongannya dari kanan ke kiri, sehingga argumen terakhir berada pada alamat terendah. Pada titik masuk fungsi, [rsp] memuat alamat kembali yang didorong instruksi call, sehingga argumen ketujuh berada di [rsp+8] dan argumen kedelapan di [rsp+16]. Contoh berikut membaca kedua slot itu langsung pada titik masuk fungsi.

c utama_stack.c
// tidak-dijalankan
#include <stdio.h>

long delapan_arg(long a, long b, long c, long d, long e, long f, long g, long h);
long dua_arg_stack(long a, long b, long c, long d, long e, long f, long g, long h);

int main(void) {
    long sisa = dua_arg_stack(10, 20, 30, 40, 50, 60, 70, 80);
    long total = delapan_arg(10, 20, 30, 40, 50, 60, 70, 80);
    printf("dua_arg_stack membaca [rsp+8] + [rsp+16] = %ld\n", sisa);
    printf("delapan_arg(10, 20, 30, 40, 50, 60, 70, 80) = %ld\n", total);
    return 0;
}
nasm delapan_arg.asm
default rel

global delapan_arg
global dua_arg_stack

section .text
delapan_arg:
    mov r11, [rsp+8]    ; argumen ke-7 dibaca langsung dari stack
    mov r10, [rsp+16]   ; argumen ke-8 dibaca langsung dari stack
    mov rax, rdi        ; enam argumen pertama dari register
    add rax, rsi
    add rax, rdx
    add rax, rcx
    add rax, r8
    add rax, r9
    add rax, r11
    add rax, r10
    ret

dua_arg_stack:
    mov rax, [rsp+8]    ; argumen ke-7: slot stack pertama
    add rax, [rsp+16]   ; argumen ke-8: slot stack kedua
    ret
bash
gcc -c utama_stack.c -o utama_stack.o
nasm -f elf64 delapan_arg.asm -o delapan_arg.o
gcc utama_stack.o delapan_arg.o -o prog-stack
./prog-stack
text Keluaran
dua_arg_stack membaca [rsp+8] + [rsp+16] = 150
delapan_arg(10, 20, 30, 40, 50, 60, 70, 80) = 360

Fungsi dua_arg_stack membaca kedua slot stack langsung pada titik masuknya, lalu menjumlahkannya menjadi 150, yakni 70 + 80. Fungsi delapan_arg menjumlahkan keenam register argumen beserta kedua slot stack menjadi 360. Kedua angka itu membuktikan bahwa argumen ketujuh dan kedelapan benar-benar tiba di stack pada offset yang diramalkan kontrak, bukan di register.

Susunan memori di belakang angka-angka tersebut dapat ditelusuri dari urutan pendorongan. Pemanggil mendorong 80 lebih dahulu, lalu 70, sehingga 70 mendarat pada alamat yang lebih rendah dan berhenti tepat di [rsp+8] pada titik masuk fungsi, sedangkan 80 berada di [rsp+16]. Posisi inilah yang dibaca kedua fungsi assembly tanpa bantuan apa pun dari compiler.

Balikan satu register dan lebih#

Nilai balik bilangan bulat dan pointer dibawa rax dengan lebar sampai 64 bit, dan untuk balikan yang lebih besar kontrak menyediakan dua jalur. Balikan selebar 128 bit dibagi ke pasangan rax dan rdx, masing-masing 8 byte, dan pemanggil membaca keduanya secara berurutan. Adapun struktur yang lebih besar dari 16 byte tidak melewati register sama sekali. Pemanggil menyediakan memorinya, lalu menyelipkan alamatnya sebagai argumen tersembunyi di rdi, dan fungsi mengembalikan alamat yang sama di rax. Pada fungsi yang memiliki frame, bentuk balikan 128 bit terlihat seperti berikut.

nasm Contoh: balikan 128 bit lewat rax dan rdx
mov rax, [rbp-16]      ; 8 byte pertama
mov rdx, [rbp-8]       ; 8 byte berikutnya
ret

Pasangan rax dan rdx cocok untuk hasil ganda yang saling terkait, misalnya hasil bagi beserta sisanya, atau struct kecil berisi dua anggota 8 byte. Adapun jalur alamat tersembunyi adalah jalur yang dipilih compiler C untuk struct yang lebih besar, sehingga fungsi assembly yang memanggil maupun dipanggil C perlu menyadari kemungkinan rdi digunakan untuk itu. Rincian lengkap klasifikasi tipe balikan terdapat pada bagian nilai balik dokumen ABI yang dirujuk di akhir topik, sedangkan untuk fungsi bilangan bulat biasa cukup diingat satu aturannya, yaitu rax sudah harus memuat hasil pada saat ret dieksekusi.

Kesalahan umum#

Empat kekeliruan berikut paling sering muncul pada pembacaan argumen dan penulisan balikan.

  • Membaca argumen ketujuh dari [rsp] alih-alih [rsp+8]. Slot [rsp] memuat alamat kembali, sehingga keliru satu slot membuat nilai yang dibaca adalah alamat, bukan argumen.
  • Kehilangan isi register argumen setelah panggilan lain. Register argumen termasuk caller-saved, sehingga isinya tidak dijamin utuh setelah call. Salin nilai yang masih diperlukan ke register callee-saved atau ke stack sebelum memanggil fungsi lain.
  • Menaruh balikan di register yang salah kelompok. Pemanggil membaca balikan dari register yang ditetapkan kontrak untuk tipe balikannya, yaitu rax untuk bilangan bulat dan xmm0 untuk pecahan, sehingga hasil di register lain tidak pernah terbaca.
  • Memanggil fungsi variadik tanpa mengatur al. Register al menyatakan banyaknya register vektor yang digunakan argumen fungsi variadik, sehingga register itu tidak boleh dibiarkan berisi nilai sembarangan meskipun gejalanya sering tidak terlihat pada panggilan sederhana.

Ringkasan#

  • Enam argumen bilangan bulat pertama menumpang rdi, rsi, rdx, rcx, r8, dan r9 secara berurutan, dan balikan dibawa rax.
  • Argumen ketujuh dan seterusnya diletakkan pemanggil di stack serta dibaca dari [rsp+8], [rsp+16], dan seterusnya pada titik masuk fungsi.
  • Balikan 128 bit dibagi ke rax dan rdx, sedangkan struktur yang lebih besar dikembalikan lewat alamat yang diselipkan di rdi.
  • Register argumen termasuk kelompok caller-saved, sehingga isinya tidak dijamin bertahan melewati panggilan fungsi lain.

Referensi#