wake-up-neo.com

Wie lauten die Aufrufkonventionen für UNIX- und Linux-Systemaufrufe auf i386 und x86-64?

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

Aber wie lauten die Konventionen für x86-64-Systemaufrufe unter UNIX und Linux?

127
claws

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


Ich habe dies mit GNU Assembler (gas) unter Linux überprüft.

Kernel-Schnittstelle

x86-32 aka i386 Linux-Systemaufruf-Konvention:

In x86-32 werden Parameter für Linux-Systemaufrufe mithilfe von Registern übergeben. %eax für syscall_number. Mit% ebx,% ecx,% edx,% esi,% edi und% ebp werden 6 Parameter an Systemaufrufe übergeben.

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

Ich habe das folgende Snippet aus dem Linux Assembly Tutorial genommen, aber ich bezweifle dies. Wenn jemand ein Beispiel zeigen kann, wäre es großartig.

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

Ein Beispiel und etwas mehr zum Lesen 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: Welche Teile dieses HelloWorld-Assembly-Codes sind wichtig, wenn ich das Programm in Assembly schreiben möchte?

Es gibt eine schnellere Möglichkeit, 32-Bit-Systemaufrufe durchzuführen: Verwenden von sysenter. Der Kernel ordnet jedem Prozess (dem vDSO) eine Speicherseite zu, wobei die User-Space-Seite des Tanzes sysenter mit dem Kernel zusammenarbeiten muss, damit er die Rücksprungadresse finden kann. Das Argument zum Registrieren der Zuordnung ist dasselbe wie für int $0x80. Sie sollten normalerweise das vDSO aufrufen, anstatt direkt sysenter zu verwenden. (Siehe Der definitive Leitfaden für Linux-Systemaufrufe für Informationen zum Verknüpfen und Aufrufen von vDSO und Weitere Informationen zu sysenter und zu allem, was mit Systemaufrufen zu tun hat.)

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

Parameter werden auf dem Stack übergeben. Schieben Sie die Parameter (letzter Parameter zuerst) 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 Systemaufrufanweisung int $0x80

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


x86-64 Linux System Call Konvention:

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

Siehe Abschnitt: "A.2 AMD64 Linux Kernel-Konventionen" von System V Application Binary Interface AMD64 Architecture Processor Supplement . Die neuesten Versionen der i386- und x86-64-System-V-psABIs finden Sie , die auf dieser Seite verlinkt sind, im Repo des ABI-Betreuers . (Siehe auch das x86 Tag-Wiki für aktuelle ABI-Links und viele andere nützliche Informationen zu 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 Anweisung syscall. Dieses löscht% rcx und% r11 sowie den% rax-Rückgabewert, andere Register bleiben jedoch erhalten.
  3. Die Nummer des Syscalls muss in Register% rax übergeben werden.
  4. Systemaufrufe sind auf sechs Argumente beschränkt, es wird kein Argument direkt auf dem Stack übergeben.
  5. Zurück vom Systemaufruf enthält Register% rax das Ergebnis des Systemaufrufs. Ein Wert im Bereich zwischen -4095 und -1 zeigt einen Fehler an, er ist -errno.
  6. Es werden nur Werte der Klasse INTEGER oder der Klasse MEMORY 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 $0x80 ABI ist verwendbar in 64-Bit-Code (wird jedoch dringend empfohlen). Was passiert, wenn Sie das 32-Bit-Int 0x80-Linux-ABI in 64-Bit-Code verwenden? Die Eingaben werden weiterhin auf 32-Bit gekürzt, sodass es für Zeiger ungeeignet ist und Nullen enthält r8-r11.

Benutzeroberfläche: Funktionsaufruf

x86-32-Funktionsaufrufkonvention:

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

Moderne Versionen von i386 System V ABI (unter Linux) erfordern eine 16-Byte-Ausrichtung von %esp vor einem call, wie es das x86-64 System V ABI immer benötigt hat. Callees dürfen das annehmen und SSE 16-Byte-Ladevorgänge/-Speicher, die fehlerhaft sind, unausgerichtet verwenden. In der Vergangenheit erforderte Linux jedoch nur eine 4-Byte-Stapelausrichtung, so dass es zusätzlichen Aufwand erforderte, natürlich zu reservieren. Ausgerichteter Platz sogar für ein 8-Byte double oder so.

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


x86-64-System-V-User-Space-Funktionsaufrufkonvention:

x86-64-System V übergibt Args in Registern, was effizienter ist als die Stack-Args-Konvention von i386-System V. Es vermeidet die Latenz und zusätzliche Anweisungen zum Speichern von Args im Speicher (Cache) und zum anschließenden erneuten Laden in den Callee. Dies funktioniert gut, da mehr Register verfügbar sind. Dies ist besser für moderne Hochleistungs-CPUs, bei denen Latenz und Ausführung außerhalb der Reihenfolge von Bedeutung sind. (Der i386 ABI ist sehr alt).

In diesem new Mechanismus werden zunächst 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" in System V Application Binary Interface AMD64 Architecture Processor Supplement , das zum Teil Folgendes enthält:

Sobald die Argumente klassifiziert sind, werden die Register (in der Reihenfolge von links nach rechts) wie folgt übergeben:

  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

So %rdi, %rsi, %rdx, %rcx, %r8 and %r9 sind die Register in der Reihenfolge , die verwendet werden, um Integer-/Zeigerparameter (d. h. INTEGER-Klasse) an eine beliebige libc-Funktion von Assembly zu übergeben. % rdi wird für den ersten INTEGER-Parameter verwendet. % rsi für 2.,% rdx für 3. und so weiter. Dann sollte die Anweisung call gegeben werden. Der Stapel (%rsp) muss 16B-ausgerichtet sein, wenn call ausgeführt wird.

Wenn mehr als 6 INTEGER-Parameter vorhanden sind, werden die 7. INTEGER-Parameter und höher auf dem Stapel übergeben. (Anrufer erscheint wie bei x86-32.)

Die ersten 8 Gleitkomma-Args werden später auf dem Stapel in% xmm0-7 übergeben. Es gibt keine aufruferhaltenden Vektorregister. (Eine Funktion mit einer Mischung aus FP und Ganzzahlargumenten kann mehr als 8 Registerargumente enthalten.)

Variadische Funktionen ( wie printf ) benötigen immer %al = die Anzahl von FP register args.

Es gibt Regeln für das Packen von Strukturen in Register (rdx:rax bei Rückkehr) vs. im Speicher. 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, wie z. B. Schattenspeicher, der muss reserviert sein vom Anrufer (anstelle einer roten Zone) und vom Anrufer beibehaltenes xmm6-xmm15. Und ganz andere Regeln, für welche Argumente in welchem ​​Register steht.

202
claws

Vielleicht suchen Sie nach dem x86_64 ABI?

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

15

Aufrufkonventionen definieren, wie Parameter in den Registern beim Aufrufen oder Aufrufen durch ein anderes Programm übergeben werden. Die beste Quelle für diese Konvention sind die für jede dieser Hardware definierten ABI-Standards. Zur Vereinfachung der Kompilierung wird dieselbe ABI auch von Userspace- und Kernel-Programmen verwendet. Linux/Freebsd folgen dem gleichen ABI für x86-64 und einem anderen Set 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 für Linux-Benutzer und -Kernel gleich ist: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on -x86-64 / (notiere die Reihenfolge a, b, c, d, e, f der Parameter):

A good rendering of calling conventions vs registers usage

Die Leistung ist einer der Gründe für diese ABI (z. B. Übergabe von Parametern über Register statt Speichern in Speicherstapeln).

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 eingebettete gibt es die 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

11
Peter Teoh

Quellkommentare zum Linux-Kernel 5.0

Ich wusste, dass die x86-Details unter Arch/x86, und das syscall Zeug geht unter Arch/x86/entry. Also ein kurzes git grep rdi in diesem 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 einen Blick auf die wichtigsten libc-Implementierungen werfen und sehen, was sie tun.

Was gibt es Schöneres, als sich mit glibc auseinanderzusetzen, das ich gerade verwende, wenn ich diese Antwort schreibe? :-)

glibc 2.29 definiert x86_64-Systemaufrufe unter sysdeps/unix/sysv/linux/x86_64/sysdep.h und das enthält einen 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;                       \
})

das finde ich ziemlich selbsterklärend. Beachten Sie, wie dies genau auf die Aufrufkonvention der regulären ABI-Funktionen von System V AMD64 abgestimmt zu sein scheint: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Schnelle Erinnerung an die Clobbers:

  • cc bedeutet Flagregister. Aber Peter Cordes kommentiert das ist hier nicht nötig.
  • memory bedeutet, dass ein Zeiger in Assembly übergeben und für den Zugriff auf den Speicher verwendet werden kann

Ein explizites Beispiel für eine minimale Ausführungsrate von Grund auf finden Sie in dieser Antwort: Aufrufen eines Systemaufrufs über sysenter in der Inline-Assembly?

Machen Sie einige Systemaufrufe in Assembly manuell

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

aarch64

Ich habe ein Beispiel für ein minimal ausführbares Userland gezeigt: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep Kernel Code sollte hier einfach sein .