Was sind die Aufrufkonventionen für UNIX- und Linux-Systemaufrufe auf i386 und x86-64?

146

Die folgenden Links erläutern die x86-32-Systemaufrufkonventionen für UNIX (BSD-Version) und Linux:

Aber wie lauten die x86-64-Systemaufrufkonventionen unter UNIX und Linux?

Krallen
quelle
Es gibt keinen "Standard" für Unix-Aufrufkonventionen. Sicher für Linux, aber ich bin sicher, dass Solaris, OpenBSD, Linux und Minix wahrscheinlich unterschiedliche, zumindest geringfügig unterschiedliche Aufrufkonventionen haben und alle Unix sind.
Earlz
2
Das ist nicht ganz richtig - für die meisten Maschinentypen steht eine Reihe von UNIX-ABIs zur Verfügung, mit denen C-Compiler Interoperabilität erreichen können. C ++ - Compiler haben ein größeres Problem.
Jonathan Leffler
1
Sie beide sind richtig. Ich suche FreeBSD & Linux.
Krallen
Ich würde mich freuen, wenn die Antwort Informationen darüber enthält, welche Register bei Systemaufrufen erhalten bleiben. Natürlich ist der Stapelzeiger (sofern er nicht im __NR_clone-Aufruf kontrolliert geändert wird), aber sind es die anderen?
Albert van der Horst
@ AlbertvanderHorst: Ja, ich habe gerade die Wiki-Antwort mit den Details für 32bit aktualisiert. 64bit war bereits korrekt: rcx und r11 werden aufgrund der Funktionsweise zerstört, und rax sysretwird durch den Rückgabewert ersetzt. Alle anderen Register bleiben auf amd64 erhalten.
Peter Cordes

Antworten:

228

Weitere Informationen zu den folgenden Themen finden Sie hier: Der endgültige Leitfaden für Linux-Systemaufrufe


Ich habe diese mit GNU Assembler (Gas) unter Linux überprüft.

Kernel-Schnittstelle

x86-32 aka i386 Linux Systemaufrufkonvention:

In x86-32 werden Parameter für Linux-Systemaufrufe mithilfe von Registern übergeben. %eaxfür syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp werden zum Übergeben von 6 Parametern an Systemaufrufe verwendet.

Der Rückgabewert ist in %eax. Alle anderen Register (einschließlich EFLAGS) werden in der gesamten Region beibehalten int $0x80.

Ich nahm folgenden Ausschnitt aus dem Linux Assembly Tutorial genommen , bin mir aber nicht sicher. Wenn jemand ein Beispiel zeigen kann, wäre es großartig.

Wenn es mehr als sechs Argumente gibt, %ebx muss der Speicherort enthalten sein, in dem die Liste der Argumente gespeichert ist. Machen Sie sich darüber jedoch keine Sorgen, da es unwahrscheinlich ist, dass Sie einen Systemaufruf mit mehr als sechs Argumenten verwenden.

Ein Beispiel und etwas mehr Informationen finden Sie unter http://www.int80h.org/bsdasm/#alternate-calling-convention . Ein weiteres Beispiel für eine Hello World für i386 Linux mit int 0x80: Hello, Welt in Assemblersprache mit Linux-Systemaufrufen?

Es gibt eine schnellere Möglichkeit, 32-Bit-Systemaufrufe durchzuführen: using sysenter. Der Kernel ordnet jedem Prozess (dem vDSO) eine Speicherseite mit der User-Space-Seite des sysenterTanzes zu, die mit dem Kernel zusammenarbeiten muss, damit er die Rücksprungadresse finden kann. Die Zuordnung von Arg zu Register ist dieselbe wie für int $0x80. Normalerweise sollten Sie das vDSO aufrufen, anstatt es sysenterdirekt zu verwenden. (Weitere Informationen zum Verknüpfen und Aufrufen von vDSO sowie weitere Informationen zu und alles andere, was mit Systemaufrufen zu tun hat, finden Sie im Definitiven Handbuch zu Linux-sysenter Systemaufrufen.)

x86-32 [Free | Open | Net | DragonFly] BSD UNIX-Systemaufrufkonvention:

Parameter werden auf dem Stapel übergeben. Schieben Sie die Parameter (letzter Parameter zuerst gedrückt) auf den Stapel. Drücken Sie dann weitere 32-Bit-Dummy-Daten (es handelt sich nicht um Dummy-Daten. Weitere Informationen finden Sie unter folgendem Link) und geben Sie dann eine Systemaufrufanweisungint $0x80

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


x86-64 Linux Systemaufrufkonvention:

x86-64 Mac OS X ist ähnlich, aber unterschiedlich . TODO: Überprüfen Sie, was * BSD tut.

Siehe Abschnitt: "A.2 AMD64- Linux- Kernelkonventionen" der System V-Anwendungsbinärschnittstelle AMD64 Architecture Processor Supplement . Die neuesten Versionen der psABIs von i386 und x86-64 System V finden Sie auf dieser Seite im Repo des ABI-Betreuers . (Siehe auch die Tag-Wiki für aktuelle ABI-Links und viele andere gute Dinge über x86 asm.)

Hier ist der Ausschnitt aus diesem Abschnitt:

  1. Anwendungen auf Benutzerebene werden als Ganzzahlregister zum Übergeben der Sequenzen% rdi,% rsi,% rdx,% rcx,% r8 und% r9 verwendet. Die Kernel-Schnittstelle verwendet% rdi,% rsi,% rdx,% r10,% r8 und% r9.
  2. Ein Systemaufruf erfolgt über die syscallAnweisung . Dadurch werden% rcx und% r11 sowie der Rückgabewert% rax blockiert , andere Register bleiben jedoch erhalten.
  3. Die Nummer des Systemaufrufs muss im Register% rax übergeben werden.
  4. Systemaufrufe sind auf sechs Argumente beschränkt, es wird kein Argument direkt an den Stapel übergeben.
  5. Zurück vom Systemaufruf enthält das Register% rax das Ergebnis des Systemaufrufs. Ein Wert im Bereich zwischen -4095 und -1 zeigt einen Fehler an -errno.
  6. Nur Werte der Klasse INTEGER oder der Klasse MEMORY werden an den Kernel übergeben.

Denken Sie daran, dass dies aus dem Linux-spezifischen Anhang zum ABI stammt und selbst für Linux informativ und nicht normativ ist. (Aber es ist tatsächlich genau.)

Dieses 32-Bit - int $0x80ABI ist verwendbar in 64-Bit - Code (aber sehr nicht empfohlen). Was passiert, wenn Sie das 32-Bit-Linux-ABI int 0x80 in 64-Bit-Code verwenden? Es schneidet seine Eingaben immer noch auf 32-Bit ab, ist also für Zeiger ungeeignet und setzt r8-r11 auf Null.

Benutzeroberfläche: Funktionsaufruf

x86-32 Funktionsaufrufkonvention:

In x86-32 wurden Parameter auf Stack übergeben. Der letzte Parameter wurde zuerst auf den Stapel geschoben, bis alle Parameter fertig sind, und dann wurde die callAnweisung ausgeführt. Dies wird zum Aufrufen von Funktionen der C-Bibliothek (libc) unter Linux aus der Assembly verwendet.

Moderne Versionen des i386 System V ABI (unter Linux verwendet) erfordern eine 16-Byte-Ausrichtung %espvor a call, wie dies beim x86-64 System V ABI immer erforderlich war. Callees dürfen davon ausgehen und SSE-16-Byte-Ladevorgänge / -Speicher verwenden, die diesen Fehler bei nicht ausgerichteter Ausführung verursachen. In der Vergangenheit erforderte Linux jedoch nur eine 4-Byte-Stapelausrichtung, sodass zusätzliche Arbeit erforderlich war, um natürlich ausgerichteten Speicherplatz selbst für 8 Byte doubleoder ähnliches zu reservieren .

Einige andere moderne 32-Bit-Systeme erfordern immer noch nicht mehr als 4-Byte-Stapelausrichtung.


x86-64 System V User-Space Funktion Aufrufkonvention:

x86-64 System V übergibt Argumente in Registern, was effizienter ist als die Stapelargument-Konvention von i386 System V. Es vermeidet die Latenz und zusätzliche Anweisungen, Argumente im Speicher (Cache) zu speichern und sie dann wieder in den Angerufenen zu laden. Dies funktioniert gut, da mehr Register verfügbar sind, und ist besser für moderne Hochleistungs-CPUs geeignet, bei denen Latenz und Ausführung außerhalb der Reihenfolge von Bedeutung sind. (Der i386 ABI ist sehr alt).

In diesem neuen Mechanismus: Zuerst werden die Parameter in Klassen unterteilt. Die Klasse jedes Parameters bestimmt, wie er an die aufgerufene Funktion übergeben wird.

Vollständige Informationen finden Sie unter: "3.2 Funktionsaufrufsequenz" des AMD64-Architekturprozessor-Supplements der System V-Anwendungsbinärschnittstelle, das teilweise lautet:

Sobald die Argumente klassifiziert sind, werden die Register (von links nach rechts) für die Übergabe wie folgt zugewiesen:

  1. Wenn die Klasse MEMORY ist, übergeben Sie das Argument auf dem Stapel.
  2. Wenn die Klasse INTEGER ist, wird das nächste verfügbare Register der Sequenz% rdi,% rsi,% rdx,% rcx,% r8 und% r9 verwendet

Dies %rdi, %rsi, %rdx, %rcx, %r8 and %r9gilt auch für die Register , die verwendet werden, um Ganzzahl- / Zeigerparameter (dh die INTEGER-Klasse) von einer Assembly an eine beliebige libc-Funktion zu übergeben. % rdi wird für den ersten INTEGER-Parameter verwendet. % rsi für 2.,% rdx für 3. und so weiter. Dann callsollte eine Anweisung gegeben werden. Der Stack ( %rsp) muss bei der Ausführung 16B-ausgerichtet sein call.

Wenn mehr als 6 INTEGER-Parameter vorhanden sind, werden der 7. INTEGER-Parameter und höher an den Stapel übergeben. (Anrufer knallt wie x86-32.)

Die ersten 8 Gleitkomma-Argumente werden später im Stapel in% xmm0-7 übergeben. Es gibt keine aufruferhaltenen Vektorregister. (Eine Funktion mit einer Mischung aus FP- und Integer-Argumenten kann insgesamt mehr als 8 Registerargumente enthalten.)

Variadische Funktionen ( wieprintf ) benötigen immer %al= die Anzahl der FP-Registerargumente.

Es gibt Regeln, wann Strukturen in Register ( rdx:raxbei Rückgabe) oder im Speicher gepackt werden sollen . Weitere Informationen finden Sie im ABI. Überprüfen Sie die Compilerausgabe, um sicherzustellen, dass Ihr Code mit den Compilern übereinstimmt, wie etwas übergeben / zurückgegeben werden soll.


Beachten Sie, dass die Windows x64-Funktionsaufrufkonvention mehrere signifikante Unterschiede zu x86-64 System V aufweist, z. B. Schattenbereich, der vom Aufrufer reserviert werden muss (anstelle einer roten Zone), und aufruferhaltenes xmm6-xmm15. Und ganz andere Regeln, für welche Argumente in welches Register gehen.

Krallen
quelle
1
In Linux 32 "bleiben alle Register außer ax bx cd dx si di bp erhalten". Ich kann mir keine vorstellen ...
Albert van der Horst
Wenn auf amd64 mehr als 6 Parameter vorhanden sind und diese an den Stapel übergeben werden, wer ist dann für die Bereinigung des Stapels nach dem Anruf, Anrufer oder Angerufenen verantwortlich?
Nicolás
1
@ Nicolás: Anrufer bereinigt den Stapel. Ich habe die Antwort mit weiteren Details zur Funktionsaufrufkonvention aktualisiert.
Peter Cordes
1
Wenn Sie Linux int 0x80ABI in 64-Bit-Code verwenden, geschieht genau dies: stackoverflow.com/questions/46087730/… . Es setzt r8-r11 auf Null und funktioniert genau so, als würde es in einem 32-Bit-Prozess ausgeführt. In diesen Fragen und Antworten habe ich ein Beispiel, das zeigt, wie es funktioniert oder wie das Abschneiden eines Zeigers fehlschlägt. Und ich habe mich auch in die Kernelquelle vertieft, um zu zeigen, warum sie sich so verhält.
Peter Cordes
1
@EvanCarroll: Das Snippet (zitierter Text) befindet sich unter dem Link zum Linux Assembly Tutorial speziell in Abschnitt 4.3 Linux Systemaufrufe
Michael Petch
14

Vielleicht suchen Sie den x86_64 ABI?

Wenn Sie nicht genau danach suchen, verwenden Sie 'x86_64 abi' in Ihrer bevorzugten Suchmaschine, um alternative Referenzen zu finden.

Jonathan Leffler
quelle
5
Eigentlich möchte ich nur die Systemaufruf-Konvention. besonders für UNIX (FreeBSD)
Krallen
3
@claws: Die Systemaufrufkonvention ist ein Teil des ABI.
Jonathan Leffler
1
Ja. Ich bin zum Kernel Development IRC jedes einzelnen Betriebssystems gegangen und habe sie danach gefragt. Sie haben mir gesagt, ich soll in die Quelle schauen und es herausfinden. Ich verstehe nicht, ohne Dinge zu dokumentieren, wie können sie einfach anfangen, sich zu entwickeln? Also habe ich eine Antwort von den Informationen hinzugefügt, die ich gesammelt habe, in der Hoffnung, dass andere den Rest der Details ausfüllen.
Krallen
@ JonathanLeffler Der Link scheint momentan nicht zu funktionieren. Wenn Sie auch ein Problem beim Besuch des Links haben, können Sie es bitte aktualisieren?
Ajay Brahmakshatriya
@ AjayBrahmakshatriya: Danke für das Heads Up; Ich habe einen Link zum Wayback Machine-Datensatz hinzugefügt. Die gesamte x86-64.org -Website antwortete nicht mit Daten.
Jonathan Leffler
11

Aufrufkonventionen definieren, wie Parameter in den Registern übergeben werden, wenn sie von einem anderen Programm aufgerufen oder aufgerufen werden. Die beste Quelle für diese Konvention sind ABI-Standards, die für jede dieser Hardware definiert sind. Zur Vereinfachung der Kompilierung wird derselbe ABI auch von Userspace und Kernel-Programm verwendet. Linux / Freebsd folgen demselben ABI für x86-64 und einem anderen Satz für 32-Bit. X86-64 ABI für Windows unterscheidet sich jedoch von Linux / FreeBSD. Und im Allgemeinen unterscheidet ABI Systemaufrufe nicht von normalen "Funktionsaufrufen". Das heißt, hier ist ein spezielles Beispiel für x86_64-Aufrufkonventionen, das sowohl für den Linux-Benutzerbereich als auch für den Kernel gleich ist: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (Beachten Sie die Reihenfolge a, b, c, d, e, f der Parameter):

Eine gute Darstellung der Aufrufkonventionen im Vergleich zur Registerverwendung

Die Leistung ist einer der Gründe für diese ABI (z. B. Übergeben von Parametern über Register, anstatt in Speicherstapeln zu speichern).

Für ARM gibt es verschiedene 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

ARM64-Konvention:

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

Für Linux auf PowerPC:

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

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

Und für Embedded gibt es den PPC EABI:

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

Dieses Dokument bietet einen guten Überblick über die verschiedenen Konventionen:

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

Peter Teoh
quelle
Ganz nebenbei. Das Poster der Frage würde nicht nach der 64-Bit-Syscall-Aufrufkonvention unter Linux fragen, wenn sie mit den allgemeinen ABI-Konvertierungen identisch wäre.
Albert van der Horst
6

Linux Kernel 5.0 Quellkommentare

Ich wusste, dass x86-Details unter arch/x86sind und dass Syscall-Sachen untergehen arch/x86/entry. Ein kurzer Blickgit grep rdi in dieses Verzeichnis führt mich zu 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.
 */

und für 32-Bit unter 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
 */

Implementierung von glibc 2.29 Linux x86_64-Systemaufrufen

Lassen Sie uns nun betrügen, indem wir uns die wichtigsten libc-Implementierungen ansehen und sehen, was sie tun.

Was gibt es Schöneres, als in glibc zu schauen, das ich gerade verwende, während ich diese Antwort schreibe? :-)

glibc 2.29 definiert x86_64-Systemaufrufe unter sysdeps/unix/sysv/linux/x86_64/sysdep.hund enthält interessanten Code, z.

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

und:

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

was ich für ziemlich selbsterklärend halte. Beachten Sie, wie dies anscheinend so konzipiert wurde, dass es genau der Aufrufkonvention der regulären System V AMD64 ABI-Funktionen entspricht: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Schnelle Erinnerung an die Clobber:

  • ccbedeutet Flaggenregister. Aber Peter Cordes sagt , dass dies hier nicht erforderlich ist.
  • memory bedeutet, dass ein Zeiger in der Assembly übergeben und für den Zugriff auf den Speicher verwendet werden kann

Ein explizites minimales ausführbares Beispiel von Grund auf finden Sie in dieser Antwort: Wie rufe ich einen Systemaufruf über sysenter in der Inline-Assembly auf?

Führen Sie einige Systemaufrufe in der Baugruppe manuell durch

Nicht sehr wissenschaftlich, aber lustig:

  • 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 stromaufwärts .

aarch64

Ich habe ein Beispiel für ein minimal ausführbares Userland unter /reverseengineering/16917/arm64-syscalls-table/18834#18834 gezeigt. Der TODO-Grep-Kernel-Code sollte hier einfach sein.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1
Der "cc"Clobber ist nicht erforderlich: Linux-Systemaufrufe speichern / stellen RFLAGS wieder her (Die syscall/ -Anweisungen sysrettun dies mit R11, und der Kernel ändert die gespeicherten R11 / RFLAGS nur über ptraceDebugger-Systemaufrufe.) Nicht, dass dies jemals von "cc"Bedeutung ist , da es sich um einen Clobber handelt implizit für x86 / x86-64 in GNU C Extended asm, sodass Sie nichts gewinnen können, wenn Sie es weglassen.
Peter Cordes