İ386 ve x86-64'te UNIX ve Linux sistem çağrıları için çağrı kuralları nelerdir?


147

Aşağıdaki bağlantılar hem UNIX (BSD aroması) hem de Linux için x86-32 sistem çağrısı kurallarını açıklar:

Peki hem UNIX hem de Linux'ta x86-64 sistem çağrısı kuralları nelerdir?


Unix çağrı kuralları için "standart" yoktur. Eminim ki, ama eminim Solaris, OpenBSD, Linux ve Minix muhtemelen en azından biraz farklı arama kurallarına sahiptir ve hepsi unix'tir.
Mart'ta Earlz

2
Bu tamamen doğru değil - çoğu makine tipi için C derleyicilerinin birlikte çalışabilirlik elde etmesini sağlayan bir dizi UNIX ABI var. C ++ derleyicilerinin daha büyük bir sorunu vardır.
Jonathan Leffler

1
İkiniz de haklısınız. FreeBSD ve Linux'u arıyorum.
pençeleri

Cevabın, sistem çağrıları boyunca hangi kayıtların korunduğuna dair bilgi içerip içermediğini takdir ediyorum. Tabii ki yığın işaretçisi (__NR_clone çağrısında kontrollü bir şekilde değiştirilmedikçe), ama diğerleri mi?
Albert van der Horst

@AlbertvanderHorst: evet, wiki cevabını 32bit ayrıntılarıyla güncelledim. 64bit zaten doğruydu: sysretçalışma şekli nedeniyle rcx ve r11 imha edildi , rax dönüş değeri ile değiştirildi. Diğer tüm kayıtlar amd64'te korunur.
Peter Cordes

Yanıtlar:


231

Buradaki konular için daha fazla okuma: Linux Sistem Çağrıları için Kesin Kılavuz


Bunları Linux'ta GNU Assembler (gas) kullanarak doğruladım.

Çekirdek Arayüzü

x86-32 aka i386 Linux Sistem Çağrısı kuralı:

Linux için x86-32 parametreleri sistem çağrısı kayıtları kullanılarak geçirilir. %eaxsyscall_number için. % ebx,% ecx,% edx,% esi,% edi,% ebp, sistem parametrelerine 6 parametre iletmek için kullanılır.

Dönüş değeri %eax. Diğer tüm kayıtlar (EFLAGS dahil) genelinde korunur int $0x80.

Aşağıdaki pasajı Linux Assembly Tutorial'dan aldım ama bundan şüpheliyim. Herhangi biri bir örnek gösterebilirse, harika olurdu.

Altıdan fazla bağımsız değişken varsa, bağımsız değişkenler %ebxlistesinin depolandığı bellek konumunu içermelidir - ancak bunun için endişelenmeyin, çünkü altıdan fazla bağımsız değişken içeren bir sistem çağrısı kullanmanız pek olası değildir.

Örnek ve biraz daha fazla okuma için http://www.int80h.org/bsdasm/#alternate-calling-convention adresine bakın . İ386 Linux için bir Merhaba Dünya örneği int 0x80: Merhaba, Linux sistem çağrıları ile montaj dilinde dünya?

32-bit sistem çağrıları yapmanın daha hızlı bir yolu vardır: kullanma sysenter. Çekirdek sysenter, dönüş adresini bulabilmesi için çekirdeğin işbirliği yapması gereken dansın kullanıcı alanı tarafı ile her işleme (vDSO) bir bellek sayfası eşler . Eşleştirme kayıt için Arg for ile aynıdır int $0x80. Normalde sysenterdoğrudan kullanmak yerine vDSO'yu aramalısınız . ( VDSO'ya bağlanma ve çağrı yapma hakkında bilgi ve sistem çağrılarıyla ilgili diğer her şey hakkında daha fazla bilgi için Linux Sistem Çağrıları için Kesin Kılavuz'a bakın sysenter.)

x86-32 [Ücretsiz | Açık | Net | DragonFly] BSD UNIX Sistemi Çağrı kuralı:

Parametreler yığına iletilir. Parametreleri (ilk itilen son parametre) yığına doğru itin. Sonra ek bir 32-bit kukla veri (Aslında kukla veri değil. Daha fazla bilgi için aşağıdaki bağlantıya bakın) ve sonra bir sistem çağrısı talimatı verinint $0x80

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


x86-64 Linux Sistem Çağrısı kuralı:

x86-64 Mac OS X benzer ancak farklıdır . YAPILACAKLAR: * BSD'nin ne yaptığını kontrol et.

Bkz. Bölüm: Sistem V Uygulaması İkili Arayüz AMD64 Mimari İşlemci Eki "A.2 AMD64 Linux Çekirdek Kuralları" . İ386 ve x86-64 System V psABI'lerinin en son sürümlerini, bu sayfadan ABI bakımcının deposunda bulabilirsiniz . (Ayrıca bkz. wiki'yi güncel ABI bağlantıları ve x86 asm ile ilgili diğer birçok iyi şey için etiketleyin.)

İşte bu bölümden snippet:

  1. Kullanıcı seviyesi uygulamaları,% rdi,% rsi,% rdx,% rcx,% r8 ve% r9 dizilerini geçmek için tamsayı kayıtları olarak kullanılır. Çekirdek arabirimi% rdi,% rsi,% rdx,% r10,% r8 ve% r9 kullanır.
  2. syscallTalimat ile bir sistem çağrısı yapılır . Bu % rcx ve% r11 clobbers yanı sıra% rax dönüş değeri, ancak diğer kayıtlar korunur.
  3. Sistem çağrısı sayısı% rax kaydında geçirilmelidir.
  4. Sistem çağrıları altı argümanla sınırlıdır, doğrudan yığına herhangi bir argüman iletilmez.
  5. Sistem çağrısından dönen% rax kaydı, sistem çağrısının sonucunu içerir. -4095 ile -1 arasındaki bir değer bir hatayı belirtir -errno.
  6. Çekirdeğe yalnızca INTEGER veya MEMORY sınıfı değerleri iletilir.

Bunun ABI için Linux'a özgü ekten olduğunu ve Linux için bile normatif değil bilgilendirici olduğunu unutmayın. (Ama aslında doğrudur.)

Bu 32 bit int $0x80ABI , 64 bit kodda kullanılabilir (ancak kesinlikle önerilmez). 64 bit kodda 32 bit int 0x80 Linux ABI kullanırsanız ne olur? Hala girişlerini 32 bit olarak keser, bu nedenle işaretçiler için uygun değildir ve r8-r11'i sıfırlar.

Kullanıcı Arabirimi: işlev çağırma

x86-32 İşlev Çağırma kuralı:

X86-32'de parametreler yığına aktarıldı. Son parametre, tüm parametreler yapılana ve ardından calltalimat yürütülene kadar ilk önce yığına itildi . Bu, Linux'ta C kütüphanesi (libc) işlevlerini derlemeden çağırmak için kullanılır.

İ386 System V ABI'nin (Linux'ta kullanılır) modern sürümleri , x86-64 System V ABI'nin her zaman gerektirdiği gibi a'dan %espönce 16 baytlık hizalama callgerektirir. Callees, bunu varsaymaya ve hizalanmamış arızalanan SSE 16 bayt yükleri / depoları kullanmasına izin verilir. Ancak tarihsel olarak, Linux sadece 4 bayt yığın hizalaması gerektiriyordu, bu nedenle 8 baytlık doubleveya başka bir şey için bile doğal olarak hizalanmış alan ayırmak için ekstra iş gerekiyordu .

Bazı diğer modern 32-bit sistemler hala 4 bayttan fazla yığın hizalaması gerektirmez.


x86-64 Sistem V kullanıcı alanı İşlev Çağırma kuralı:

x86-64 Sistem V, kayıtları i386 Sistem V'in yığın bağımsız değişken kuralından daha verimli olan kayıtlara geçirir. Gecikmeleri ve değişkenleri belleğe (önbellek) depolamak ve daha sonra tekrar callee'ye yüklemek için ek talimatları önler. Bu, daha fazla kayıt olduğundan ve gecikmenin ve sıra dışı yürütmenin önemli olduğu modern yüksek performanslı CPU'lar için daha iyi olduğu için işe yarar. (İ386 ABI çok eskidir).

Bu yeni mekanizmada: İlk olarak parametreler sınıflara ayrılır. Her parametrenin sınıfı, çağrılan işleve aktarılma şeklini belirler.

Tam bilgi için bkz . Kısım olarak okuyan System V Uygulaması İkili Arabirim AMD64 Mimari İşlemci Eki'nin "3.2 İşlev Çağırma Dizisi" :

Bağımsız değişkenler sınıflandırıldıktan sonra, kayıtlar aşağıdaki gibi iletilmek üzere atanır (soldan sağa doğru):

  1. Sınıf HAFIZA ise, bağımsız değişkeni yığına iletin.
  2. Sınıf INTEGER ise,% rdi,% rsi,% rdx,% rcx,% r8 ve% r9 dizilerinin bir sonraki kullanılabilir kaydı kullanılır.

Böylece %rdi, %rsi, %rdx, %rcx, %r8 and %r9, tamsayı / pointer (yani INTEGER sınıfı) parametrelerini derlemeden herhangi bir libc fonksiyonuna geçirmek için kullanılan kayıtlar da kullanılır. % rdi, ilk INTEGER parametresi için kullanılır. 2. için% rsi, 3. için% rdx vb. O zaman calltalimat verilmelidir. Yığın ( %rsp) callyürütüldüğünde 16B hizalı olmalıdır .

6'dan fazla INTEGER parametresi varsa, 7. INTEGER parametresi ve daha sonra yığına iletilir. (Arayan açılır, x86-32 ile aynı.)

İlk 8 kayan nokta değişkeni% xmm0-7 içinde, daha sonra yığın üzerinde geçirilir. Arama korumalı vektör kayıtları yok. (FP ve tamsayı bağımsız değişkenlerinin bir karışımına sahip bir işlev toplam 8'den fazla kayıt bağımsız değişkenine sahip olabilir.)

Değişken işlevler ( gibiprintf ) her zaman %al= FP kayıt argümanlarının sayısına ihtiyaç duyar .

Yapıların ne zaman rdx:raxbellekte kayıtlara ( dönüşte) paketleneceğine dair kurallar vardır . Ayrıntılar için ABI'ye bakın ve kodunuzun derleyicilere bir şeyin nasıl iletilmesi / döndürülmesi gerektiği konusunda hemfikir olduğundan emin olmak için derleyici çıktısını kontrol edin.


O Not Windows x64 fonksiyonu çağıran kongre gölge alanı gibi X86-64 Sistem V birden fazla önemli farklılıklar vardır gerekir (yerine kırmızı-bölgenin) arayan tarafından ayrılacak ve çağrı korunmuş xmm6-xmm15. Ve hangi argümanın hangi kayda girdiği çok farklı kurallar.


1
Linux 32'de "ax bx cd dx si di bp dışındaki tüm kayıtlar korunur". Ben düşünemiyorum ...
Albert van der Horst

Amd64'te, 6'dan fazla parametre varsa ve bunlar yığına aktarılırsa, çağrı, arayan veya callee'den sonra yığını temizlemekten kim sorumludur?
Nicolás

1
@ Nicolás: Arayan yığını temizler. Cevabı, işlev çağırma kuralı hakkında daha fazla ayrıntıyla güncelledim.
Peter Cordes

1
Linux'un int 0x80ABI'sini 64 bit kodda kullanıyorsanız, tam olarak böyle olur: stackoverflow.com/questions/46087730/… . R8-r11'i sıfırlar ve 32 bit işlemde çalıştırıldığı gibi çalışır. Bu Soru-Cevap bölümünde, çalıştığını gösteren veya bir işaretçiyi keserek başarısız olan bir örneğim var. Ayrıca neden bu şekilde davrandığını göstermek için çekirdek kaynağına da girdim.
Peter Cordes

1
@EvanCarroll: Parçacık (alıntılanan metin), özellikle bölüm 4.3 Linux Sistem Çağrıları
Michael Petch

14

Belki de x86_64 ABI'yi mi arıyorsunuz?

Tam olarak aradığınız şey bu değilse, alternatif referanslar bulmak için tercih ettiğiniz arama motorunda 'x86_64 abi' kullanın.


5
aslında, sadece Sistem Çağrısı kuralını istiyorum. UNIX için esp (FreeBSD)
pençeleri

3
@claws: sistem çağrısı kuralı ABI'nin bir parçasıdır.
Jonathan Leffler

1
Evet. Her bir işletim sisteminin çekirdek geliştirme irc'ye gittim ve onlara sordum. Bana kaynağa bakmamı ve çözmememi söylediler. Bir şeyleri belgelemeden anlamıyorum, nasıl gelişmeye başlayabilirler? Bu yüzden, topladığım bilgilerden, başkalarının diğer ayrıntıları doldurmasını umarak bir cevap ekledim.
pençeleri

@JonathanLeffler bağlantı şu anda çalışmıyor gibi görünüyor. Bağlantıyı ziyaret ederken de bir sorun yaşıyorsanız, lütfen güncelleyebilir misiniz?
Ajay Brahmakshatriya

@AjayBrahmakshatriya: Uyarı için teşekkürler; Wayback Machine kaydına bir link ekledim. X86-64.org web sitesinin tamamı herhangi bir veriyle yanıt vermedi.
Jonathan Leffler

11

Arama kuralları, başka bir program tarafından çağrılırken veya çağrılırken parametrelerin kayıtlarda nasıl iletileceğini tanımlar. Ve bu sözleşmenin en iyi kaynağı, her bir donanım için tanımlanan ABI standartları biçimindedir. Derleme kolaylığı için, aynı ABI kullanıcı alanı ve çekirdek programı tarafından da kullanılır. Linux / Freebsd, x86-64 için aynı ABI'yi ve 32 bit için başka bir seti takip eder. Ancak Windows için x86-64 ABI, Linux / FreeBSD'den farklıdır. Ve genel olarak ABI, sistem çağrısını normal "işlev çağrıları" na göre ayırmaz. Yani, x86_64 çağrı kurallarının belirli bir örneği ve hem Linux kullanıcı alanı hem de çekirdek için aynı: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (parametrelerin a, b, c, d, e, f sırasına dikkat edin):

Arama kurallarına kıyasla kayıt kullanımının iyi bir gösterimi

Performans bu ABI'nin nedenlerinden biridir (örn. Parametreleri bellek yığınlarına kaydetmek yerine kayıtlardan geçirme)

ARM için çeşitli ABI var:

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

ARM64 sözleşmesi:

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

PowerPC'de Linux için:

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

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

Ve gömülü için PPC EABI var:

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

Bu belge, tüm farklı sözleşmelere genel bir bakıştır:

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


Tamamen noktanın yanında. Sorunun posteri, genel ABI dönüşümlerinden aynıysa, linux'daki 64 bit sistem çağrısı çağırma kuralını istemez.
Albert van der Horst

6

Linux çekirdek 5.0 kaynak yorumları

Ben x86 özellikleri altında arch/x86olduğunu ve bu sistem şeyler altında olduğunu biliyordum arch/x86/entry. Hızlı bir Yani git grep rdibu dizin derivasyonlarda yapmamı kemer / x86 / giriş / 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.
 */

ve 32-bit kemer / x86 / giriş / 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 Linux x86_64 sistem çağrısı uygulaması

Şimdi büyük bir libc uygulamasına bakarak hile yapalım ve ne yaptıklarını görelim.

Bu cevabı yazarken şu anda kullandığım glibc'ye bakmaktan daha iyi ne olabilir? :-)

glibc 2.29, x86_64 sistem çağrılarını tanımlar sysdeps/unix/sysv/linux/x86_64/sysdep.hve bu bazı ilginç kodlar içerir, örneğin:

/* 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.  */

ve:

/* 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;                       \
})

kendimi açıklayıcı olduğunu hissediyorum. Bunun, normal System V AMD64 ABI işlevlerinin çağrı kurallarına tam olarak uyacak şekilde nasıl tasarlandığına dikkat edin : https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Clobbers hızlı hatırlatma:

  • ccbayrak kayıtları anlamına gelir. Fakat Peter Cordes bunun gereksiz olduğunu söylüyor.
  • memory anlamına gelir montajda bir işaretçi geçirilebilir ve belleğe erişmek için kullanılabilir

Sıfırdan açık bir minimum çalıştırılabilir örnek için şu cevaba bakın: Satır içi montajda sysenter üzerinden bir sistem çağrısı nasıl çağırılır?

Manuel olarak montajda bazı sistem çağrıları yapın

Çok bilimsel değil, eğlenceli:

  • 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 akış yukarı .

aarch64

En az runnable userland örneği gösterdim: /reverseengineering/16917/arm64-syscalls-table/18834#18834 TODO grep çekirdek kodu burada, kolay olmalı.


1
"cc"Clobber gereksizdir: Linux sistem çağrıları / kaydetmek RFLAGS restore ( syscall/ sysrettalimatlar R11 kullanarak ve çekirdek kaydedilmiş R11 değiştirmez / aracılığıyla dışındaki RFLAGS bunu ptraceayıklayıcı sistemi çağrıları.) Bir çünkü hiç fark etmez o "cc"clobber olduğunu GNU C Extended asm içinde x86 / x86-64 için örtük olarak, dışarıda bırakarak hiçbir şey kazanamazsınız.
Peter Cordes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.