Jakie są konwencje wywoływania wywołań systemowych UNIX i Linux (i funkcji przestrzeni użytkownika) na i386 i x86-64

Poniższe linki wyjaśniają konwencje wywołań systemowych x86-32 zarówno dla Uniksa (BSD) jak i Linuksa:

Ale jakie są konwencje wywołań systemowych x86 - 64 na Unixie i Linuksie?

Author: Peter Cordes, 2010-03-29

4 answers

Czytaj dalej na dowolny z tematów tutaj: the Definitive Guide to Linux system Calls


Zweryfikowałem je używając asemblera GNU (gas) na Linuksie.

Interfejs Jądra

X86-32 aka i386 linux system Call convention:

W x86-32 parametry wywołania systemu Linux są przekazywane za pomocą rejestrów. %eax dla syscall_number. %ebx, %ecx, %edx, %esi, %edi, % ebp są używane do przekazywania 6 parametrów do systemu telefony.

Wartość zwracana jest w %eax. Wszystkie pozostałe rejestry (w tym EFLAGS) są przechowywane w int $0x80.

Wziąłem następujący fragment z Linux Assembly Tutorial ale wątpię w to. Jeśli ktoś może pokazać przykład, byłoby świetnie.

Jeśli jest więcej niż sześć argumentów, %ebx musi zawierać pamięć miejsce, w którym lista argumentów jest przechowywany - ale nie martw się o to ponieważ jest mało prawdopodobne, że użyjesz syscall z więcej niż sześć argumenty.

Dla przykładu i trochę więcej czytania, zobacz http://www.int80h.org/bsdasm/#alternate-calling-convention . kolejny przykład Hello World Dla i386 linux za pomocą int 0x80: Hello, world in assembly language with Linux system calls?

Istnieje szybszy sposób wykonywania 32-bitowych wywołań systemowych: używając sysenter. Jądro mapuje stronę pamięci do każdego procesu (vDSO), ze stroną przestrzeni użytkownika sysenter, która musi współpracuj z jądrem, aby mogło znaleźć adres zwrotny. Arg to register mapping jest takie samo jak dla int $0x80. Zwykle należy wywołać vdso zamiast używać sysenter bezpośrednio. (Patrz the Definitive Guide to Linux system Calls aby uzyskać informacje na temat łączenia i wywoływania do vDSO, i aby uzyskać więcej informacji na temat sysenter, i wszystko inne, związane z wywołaniami systemowymi.)

X86-32 [Free|Open|Net|DragonFly]BSD Unix System Call konwencja:

Parametry są przekazywane na stos. Wypchnij parametry (ostatni parametr wypchnięty pierwszy) na stos. Następnie wypchnij dodatkowe 32-bitowe atrapy danych (w rzeczywistości nie są to atrapy danych. więcej informacji można znaleźć w poniższym linku), a następnie podać instrukcję wywołania systemowego int $0x80

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


X86-64 konwencja wywołania systemu Linux:

(Uwaga: x86-64 Mac OS X jest podobny, ale inny od Linuksa. TODO: sprawdź co robi * BSD)

Zobacz sekcję: "A. 2 AMD64 Linux konwencje Jądra" System V Application Binary Interface amd64 Architecture Processor Supplement . Najnowsze wersje i386 i x86-64 System V psABIs można znaleźć podlinkowane z tej strony w repo opiekuna ABI . (Zobacz także tag wiki x86 dla aktualnych linków ABI i wielu innych dobrych rzeczy na temat x86 asm.)

Oto fragment z tej sekcji:

  1. aplikacje na poziomie użytkownika używają jako rejestrów całkowitych do przekazywania Sekwencja % rdi, %rsi, %rdx, %rcx, %r8 i %r9. interfejs jądra używa %rdi, %rsi, %rdx, %r10, % r8 i % R9.
  2. wywołanie systemowe odbywa się poprzez syscall Instrukcja . Ten clobbers %rcx i %r11, jak również wartość zwracana %rax, ale inne rejestry są zachowane.
  3. numer syscall musi być przekazany w zarejestruj %rax.
  4. wywołania systemowe są ograniczone do sześciu argumentów, żaden argument nie jest przekazywany bezpośrednio na stosie.
  5. powrót z syscall, register % rax zawiera wynik system-call. Wartość z zakresu od -4095 do -1 wskazuje błąd, to jest -errno.
  6. tylko wartości klasy INTEGER lub class MEMORY są przekazywane do jądra.

Pamiętaj, że to jest z linuksowego dodatku do ABI, a nawet dla Linuksa jest to pouczające nie normatywne. (Ale w rzeczywistości jest to dokładne.)

Ten 32-bitowy int $0x80 ABI jest użyteczny w 64-bitowym kodzie (ale wysoce niezalecany). co się stanie, jeśli użyjesz 32-bitowego int 0x80 Linux ABI w 64-bitowym kodzie? nadal obcina wejścia na 32-bit, więc nie nadaje się do wskaźników i zeruje r8-r11.

Interfejs użytkownika: wywołanie funkcji

X86-32 konwencja wywołania funkcji:

W x86-32 parametry były przekazywane na stosie. Ostatni parametr był wpychany najpierw na stos, aż wszystkie parametry zostały wykonane, a następnie została wykonana Instrukcja call. Jest to używane do wywoływania funkcji biblioteki C (libc) w Linuksie z assembly.

Współczesne wersje systemu i386 V ABI (używane w Linuksie) wymagają 16-bajtowego wyrównania %esp przed call, tak jak system x86-64 V ABI zawsze wymagał. Callees mogą zakładać, że i używać SSE 16-bajtowe ładunki / sklepy, które błąd na unaligned. Ale historycznie Linux wymagał tylko 4-bajtowe wyrównanie stosu, więc potrzeba było dodatkowej pracy, aby zarezerwować naturalnie wyrównane miejsce nawet dla 8-bajtowego double czy czegoś takiego.

Inne nowoczesne 32-bitowe systemy nadal nie wymagają wyrównania stosu o więcej niż 4 bajty.


X86-64 System V przestrzeń użytkownika konwencja wywołania funkcji:

X86-64 System V przekazuje args w rejestrach, co jest bardziej wydajne niż konwencja args stosu systemu V i386. Pozwala to uniknąć opóźnień i dodatkowych instrukcji przechowywania args do pamięci (cache) a potem Ładuję je z powrotem do callee. Działa to dobrze, ponieważ dostępnych jest więcej rejestrów i jest lepsze dla nowoczesnych wysokowydajnych procesorów, w których liczą się opóźnienia i realizacja poza zamówieniem. (I386 ABI jest bardzo stary).

W tym nowy mechanizm: najpierw parametry są podzielone na klasy. Klasa każdego parametru określa sposób, w jaki jest przekazywany do wywołanej funkcji.

Pełne informacje można znaleźć w : "3.2 wywołanie funkcji Sequence " z System V Application Binary Interface amd64 Architecture Processor Supplement który czyta, w części:

Po zaklasyfikowaniu argumentów, rejestry zostają przypisane (w kolejność od lewej do prawej) dla przejścia w następujący sposób:

  1. jeśli klasa jest pamięcią, podaj argument na stosie.
  2. jeśli klasa jest liczbą całkowitą, następny dostępny rejestr stosuje się sekwencje %rdi, %rsi, %rdx, %rcx, %r8 i %r9

Więc %rdi, %rsi, %rdx, %rcx, %r8 and %r9 są rejestry w kolejności używane do przekazywania parametrów integer/pointer (tj. klasy INTEGER) dowolnej funkcji libc z assembly. %rdi jest używane dla pierwszego parametru INTEGER. %rsi dla 2nd, %rdx dla 3rd i tak dalej. Następnie call Należy podać instrukcję. Stos (%rsp) musi być wyrównany do 16B podczas wykonywania call.

Jeśli jest więcej niż 6 parametrów INTEGER, 7. parametr INTEGER i Później są przekazywane na stosie. (Dzwoniący wyskakuje, tak samo jak x86-32.)

Pierwsza 8 zmiennoprzecinkowe ARG są przekazywane w %xmm0-7, później na stosie. Nie ma rejestrów wektorowych zachowanych do wywołania. (Funkcja z mieszanką argumentów FP i integer może mieć więcej niż 8 całkowitych argumentów rejestru.)

Variadic functions (like printf) zawsze potrzebujesz %al = numer args rejestru FP.

Istnieją reguły, kiedy pakować struktury do rejestrów (rdx:rax po powrocie) vs. w pamięci. Zobacz ABI po szczegóły i sprawdź wyjście kompilatora, aby upewnić się, że kod zgadza się z kompilatorami o tym, jak coś powinno być przekazywane/zwracane.


Zauważ, że konwencja wywołania funkcji systemu Windows x64 ma wiele znaczących różnic od x86-64 System V, takich jak przestrzeń cieni, która musi być zarezerwowana przez wywołującego (zamiast czerwonej strefy) i zachowana przez wywołanie xmm6-xmm15. I bardzo różne zasady dla których ARG idzie w którym rejestrze.

 252
Author: claws,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-19 19:32:53

Może szukasz ABI x86_64?

Jeśli nie jest to dokładnie to, czego szukasz, użyj "x86_64 abi" w preferowanej wyszukiwarce, aby znaleźć alternatywne odniesienia.

 15
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-25 18:48:32

Konwencje wywołania definiują sposób przekazywania parametrów w rejestrach podczas wywoływania lub wywoływania przez inny program. A najlepszym źródłem tej konwencji są standardy ABI zdefiniowane dla każdego z tych urządzeń. Dla ułatwienia kompilacji, ten sam ABI jest również używany przez program userspace i kernel. Linux / Freebsd podąża za tym samym ABI dla x86-64 i innym zestawem dla 32-bitów. Ale x86-64 ABI Dla Windows różni się od Linuksa / FreeBSD. I generalnie ABI nie rozróżnia wywołanie systemowe vs zwykłe "wywołania funkcji". Ie, oto szczególny przykład wywoływania konwencji x86_64 i jest to takie samo zarówno dla przestrzeni użytkownika Linuksa, jak i jądra: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ (zwróć uwagę na ciąg A,b,c,D,e, f parametrów):

Dobre renderowanie wywołujących konwencje vs wykorzystanie rejestrów

Wydajność jest jedną z przyczyn tych ABI (np. przekazywanie parametrów przez rejestry zamiast zapisywania do stosów pamięci)

Dla ARM istnieje wiele 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

Konwencja ARM64:

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

Dla Linuksa na PowerPC:

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

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

I dla osadzonych jest PPC EABI:

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

Ten dokument jest dobrym przeglądem wszystkich różnych konwencji:

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

 12
Author: Peter Teoh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-23 12:26:39

Linux kernel 5.0 komentarze źródłowe

Wiedziałem, że szczegóły x86 są Pod arch/x86, a te sprawy syscall idą pod arch/x86/entry. Tak więc szybki git grep rdi w tym katalogu prowadzi mnie do 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.
 */

I dla 32-bitowych w 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 Linux x86_64 implementacja wywołań systemowych

Teraz oszukujmy patrząc na główne implementacje libc i zobaczmy czym one są robiąc.

Co może być lepszego niż przeglądanie glibc, którego teraz używam podczas pisania tej odpowiedzi? :-)

Glibc 2.29 definiuje syscalls x86_64 na sysdeps/unix/sysv/linux/x86_64/sysdep.h i zawiera jakiś ciekawy kod, np.:

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

I:

/* 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;                       \
})
Co wydaje mi się oczywiste. Zauważ, jak wydaje się, że został zaprojektowany tak, aby dokładnie pasował do konwencji wywoływania zwykłych funkcji systemu V AMD64 ABI: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Szybkie przypomnienie clobbers:

  • cc oznacza rejestry flagi. Ale [45]} Peter Cordes komentuje że jest to niepotrzebne tutaj.
  • memory oznacza, że wskaźnik może być przekazany w assembly i użyty do uzyskania dostępu do pamięci

Aby uzyskać jednoznaczny, minimalny przykład od podstaw, zobacz odpowiedź: Jak wywołać wywołanie systemowe przez syscall lub sysenter in inline assembly?

Aby niektóre syscalls w montażu ręcznie

Niezbyt naukowe, ale zabawne:

  • 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 upstream .

Wykonywanie wywołań systemowych z C

Oto przykład z ograniczeniami rejestru: jak wywołać wywołanie systemowe przez syscall lub sysenter w wbudowanym zestawie?

Aarch64

Pokazałem minimum przykład runnable userland na: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep kernel kod tutaj, powinno być łatwe.

 8
Author: Ciro Santilli TRUMP BAN IS BAD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-07 09:09:34