Apa saja konvensi panggilan untuk panggilan sistem UNIX & Linux di i386 dan x86-64

147

Tautan berikut menjelaskan konvensi sistem panggilan x86-32 untuk UNIX (BSD flavour) & Linux:

Tapi apa konvensi sistem panggilan x86-64 di UNIX & Linux?

cakar
sumber
Tidak ada "standar" untuk konvensi pemanggilan Unix. Untuk linux pasti, tapi saya yakin bahwa Solaris, OpenBSD, Linux dan Minix mungkin memiliki konvensi pemanggilan yang sedikit berbeda dan setidaknya semuanya berbeda.
Earlz
2
Itu tidak sepenuhnya benar - ada satu set UNIX ABI tersedia untuk sebagian besar jenis mesin, yang memungkinkan kompiler C mencapai interoperabilitas. Kompiler C ++ memiliki masalah yang lebih besar.
Jonathan Leffler
1
Anda berdua benar. Saya mencari FreeBSD & Linux.
cakar
Saya akan sangat menghargai jika jawabannya berisi informasi tentang register apa yang disimpan di seluruh panggilan sistem. Tentu saja stack pointer, (kecuali diubah secara terkontrol dalam panggilan __NR_clone), tetapi apakah yang lain?
Albert van der Horst
@AlbertvanderHorst: ya, saya baru saja memperbarui jawaban wiki dengan perincian untuk 32bit. 64bit sudah akurat: rcx dan r11 dihancurkan karena cara sysretkerjanya, bersama dengan rax diganti dengan nilai pengembalian. Semua register lainnya disimpan pada amd64.
Peter Cordes

Jawaban:

230

Bacaan lebih lanjut untuk topik apa pun di sini: Panduan Definitif untuk Panggilan Sistem Linux


Saya memverifikasi ini menggunakan GNU Assembler (gas) di Linux.

Antarmuka Kernel

x86-32 alias Konvensi Sistem Panggilan i386 Linux:

Dalam x86-32, parameter untuk panggilan sistem Linux dilewatkan menggunakan register. %eaxuntuk syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp digunakan untuk melewatkan 6 parameter ke panggilan sistem.

Nilai kembali dalam %eax. Semua register lain (termasuk EFLAGS) dilestarikan di seluruh int $0x80.

Saya mengikuti cuplikan dari Tutorial Perakitan Linux tapi saya ragu tentang ini. Jika ada yang bisa menunjukkan contoh, itu akan bagus.

Jika ada lebih dari enam argumen, %ebxharus berisi lokasi memori tempat daftar argumen disimpan - tetapi jangan khawatir tentang ini karena tidak mungkin Anda akan menggunakan syscall dengan lebih dari enam argumen.

Untuk contoh dan bacaan lebih lanjut, lihat http://www.int80h.org/bsdasm/#alternate-calling-convention . Contoh lain dari Hello World untuk i386 Linux menggunakan int 0x80: Halo, dunia dalam bahasa assembly dengan panggilan sistem Linux?

Ada cara yang lebih cepat untuk melakukan panggilan sistem 32-bit: menggunakan sysenter. Kernel memetakan satu halaman memori ke dalam setiap proses (vDSO), dengan sisi ruang pengguna dari sysentertarian, yang harus bekerja sama dengan kernel agar dapat menemukan alamat pengirim. Arg untuk mendaftarkan pemetaan sama dengan untuk int $0x80. Anda biasanya harus memanggil ke vDSO daripada menggunakan sysentersecara langsung. (Lihat Panduan Definitif untuk Panggilan Sistem Linux untuk info tentang menautkan dan memanggil ke vDSO, dan untuk info lebih lanjut tentang sysenter, dan segala sesuatu yang berkaitan dengan panggilan sistem.)

x86-32 [Gratis | Buka | Net | DragonFly] Konvensi Sistem Panggilan BSD UNIX:

Parameter dilewatkan pada tumpukan. Dorong parameter (parameter terakhir didorong pertama) ke stack. Kemudian dorong data dummy 32-bit tambahan (Ini sebenarnya bukan data dummy. Lihat tautan berikut untuk info lebih lanjut) dan kemudian berikan instruksi panggilan sistemint $0x80

http://www.int80h.org/bsdasm/#default-calling-convention


Konvensi Sistem Panggilan Linux x86-64:

x86-64 Mac OS X serupa tetapi berbeda . TODO: periksa apa yang * BSD lakukan.

Lihat bagian: "A.2 Konvensi K64 Linux AMD64 " dari Aplikasi Sistem V Antarmuka Biner Tambahan Prosesor Arsitektur AMD64 . Versi terbaru dari Sistem i386 dan x86-64 psABI dapat ditemukan ditautkan dari halaman ini di repo pengelola ABI . (Lihat juga beri tag wiki untuk tautan ABI terbaru dan banyak hal bagus lainnya tentang as x86.)

Berikut cuplikan dari bagian ini:

  1. Aplikasi tingkat pengguna digunakan sebagai register integer untuk melewatkan urutan% rdi,% rsi,% rdx,% rcx,% r8 dan% r9. Antarmuka kernel menggunakan% rdi,% rsi,% rdx,% r10,% r8 dan% r9.
  2. Panggilan sistem dilakukan melalui syscallinstruksi . Clobbers ini % rcx dan% r11 serta nilai pengembalian % rax , tetapi register lain dipertahankan.
  3. Jumlah syscall harus diteruskan dalam register% rax.
  4. Panggilan sistem dibatasi hingga enam argumen, tidak ada argumen yang diteruskan langsung ke stack.
  5. Kembali dari syscall, register% rax berisi hasil dari system-call. Nilai dalam kisaran antara -4095 dan -1 menunjukkan kesalahan -errno.
  6. Hanya nilai class INTEGER atau MEMORY kelas yang diteruskan ke kernel.

Ingat ini dari lampiran khusus Linux ke ABI, dan bahkan untuk Linux itu informatif bukan normatif. (Tapi itu sebenarnya akurat.)

int $0x80ABI 32-bit ini dapat digunakan dalam kode 64-bit (tapi sangat tidak disarankan). Apa yang terjadi jika Anda menggunakan ABI 32-bit int 0x80 Linux dalam kode 64-bit? Itu masih memotong inputnya ke 32-bit, jadi itu tidak cocok untuk pointer, dan itu nol r8-r11.

Antarmuka Pengguna: pemanggilan fungsi

Konvensi Fungsi Memanggil x86-32:

Dalam x86-32 parameter diteruskan pada stack. Parameter terakhir didorong pertama ke stack sampai semua parameter dilakukan dan kemudian callinstruksi dieksekusi. Ini digunakan untuk memanggil fungsi C library (libc) di Linux dari assembly.

Versi modern dari System V ABI i386 (digunakan pada Linux) memerlukan perataan 16 byte %espsebelum call, seperti Sistem V86 AB86 x86 yang selalu diperlukan. Calle diizinkan untuk mengasumsikan itu dan menggunakan beban / toko SSE 16 byte yang gagal pada unaligned. Tetapi secara historis, Linux hanya membutuhkan 4-byte stack alignment, sehingga butuh kerja ekstra untuk memesan ruang yang rata-rata bahkan untuk 8-byte doubleatau sesuatu.

Beberapa sistem 32-bit modern lainnya masih tidak memerlukan lebih dari 4 byte stack alignment.


x86-64 Konvensi pengguna-ruang System V: Panggilan Fungsi:

x86-64 System V melewatkan args dalam register, yang lebih efisien daripada konvensi stack args i386 System V. Ini menghindari latensi dan instruksi tambahan menyimpan args ke memori (cache) dan kemudian memuatnya kembali di callee. Ini bekerja dengan baik karena ada lebih banyak register yang tersedia, dan lebih baik untuk CPU berkinerja tinggi modern di mana masalah latensi dan eksekusi tidak sesuai pesanan. (ABI i386 sudah sangat tua).

Dalam mekanisme baru ini : Pertama parameter dibagi ke dalam kelas. Kelas dari setiap parameter menentukan cara di mana ia dilewatkan ke fungsi yang dipanggil.

Untuk informasi lengkap, lihat: "3.2 Urutan Pemanggilan Fungsi" dari Aplikasi Sistem V Antarmuka Biner Tambahan Prosesor Arsitektur AMD64 yang berbunyi, sebagian:

Setelah argumen diklasifikasikan, register ditugaskan (dalam urutan kiri-ke-kanan) untuk diteruskan sebagai berikut:

  1. Jika kelas adalah MEMORY, sampaikan argumen pada stack.
  2. Jika kelas adalah INTEGER, register berikutnya yang tersedia dari urutan% rdi,% rsi,% rdx,% rcx,% r8 dan% r9 digunakan

Jadi %rdi, %rsi, %rdx, %rcx, %r8 and %r9adalah register agar digunakan untuk melewati bilangan bulat / pointer (yaitu INTEGER kelas) parameter ke fungsi libc dari perakitan. % rdi digunakan untuk parameter INTEGER pertama. % rsi untuk ke-2,% rdx untuk ke-3 dan seterusnya. Maka callinstruksi harus diberikan. Stack ( %rsp) harus sejajar 16B saat calldijalankan.

Jika ada lebih dari 6 parameter INTEGER, parameter INTEGER ke-7 dan yang lebih baru dilewatkan pada stack. (Pemanggil muncul, sama seperti x86-32.)

8 argumen floating point pertama dilewatkan dalam% xmm0-7, nanti di stack. Tidak ada register vektor yang diawetkan dengan panggilan. (Fungsi dengan campuran argumen FP dan integer dapat memiliki lebih dari 8 argumen register total.)

Fungsi variadik ( sepertiprintf ) selalu membutuhkan %al= jumlah argumen register FP.

Ada aturan untuk kapan memasukkan paket ke register ( rdx:raxsaat kembali) vs. dalam memori. Lihat ABI untuk detailnya, dan periksa output kompiler untuk memastikan kode Anda setuju dengan kompiler tentang bagaimana sesuatu harus dilewatkan / dikembalikan.


Perhatikan bahwa konvensi pemanggilan fungsi Windows x64 memiliki beberapa perbedaan signifikan dari Sistem x86-64, seperti ruang bayangan yang harus dipesan oleh penelepon (bukan zona merah), dan xmm6-xmm15 yang dipelihara dengan panggilan. Dan aturan yang sangat berbeda untuk arg mana masuk register.

cakar
sumber
1
Di linux 32 "semua register kecuali ax bx cd dx si di bp disimpan". Saya tidak bisa memikirkan apa pun ...
Albert van der Horst
Pada amd64, jika ada lebih dari 6 parameter dan dilewatkan pada stack, siapa yang bertanggung jawab untuk membersihkan stack setelah panggilan, penelepon atau callee?
Nicolás
1
@ Nicolás: pemanggil membersihkan tumpukan. Saya memperbarui jawabannya dengan lebih detail tentang konvensi pemanggilan fungsi.
Peter Cordes
1
Jika Anda menggunakan int 0x80ABI Linux dalam kode 64-bit, inilah yang terjadi: stackoverflow.com/questions/46087730/… . Ini nol r8-r11, dan bekerja persis seperti ketika dijalankan dalam proses 32-bit. Dalam T&J itu saya memiliki contoh yang menunjukkan itu berfungsi, atau gagal dengan memotong pointer. Dan saya juga menggali sumber kernel untuk menunjukkan mengapa ia berperilaku seperti itu.
Peter Cordes
1
@EvanCarroll: Cuplikan (kutipan teks) ada di tautan yang diberikan Tutorial Perakitan Linux secara khusus di bagian 4.3 Panggilan Sistem Linux
Michael Petch
14

Mungkin Anda mencari ABI x86_64?

Jika bukan itu yang Anda cari, gunakan 'x86_64 abi' di mesin pencari pilihan Anda untuk menemukan referensi alternatif.

Jonathan Leffler
sumber
5
sebenarnya, saya hanya ingin konvensi System Call. esp untuk UNIX (FreeBSD)
cakar
3
@ claws: konvensi pemanggilan sistem adalah salah satu bagian dari ABI.
Jonathan Leffler
1
ya. Saya telah pergi ke irc pengembangan OS masing-masing individu dan bertanya kepada mereka tentang hal itu. Mereka mengatakan kepada saya untuk mencari sumbernya dan mencari tahu. Saya tidak mengerti tanpa mendokumentasikan hal-hal bagaimana mereka bisa mulai berkembang? Jadi, saya telah menambahkan jawaban dari info yang saya kumpulkan, berharap orang lain mengisi sisa detailnya.
cakar
@JonathanLeffler tautannya sepertinya tidak berfungsi sekarang. Jika Anda juga mendapat masalah saat mengunjungi tautan, bisakah Anda memperbaruinya?
Ajay Brahmakshatriya
@AjayBrahmakshatriya: Terima kasih atas perhatiannya; Saya telah menambahkan tautan ke catatan Wayback Machine. Seluruh situs web x86-64.org tidak merespons dengan data apa pun.
Jonathan Leffler
11

Konvensi panggilan mendefinisikan bagaimana parameter dilewatkan dalam register saat memanggil atau dipanggil oleh program lain. Dan sumber terbaik konvensi ini adalah dalam bentuk standar ABI yang ditentukan untuk masing-masing perangkat keras ini. Untuk kemudahan kompilasi, ABI yang sama juga digunakan oleh userspace dan program kernel. Linux / Freebsd mengikuti ABI yang sama untuk x86-64 dan perangkat lain untuk 32-bit. Tetapi ABI x86-64 untuk Windows berbeda dari Linux / FreeBSD. Dan umumnya ABI tidak membedakan system call vs "fungsi panggilan" yang normal. Yaitu, berikut adalah contoh khusus dari konvensi pemanggilan x86_64 dan itu sama untuk ruang pengguna dan kernel Linux: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (perhatikan urutan a, b, c, d, e, f dari parameter):

Sebuah rendering yang baik dari konvensi panggilan vs penggunaan register

Kinerja adalah salah satu alasan untuk ABI ini (misalnya, melewatkan parameter melalui register alih-alih menyimpan ke dalam tumpukan memori)

Untuk ARM ada berbagai ABI:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html

https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/iPhoneOSABIReference.pdf

Konvensi ARM64:

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf

Untuk Linux di PowerPC:

http://refspecs.freestandards.org/elf/elfspec_ppc.pdf

http://www.0x04.net/doc/elf/psABI-ppc64.pdf

Dan untuk embedded ada PPC EABI:

http://www.freescale.com/files/32bit/doc/app_note/PPCEABI.pdf

Dokumen ini adalah tinjauan umum yang baik dari semua konvensi yang berbeda:

http://www.agner.org/optimize/calling_conventions.pdf

Peter Teoh
sumber
Benar-benar selain intinya. Poster pertanyaan tidak akan meminta konvensi pemanggilan syscall 64 bit di linux jika itu sama dengan konversi ABI umum.
Albert van der Horst
6

Kernel Linux 5.0 komentar sumber

Saya tahu bahwa spesifikasi x86 berada di bawah arch/x86, dan hal-hal syscall berjalan di bawah arch/x86/entry. Jadi cepat git grep rdidalam direktori itu membawa saya ke arch / x86 / entry / entry_64.S :

/*
 * 64-bit SYSCALL instruction entry. Up to 6 arguments in registers.
 *
 * This is the only entry point used for 64-bit system calls.  The
 * hardware interface is reasonably well designed and the register to
 * argument mapping Linux uses fits well with the registers that are
 * available when SYSCALL is used.
 *
 * SYSCALL instructions can be found inlined in libc implementations as
 * well as some other programs and libraries.  There are also a handful
 * of SYSCALL instructions in the vDSO used, for example, as a
 * clock_gettimeofday fallback.
 *
 * 64-bit SYSCALL saves rip to rcx, clears rflags.RF, then saves rflags to r11,
 * then loads new ss, cs, and rip from previously programmed MSRs.
 * rflags gets masked by a value from another MSR (so CLD and CLAC
 * are not needed). SYSCALL does not save anything on the stack
 * and does not change rsp.
 *
 * Registers on entry:
 * rax  system call number
 * rcx  return address
 * r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
 * rdi  arg0
 * rsi  arg1
 * rdx  arg2
 * r10  arg3 (needs to be moved to rcx to conform to C ABI)
 * r8   arg4
 * r9   arg5
 * (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
 *
 * Only called from user space.
 *
 * When user can change pt_regs->foo always force IRET. That is because
 * it deals with uncanonical addresses better. SYSRET has trouble
 * with them due to bugs in both AMD and Intel CPUs.
 */

dan untuk 32-bit di arch / x86 / entry / entry_32.S :

/*
 * 32-bit SYSENTER entry.
 *
 * 32-bit system calls through the vDSO's __kernel_vsyscall enter here
 * if X86_FEATURE_SEP is available.  This is the preferred system call
 * entry on 32-bit systems.
 *
 * The SYSENTER instruction, in principle, should *only* occur in the
 * vDSO.  In practice, a small number of Android devices were shipped
 * with a copy of Bionic that inlined a SYSENTER instruction.  This
 * never happened in any of Google's Bionic versions -- it only happened
 * in a narrow range of Intel-provided versions.
 *
 * SYSENTER loads SS, ESP, CS, and EIP from previously programmed MSRs.
 * IF and VM in RFLAGS are cleared (IOW: interrupts are off).
 * SYSENTER does not save anything on the stack,
 * and does not save old EIP (!!!), ESP, or EFLAGS.
 *
 * To avoid losing track of EFLAGS.VM (and thus potentially corrupting
 * user and/or vm86 state), we explicitly disable the SYSENTER
 * instruction in vm86 mode by reprogramming the MSRs.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  user stack
 * 0(%ebp) arg6
 */

glibc 2.29 implementasi system call Linux x86_64

Sekarang mari kita selingkuh dengan melihat implementasi libc utama dan lihat apa yang mereka lakukan.

Apa yang bisa lebih baik daripada melihat ke glibc yang saya gunakan saat ini ketika saya menulis jawaban ini? :-)

glibc 2.29 mendefinisikan syscalls x86_64 di sysdeps/unix/sysv/linux/x86_64/sysdep.hdan yang berisi beberapa kode menarik, misalnya:

/* The Linux/x86-64 kernel expects the system call parameters in
   registers according to the following table:

    syscall number  rax
    arg 1       rdi
    arg 2       rsi
    arg 3       rdx
    arg 4       r10
    arg 5       r8
    arg 6       r9

    The Linux kernel uses and destroys internally these registers:
    return address from
    syscall     rcx
    eflags from syscall r11

    Normal function call, including calls to the system call stub
    functions in the libc, get the first six parameters passed in
    registers and the seventh parameter and later on the stack.  The
    register use is as follows:

     system call number in the DO_CALL macro
     arg 1      rdi
     arg 2      rsi
     arg 3      rdx
     arg 4      rcx
     arg 5      r8
     arg 6      r9

    We have to take care that the stack is aligned to 16 bytes.  When
    called the stack is not aligned since the return address has just
    been pushed.


    Syscalls of more than 6 arguments are not supported.  */

dan:

/* Registers clobbered by syscall.  */
# define REGISTERS_CLOBBERED_BY_SYSCALL "cc", "r11", "cx"

#undef internal_syscall6
#define internal_syscall6(number, err, arg1, arg2, arg3, arg4, arg5, arg6) \
({                                  \
    unsigned long int resultvar;                    \
    TYPEFY (arg6, __arg6) = ARGIFY (arg6);              \
    TYPEFY (arg5, __arg5) = ARGIFY (arg5);              \
    TYPEFY (arg4, __arg4) = ARGIFY (arg4);              \
    TYPEFY (arg3, __arg3) = ARGIFY (arg3);              \
    TYPEFY (arg2, __arg2) = ARGIFY (arg2);              \
    TYPEFY (arg1, __arg1) = ARGIFY (arg1);              \
    register TYPEFY (arg6, _a6) asm ("r9") = __arg6;            \
    register TYPEFY (arg5, _a5) asm ("r8") = __arg5;            \
    register TYPEFY (arg4, _a4) asm ("r10") = __arg4;           \
    register TYPEFY (arg3, _a3) asm ("rdx") = __arg3;           \
    register TYPEFY (arg2, _a2) asm ("rsi") = __arg2;           \
    register TYPEFY (arg1, _a1) asm ("rdi") = __arg1;           \
    asm volatile (                          \
    "syscall\n\t"                           \
    : "=a" (resultvar)                          \
    : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4),     \
      "r" (_a5), "r" (_a6)                      \
    : "memory", REGISTERS_CLOBBERED_BY_SYSCALL);            \
    (long int) resultvar;                       \
})

yang saya rasa cukup jelas. Perhatikan bagaimana ini tampaknya telah dirancang untuk benar-benar cocok dengan konvensi pemanggilan fungsi System V AMD64 ABI reguler: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Pengingat cepat dari para clobbers:

  • ccberarti register bendera. Tetapi Peter Cordes berkomentar bahwa ini tidak perlu di sini.
  • memory berarti bahwa penunjuk dapat dilewatkan dalam rakitan dan digunakan untuk mengakses memori

Untuk contoh runnable minimal yang eksplisit dari awal, lihat jawaban ini: Bagaimana menjalankan panggilan sistem melalui sysenter dalam inline assembly?

Buat beberapa panggilan syscall secara manual

Tidak terlalu ilmiah, tetapi menyenangkan:

  • x86_64.S

    .text
    .global _start
    _start:
    asm_main_after_prologue:
        /* write */
        mov $1, %rax    /* syscall number */
        mov $1, %rdi    /* stdout */
        mov $msg, %rsi  /* buffer */
        mov $len, %rdx  /* len */
        syscall
    
        /* exit */
        mov $60, %rax   /* syscall number */
        mov $0, %rdi    /* exit status */
        syscall
    msg:
        .ascii "hello\n"
    len = . - msg
    

    GitHub hulu .

aarch64

Saya telah menunjukkan contoh userland runnable minimal di: /reverseengineering/16917/arm64-syscalls-table/18834#18834 TODO grep kode kernel di sini, seharusnya mudah.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
1
The "cc"mengkritik tidak perlu: syscalls Linux menyimpan / mengembalikan RFLAGS (The syscall/ sysretpetunjuk melakukan itu menggunakan R11, dan kernel tidak memodifikasi R11 disimpan / RFLAGS selain melalui ptracepanggilan sistem debugger.) Bukan berarti itu pernah penting, karena "cc"mengkritik adalah implisit untuk x86 / x86-64 di GNU C Extended asm, jadi Anda tidak bisa mendapatkan apa-apa dengan meninggalkannya.
Peter Cordes