Jakie są konwencje wywoływania dla wywołań systemowych UNIX i Linux na i386 i x86-64

147

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

Ale jakie są konwencje wywołań systemowych x86-64 w systemach UNIX i Linux?

pazury
źródło
Nie ma „standardu” dla uniksowych konwencji wywoływania. Na pewno dla Linuksa, ale jestem pewien, że Solaris, OpenBSD, Linux i Minix prawdopodobnie mają różne, przynajmniej trochę inne konwencje wywoływania i wszystkie są uniksowe.
Earlz
2
To nie do końca prawda - dla większości typów maszyn dostępny jest zestaw uniksowych interfejsów ABI, które umożliwiają kompilatorom języka C osiągnięcie interoperacyjności. Kompilatory C ++ mają większy problem.
Jonathan Leffler
1
Oboje macie rację. Szukam FreeBSD i Linux.
pazury
Byłbym wdzięczny, gdyby odpowiedź zawierała informacje o tym, jakie rejestry są zachowane w wywołaniach systemowych. Oczywiście wskaźnik stosu to (chyba że zmieniono go w kontrolowany sposób w wywołaniu __NR_clone), ale czy są inne?
Albert van der Horst
@AlbertvanderHorst: tak, właśnie zaktualizowałem odpowiedź wiki o szczegóły dotyczące wersji 32-bitowej. 64bit był już dokładny: rcx i r11 są niszczone ze względu na sposób sysretdziałania, wraz z zastąpieniem rax wartością zwracaną. Wszystkie inne rejestry są zachowane w amd64.
Peter Cordes

Odpowiedzi:

230

Dalsze czytanie dowolnego z tematów tutaj: Ostateczny przewodnik po wywołaniach systemu Linux


Sprawdziłem je używając GNU Assembler (gas) w Linuksie.

Interfejs jądra

x86-32 aka i386 Konwencja wywołań systemowych Linux:

W x86-32 parametry dla wywołań systemowych Linuksa są przekazywane za pomocą rejestrów. %eaxdla syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp służą do przekazywania 6 parametrów do wywołań systemowych.

Wartość zwracana jest w %eax. Wszystkie inne rejestry (w tym EFLAGS) są zachowywane w int $0x80.

Wziąłem następujący fragment z samouczka montażu Linuksa, ale mam co do tego wątpliwości. Byłoby świetnie, gdyby ktoś mógł pokazać przykład.

Jeśli jest więcej niż sześć argumentów, %ebxmusi zawierać lokalizację pamięci, w której przechowywana jest lista argumentów - ale nie martw się tym, ponieważ jest mało prawdopodobne, że użyjesz wywołania systemowego z więcej niż sześcioma argumentami.

Przykład i trochę więcej informacji można znaleźć pod adresem http://www.int80h.org/bsdasm/#alternate-calling-convention . Inny przykład Hello World dla i386 Linux używający int 0x80: Hello, world in asembler z wywołaniami systemu Linux?

Istnieje szybszy sposób wykonywania 32-bitowych wywołań systemowych: użycie sysenter. Jądro mapuje stronę pamięci do każdego procesu (vDSO), ze stroną sysentertańca przestrzeni użytkownika , która musi współpracować z jądrem, aby móc znaleźć adres zwrotny. Argument mapowania rejestrów jest taki sam jak dla int $0x80. Zwykle powinieneś dzwonić do vDSO zamiast używać go sysenterbezpośrednio. (Zobacz The Definitive Guide to Linux System Calls, aby uzyskać informacje na temat łączenia i wywoływania vDSO, a także więcej informacji sysenteri wszystkiego, co ma związek z wywołaniami systemowymi).

x86-32 [Free | Open | Net | DragonFly] Konwencja wywołań systemowych BSD UNIX:

Parametry są przekazywane na stosie. Umieść parametry (ostatni parametr wstawiony jako pierwszy) na stos. Następnie wypchnij dodatkowe 32-bitowe fikcyjne dane (nie są to faktycznie fikcyjne dane. Więcej informacji znajdziesz w poniższym linku), a następnie podaj instrukcję wywołania systemowegoint $0x80

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


Konwencja wywołań systemowych Linux x86-64:

x86-64 Mac OS X jest podobny, ale inny . DO ZROBIENIA: sprawdź, co robi * BSD.

Zobacz sekcję: „A.2 Konwencje jądra systemu AMD64 Linux ” w Dodatku do interfejsu binarnego aplikacji Systemu V Dodatek do procesora architektury AMD64 . Można znaleźć najnowsze wersje psABI Systemu V i386 i x86-64 na tej stronie w repozytorium opiekuna ABI . (Zobacz także oznacz wiki dla aktualnych linków ABI i wielu innych dobrych rzeczy o asm x86.)

Oto fragment z tej sekcji:

  1. Aplikacje na poziomie użytkownika używają rejestrów całkowitych do przekazywania sekwencji% 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ę za pośrednictwem syscallinstrukcji . Powoduje to przebicie% rcx i% r11, a także wartości zwracanej% rax, ale inne rejestry są zachowane.
  3. Numer wywołania systemowego musi zostać przekazany do rejestru% rax.
  4. Wywołania systemowe są ograniczone do sześciu argumentów, żaden argument nie jest przekazywany bezpośrednio na stos.
  5. Wracając z wywołania systemowego, rejestr% rax zawiera wynik wywołania systemowego. Wartość w zakresie od -4095 do -1 wskazuje na błąd -errno.
  6. Do jądra przekazywane są tylko wartości klasy INTEGER lub MEMORY.

Pamiętaj, że pochodzi to z dodatku do ABI specyficznego dla Linuksa, a nawet dla Linuksa ma charakter informacyjny, a nie normatywny. (Ale w rzeczywistości jest dokładny.)

Ten 32-bitowy int $0x80ABI jest użyteczny w 64-bitowym kodzie (ale zdecydowanie nie jest zalecany). Co się stanie, jeśli użyjesz 32-bitowego int 0x80 Linux ABI w kodzie 64-bitowym? Nadal skraca swoje dane wejściowe do 32-bitowych, więc nie nadaje się do wskaźników i zeruje r8-r11.

Interfejs użytkownika: wywołanie funkcji

Konwencja wywoływania funkcji x86-32:

W x86-32 parametry były przekazywane na stosie. Ostatni parametr został najpierw odłożony na stos, aż wszystkie parametry zostały wykonane, a następnie callzostała wykonana instrukcja. Służy do wywoływania funkcji biblioteki C (libc) w systemie Linux z zestawu.

Nowoczesne wersje i386 System V ABI (używane w Linuksie) wymagają 16-bajtowego wyrównania %espprzed a call, tak jak w przypadku x86-64 System V ABI. Osoby wywołujące mogą założyć, że i używać 16-bajtowych obciążeń / magazynów SSE, które powodują błędy w stanie niewyrównanym. Ale historycznie Linux wymagał tylko 4-bajtowego wyrównania stosu, więc zarezerwowanie naturalnie wyrównanego miejsca wymagało dodatkowej pracy, nawet dla 8-bajtowego doublelub czegoś podobnego.

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


x86-64 Konwencja wywoływania funkcji w przestrzeni użytkownika Systemu V:

x86-64 System V przekazuje argumenty do rejestrów, co jest bardziej wydajne niż konwencja argumentów stosu w i386 System V. Pozwala uniknąć opóźnień i dodatkowych instrukcji dotyczących przechowywania argumentów w pamięci (cache), a następnie ponownego ładowania ich do wywoływanego. Działa to dobrze, ponieważ dostępnych jest więcej rejestrów i jest lepsze dla nowoczesnych procesorów o wysokiej wydajności, w których liczy się opóźnienie i wykonanie poza kolejnością. (I386 ABI jest bardzo stary).

W tym nowym mechanizmie: Najpierw parametry są podzielone na klasy. Klasa każdego parametru określa sposób, w jaki jest on przekazywany do wywoływanej funkcji.

Pełne informacje można znaleźć w: „3.2 Sekwencja wywoływania funkcji” w Dodatku binarnym interfejsu aplikacji Systemu V do procesora architektury AMD64 , w którym czytamy między innymi:

Po sklasyfikowaniu argumentów rejestry są przypisywane (w kolejności od lewej do prawej) do przekazania w następujący sposób:

  1. Jeśli klasa to MEMORY, przekaż argument na stosie.
  2. Jeśli klasa to INTEGER, używany jest następny dostępny rejestr sekwencji% rdi,% rsi,% rdx,% rcx,% r8 i% r9

Kolejność%rdi, %rsi, %rdx, %rcx, %r8 and %r9 rejestrów jest więc używana do przekazywania parametrów całkowitoliczbowych / wskaźników (tj. Klasy INTEGER) do dowolnej funkcji libc z assemblera. % rdi jest używany jako pierwszy parametr INTEGER. % rsi dla drugiego,% rdx dla trzeciego i tak dalej. Następnie należy udzielić instrukcji. Metoda stack ( ) musi być wyrównana do 16B podczas wykonywania.call%rspcall

Jeśli jest więcej niż 6 parametrów INTEGER, na stos przekazywany jest siódmy parametr INTEGER i później. (Wyskakuje dzwoniący, tak samo jak x86-32.)

Pierwsze 8 argumentów zmiennoprzecinkowych jest przekazywanych w% xmm0-7, później na stosie. Nie ma rejestrów wektorów z zachowanymi połączeniami. (Funkcja z kombinacją argumentów FP i liczb całkowitych może mieć łącznie więcej niż 8 argumentów rejestru).

Funkcje zmienne ( takie jakprintf ) zawsze potrzebują %al= liczba argumentów rejestru FP.

Istnieją zasady dotyczące tego, kiedy pakować struktury do rejestrów ( rdx:raxpo powrocie), a kiedy w pamięci. Zobacz ABI, aby uzyskać szczegółowe informacje, i sprawdź dane wyjściowe kompilatora, aby upewnić się, że kod zgadza się z kompilatorami, w jaki sposób coś powinno zostać przekazane / zwrócone.


Zauważ, że konwencja wywoływania funkcji Windows x64 ma wiele znaczących różnic w stosunku do x86-64 System V, na przykład przestrzeń w cieniu, która musi być zarezerwowana przez wywołującego (zamiast czerwonej strefy) i zachowana rozmowa xmm6-xmm15. I bardzo różne reguły, dla których argument trafia do którego rejestru.

pazurów
źródło
1
W Linuksie 32 "zachowane są wszystkie rejestry z wyjątkiem ax bx cd dx si di bp". Nie mogę wymyślić żadnego ...
Albert van der Horst
W amd64, jeśli jest więcej niż 6 parametrów i są one przekazywane na stosie, kto jest odpowiedzialny za czyszczenie stosu po wywołaniu, dzwoniącym lub wywoływanym?
Nicolás
1
@ Nicolás: rozmówca czyści stos. Zaktualizowałem odpowiedź, dodając więcej szczegółów na temat konwencji wywoływania funkcji.
Peter Cordes,
1
Jeśli używasz int 0x80ABI Linuksa w 64-bitowym kodzie, dokładnie tak się dzieje: stackoverflow.com/questions/46087730/… . Zeruje r8-r11 i działa dokładnie tak, jak w przypadku uruchomienia w procesie 32-bitowym. W tym pytaniu i odpowiedzi mam przykład pokazujący, że działa lub nie działa z obcięciem wskaźnika. Zagłębiłem się również w źródłach jądra, aby pokazać, dlaczego tak się zachowuje.
Peter Cordes,
1
@EvanCarroll: Fragment (cytowany tekst) znajduje się pod linkiem podanym w samouczku montażu Linuksa, konkretnie w sekcji 4.3 Wywołania systemu Linux
Michael Petch
14

Być może szukasz interfejsu 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.

Jonathan Leffler
źródło
5
właściwie, chcę tylko konwencji wywołań systemowych. esp dla UNIX (FreeBSD)
pazury
3
@claws: konwencja wywołań systemowych jest częścią ABI.
Jonathan Leffler
1
Tak. Poszedłem do irc zajmującego się rozwojem jądra każdego systemu operacyjnego i zapytałem ich o to. Kazali mi zajrzeć do źródła i dowiedzieć się. Nie rozumiem bez udokumentowania rzeczy, jak mogą po prostu zacząć się rozwijać? Więc dodałem odpowiedź z informacji, które zebrałem, mając nadzieję, że inni uzupełnią resztę szczegółów.
pazury
@JonathanLeffler link wydaje się nie działać w tej chwili. Jeśli pojawia się również problem z odwiedzaniem łącza, czy możesz go zaktualizować?
Ajay Brahmakshatriya
@AjayBrahmakshatriya: Dzięki za ostrzeżenia; Dodałem łącze do rekordu Wayback Machine. Cała witryna internetowa x86-64.org nie odpowiedziała żadnymi danymi.
Jonathan Leffler
11

Konwencje wywoływania definiują sposób przekazywania parametrów do rejestrów 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ń. Aby ułatwić kompilację, ten sam interfejs ABI jest również używany przez przestrzeń użytkownika i program jądra. Linux / Freebsd stosuje ten sam ABI dla x86-64 i inny zestaw dla wersji 32-bitowej. Ale x86-64 ABI dla Windows różni się od Linux / FreeBSD. I ogólnie ABI nie rozróżnia wywołań systemowych od zwykłych „wywołań funkcji”. To znaczy, oto szczególny przykład konwencji wywoływania x86_64 i jest taki sam dla przestrzeni użytkownika i jądra Linuksa: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (zwróć uwagę na sekwencję a, b, c, d, e, f parametrów):

Dobre renderowanie konwencji wywoływania a użycie rejestrów

Wydajność jest jednym z powodów tych ABI (np. Przekazywanie parametrów przez rejestry zamiast zapisywania w stosach pamięci)

W przypadku ARM istnieją różne 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

Linux na PowerPC:

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

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

A dla rozwiązań wbudowanych jest PPC EABI:

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

Ten dokument zawiera dobry przegląd wszystkich różnych konwencji:

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

Peter Teoh
źródło
Zupełnie poza tematem. Plakat z pytaniem nie prosiłby o konwencję wywoływania 64-bitowych wywołań systemowych w Linuksie, gdyby była taka sama, jak ogólne konwersje ABI.
Albert van der Horst
6

Komentarze do źródeł jądra Linux 5.0

Wiedziałem, że specyfika x86 jest za arch/x86słaba, a rzeczy syscall się nie sprawdzają arch/x86/entry. Tak więc szybkie przejście git grep rdido tego 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 wersji 32-bitowej 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łania systemowego

Teraz oszukujmy, patrząc na główne implementacje libc i zobaczmy, co robią.

Co może być lepszego niż zajrzenie do glibc, którego używam teraz, pisząc tę ​​odpowiedź? :-)

glibc 2.29 definiuje wywołania systemowe x86_64 w sysdeps/unix/sysv/linux/x86_64/sysdep.hi zawiera interesujący 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;                       \
})

które moim zdaniem są dość oczywiste. Zwróć uwagę, jak wydaje się, że zostało to zaprojektowane tak, aby dokładnie pasowało do konwencji wywoływania zwykłych funkcji AMD64 ABI systemu V: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Szybkie przypomnienie o napastnikach:

  • ccoznacza rejestry flag. Ale Peter Cordes komentuje że nie jest to tutaj konieczne.
  • memory oznacza, że ​​wskaźnik może być przekazywany w asemblerze i używany do uzyskiwania dostępu do pamięci

Aby uzyskać wyraźny, minimalny, uruchamialny przykład od podstaw, zobacz odpowiedź: Jak wywołać wywołanie systemowe za pośrednictwem sysenter w asemblerze wbudowanym?

Wykonaj ręcznie wywołania systemowe w asemblerze

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 .

aarch64

Pokazałem minimalny działający przykład przestrzeni użytkownika pod adresem : /reverseengineering/16917/arm64-syscalls-table/18834#18834 TODO kod jądra grep tutaj, powinien być łatwy.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
1
"cc"Clobber jest niepotrzebna: syscalli Linux zapisu / przywrócenia RFLAGS (The syscall/ sysretinstrukcje zrobić za pomocą R11, a jądro nie zmienia zapisany R11 / RFLAGS inny niż poprzez ptracedebugera wywołań systemowych.) Nie, że kiedykolwiek się liczy, bo "cc"clobber jest domyślnie dla x86 / x86-64 w GNU C Extended asm, więc nie możesz nic zyskać, pomijając to.
Peter Cordes