Quelles sont les conventions d'appel pour les appels système UNIX et Linux sur i386 et x86-64

147

Les liens suivants expliquent les conventions d'appel système x86-32 pour UNIX (saveur BSD) et Linux:

Mais quelles sont les conventions d'appel système x86-64 sous UNIX et Linux?

les griffes
la source
Il n'y a pas de "standard" pour les conventions d'appel Unix. Pour Linux, bien sûr, mais je suis sûr que Solaris, OpenBSD, Linux et Minix ont probablement des conventions d'appel différentes au moins légèrement différentes et elles sont toutes unix.
Earlz
2
Ce n'est pas tout à fait vrai - il existe un ensemble d'ABI UNIX disponibles pour la plupart des types de machines, ce qui permet aux compilateurs C de réaliser l'interopérabilité. Les compilateurs C ++ ont un plus gros problème.
Jonathan Leffler
1
Vous avez tous les deux raison. Je recherche FreeBSD et Linux.
griffes
J'apprécierais que la réponse contienne des informations sur les registres qui sont conservés dans les appels système. Bien sûr, le pointeur de pile est, (sauf s'il est changé de manière contrôlée dans l'appel __NR_clone), mais sont-ils les autres?
Albert van der Horst
@AlbertvanderHorst: oui, je viens de mettre à jour la réponse du wiki avec les détails pour 32 bits. 64 bits était déjà précis: rcx et r11 sont détruits à cause de la façon dont sysretfonctionne, et rax est remplacé par la valeur de retour. Tous les autres registres sont conservés sur amd64.
Peter Cordes

Réponses:

230

Lectures complémentaires pour l'un des sujets ici: Le guide définitif des appels système Linux


J'ai vérifié ces derniers en utilisant GNU Assembler (gas) sous Linux.

Interface du noyau

Convention d'appel système Linux x86-32 aka i386:

Dans x86-32, les paramètres pour l'appel système Linux sont passés à l'aide de registres. %eaxpour syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp sont utilisés pour passer 6 paramètres aux appels système.

La valeur de retour est dans %eax. Tous les autres registres (y compris EFLAGS) sont conservés dans le int $0x80.

J'ai pris l'extrait suivant du tutoriel d'assemblage Linux, mais j'en doute. Si quelqu'un peut montrer un exemple, ce serait formidable.

S'il y a plus de six arguments, %ebxdoit contenir l'emplacement mémoire où la liste des arguments est stockée - mais ne vous inquiétez pas à ce sujet car il est peu probable que vous utilisiez un appel système avec plus de six arguments.

Pour un exemple et un peu plus de lecture, reportez-vous à http://www.int80h.org/bsdasm/#alternate-calling-convention . Un autre exemple de Hello World pour i386 Linux utilisant int 0x80: Hello, world en langage assembleur avec des appels système Linux?

Il existe un moyen plus rapide de passer des appels système 32 bits: utiliser sysenter. Le noyau mappe une page de mémoire dans chaque processus (le vDSO), avec le côté espace utilisateur de la sysenterdanse, qui doit coopérer avec le noyau pour qu'il puisse trouver l'adresse de retour. Arg pour enregistrer le mappage est le même que pour int $0x80. Vous devez normalement appeler le vDSO au lieu d'utiliser sysenterdirectement. (Voir The Definitive Guide to Linux System Calls pour plus d'informations sur la liaison et l'appel dans le vDSO, et pour plus d'informations sur sysenter, et tout ce qui concerne les appels système.)

x86-32 [Free | Open | Net | DragonFly] Convention d'appel système BSD UNIX:

Les paramètres sont passés sur la pile. Poussez les paramètres (le dernier paramètre poussé en premier) sur la pile. Ensuite, envoyez 32 bits supplémentaires de données factices (ce ne sont pas réellement des données factices. Reportez-vous au lien suivant pour plus d'informations), puis donnez une instruction d'appel systèmeint $0x80

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


Convention d'appel système Linux x86-64:

x86-64 Mac OS X est similaire mais différent . TODO: vérifiez ce que fait * BSD.

Reportez - vous à la section: "A.2 AMD64 Linux Conventions du noyau" du système binaire d' application V Interface Architecture AMD64 Supplément processeur . Les dernières versions des psABI i386 et x86-64 System V peuvent être trouvées liées à partir de cette page dans le repo du responsable ABI . (Voir aussi le tag wiki pour les liens ABI à jour et beaucoup d'autres bonnes choses sur x86 asm.)

Voici l'extrait de cette section:

  1. Les applications de niveau utilisateur utilisent comme registres d'entiers pour transmettre la séquence% rdi,% rsi,% rdx,% rcx,% r8 et% r9. L'interface du noyau utilise% rdi,% rsi,% rdx,% r10,% r8 et% r9.
  2. Un appel système se fait via l' syscallinstruction . Cela supprime% rcx et% r11 ainsi que la valeur de retour% rax, mais les autres registres sont conservés.
  3. Le numéro de l'appel système doit être passé dans le registre% rax.
  4. Les appels système sont limités à six arguments, aucun argument n'est passé directement sur la pile.
  5. En revenant de l'appel système, le registre% rax contient le résultat de l'appel système. Une valeur comprise entre -4095 et -1 indique une erreur, c'est le cas -errno.
  6. Seules les valeurs de la classe INTEGER ou de la classe MEMORY sont transmises au noyau.

Rappelez-vous que cela provient de l'annexe spécifique à Linux de l'ABI, et même pour Linux, c'est informatif et non normatif. (Mais c'est en fait exact.)

Cet int $0x80ABI 32 bits est utilisable en code 64 bits (mais fortement déconseillé). Que se passe-t-il si vous utilisez l'ABI Linux int 0x80 32 bits dans un code 64 bits? Il tronque toujours ses entrées en 32 bits, il ne convient donc pas aux pointeurs et met à zéro r8-r11.

Interface utilisateur: appel de fonction

Convention d'appel de fonction x86-32:

Dans x86-32, les paramètres étaient passés sur la pile. Le dernier paramètre a été poussé en premier sur la pile jusqu'à ce que tous les paramètres soient terminés, puis l' callinstruction a été exécutée. Ceci est utilisé pour appeler les fonctions de la bibliothèque C (libc) sur Linux à partir de l'assembly.

Les versions modernes de l'ABI i386 System V (utilisé sous Linux) nécessitent un alignement de 16 octets %espavant a call, comme l'ABI System V x86-64 l'a toujours exigé. Les appelées sont autorisés à supposer que et à utiliser les charges / stockages SSE 16 octets qui échouent sur non aligné. Mais historiquement, Linux ne nécessitait qu'un alignement de pile de 4 octets, il fallait donc un travail supplémentaire pour réserver un espace naturellement aligné, même pour un 8 octets doubleou quelque chose du genre.

Certains autres systèmes 32 bits modernes ne nécessitent toujours pas d'alignement de pile de plus de 4 octets.


x86-64 System V espace utilisateur Fonction Convention d'appel:

x86-64 System V passe les arguments dans les registres, ce qui est plus efficace que la convention d'arguments de pile d'i386 System V. Cela évite la latence et les instructions supplémentaires de stockage des arguments en mémoire (cache), puis de les charger à nouveau dans l'appelé. Cela fonctionne bien car il y a plus de registres disponibles et c'est mieux pour les processeurs modernes hautes performances où la latence et l'exécution dans le désordre sont importantes. (Le i386 ABI est très ancien).

Dans ce nouveau mécanisme: Premièrement, les paramètres sont divisés en classes. La classe de chaque paramètre détermine la manière dont il est passé à la fonction appelée.

Pour des informations complètes, reportez-vous à: "3.2 Function Calling Sequence" du supplément de processeur d'architecture AMD64 de l'interface binaire d'application System V qui lit, en partie:

Une fois les arguments classés, les registres sont affectés (dans l'ordre de gauche à droite) pour être transmis comme suit:

  1. Si la classe est MEMORY, passez l'argument sur la pile.
  2. Si la classe est INTEGER, le prochain registre disponible de la séquence% rdi,% rsi,% rdx,% rcx,% r8 et% r9 est utilisé

Ainsi , %rdi, %rsi, %rdx, %rcx, %r8 and %r9les registres de commande utilisés pour transmettre entier / pointeur paramètres ( par exemple classe INTEGER) à toute fonction libc de montage. % rdi est utilisé pour le premier paramètre INTEGER. % rsi pour le 2ème,% rdx pour le 3ème et ainsi de suite. Ensuite, des callinstructions devraient être données. La pile ( %rsp) doit être alignée sur 16B lors de l' callexécution.

S'il y a plus de 6 paramètres INTEGER, le 7ème paramètre INTEGER et les suivants sont passés sur la pile. (L'appelant apparaît, identique à x86-32.)

Les 8 premiers arguments à virgule flottante sont passés dans% xmm0-7, plus tard sur la pile. Il n'y a pas de registres vectoriels préservés des appels. (Une fonction avec un mélange d'arguments FP et entiers peut avoir plus de 8 arguments de registre au total.)

Les fonctions variadiques ( commeprintf ) ont toujours besoin %aldu nombre d'arguments du registre FP.

Il existe des règles pour conditionner les structures dans des registres ( rdx:raxau retour) ou en mémoire. Consultez l'ABI pour plus de détails et vérifiez la sortie du compilateur pour vous assurer que votre code est d'accord avec les compilateurs sur la manière dont quelque chose doit être transmis / retourné.


Notez que la convention d'appel de la fonction Windows x64 présente plusieurs différences significatives par rapport au système x86-64 System V, comme l'espace d'ombre qui doit être réservé par l'appelant (au lieu d'une zone rouge) et l'appel préservé xmm6-xmm15. Et des règles très différentes pour lesquelles arg va dans quel registre.

griffes
la source
1
Sous Linux 32 "tous les registres sauf ax bx cd dx si di bp sont préservés". Je ne peux penser à aucun ...
Albert van der Horst
Sur amd64, s'il y a plus de 6 paramètres et qu'ils sont passés sur la pile, qui est responsable du nettoyage de la pile après l'appel, l'appelant ou l'appelé?
Nicolás
1
@ Nicolás: l'appelant nettoie la pile. J'ai mis à jour la réponse avec plus de détails sur la convention d'appel de fonction.
Peter Cordes
1
Si vous utilisez l' int 0x80ABI de Linux dans un code 64 bits, c'est exactement ce qui se passe: stackoverflow.com/questions/46087730/… . Il met à zéro r8-r11 et fonctionne exactement comme lorsqu'il est exécuté dans un processus 32 bits. Dans ce Q&A, j'ai un exemple montrant qu'il fonctionne ou échoue avec la troncature d'un pointeur. Et j'ai aussi fouillé dans la source du noyau pour montrer pourquoi il se comporte de cette façon.
Peter Cordes
1
@EvanCarroll: L'extrait (texte cité) est sur le lien donné Tutoriel d'assemblage Linux spécifiquement dans la section 4.3 Appels système Linux
Michael Petch
14

Peut-être cherchez-vous l'ABI x86_64?

Si ce n'est pas exactement ce que vous recherchez, utilisez 'x86_64 abi' dans votre moteur de recherche préféré pour trouver des références alternatives.

Jonathan Leffler
la source
5
en fait, je ne veux que la convention d'appel système. esp pour UNIX (FreeBSD)
griffes
3
@claws: la convention d'appel système est une partie de l'ABI.
Jonathan Leffler
1
Ouais. Je suis allé à l'irc de développement du noyau de chaque OS et je leur ai posé des questions à ce sujet. Ils m'ont dit de rechercher la source et de comprendre. Je ne comprends pas sans documenter les choses comment peuvent-ils commencer à se développer? J'ai donc ajouté une réponse à partir des informations que j'ai collectées, dans l'espoir que d'autres remplissent le reste des détails.
griffes
@JonathanLeffler, le lien semble ne pas fonctionner pour le moment. Si vous rencontrez également un problème en visitant le lien, pouvez-vous le mettre à jour?
Ajay Brahmakshatriya
@AjayBrahmakshatriya: Merci pour la mise en garde; J'ai ajouté un lien vers l'enregistrement Wayback Machine. L'ensemble du site Web x86-64.org n'a répondu avec aucune donnée.
Jonathan Leffler
11

Les conventions d'appel définissent la manière dont les paramètres sont passés dans les registres lors de l'appel ou de l'appel d'un autre programme. Et la meilleure source de ces conventions se trouve sous la forme de normes ABI définies pour chacun de ces matériels. Pour faciliter la compilation, le même ABI est également utilisé par l'espace utilisateur et le programme du noyau. Linux / Freebsd suivent le même ABI pour x86-64 et un autre ensemble pour 32 bits. Mais x86-64 ABI pour Windows est différent de Linux / FreeBSD. Et généralement ABI ne différencie pas les appels système des "appels de fonctions" normaux. C'est à dire, voici un exemple particulier de conventions d'appel x86_64 et il en va de même pour l'espace utilisateur Linux et le noyau: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 / (notez la séquence a, b, c, d, e, f des paramètres):

Un bon rendu des conventions d'appel par rapport à l'utilisation des registres

Les performances sont l'une des raisons de ces ABI (par exemple, passer des paramètres via des registres au lieu de les enregistrer dans des piles de mémoire)

Pour ARM, il existe différents 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

Convention ARM64:

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

Pour Linux sur PowerPC:

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

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

Et pour embarqué, il y a le PPC EABI:

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

Ce document est un bon aperçu de toutes les différentes conventions:

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

Peter Teoh
la source
Complètement à part le point. L'auteur de la question ne demanderait pas la convention d'appel syscall 64 bits sous Linux si elle était la même que les conversions ABI générales.
Albert van der Horst
6

Commentaires sur les sources du noyau Linux 5.0

Je savais que les spécificités de x86 sont sous arch/x86, et que les trucs syscall passent sous arch/x86/entry. Donc un rapide git grep rdidans ce répertoire m'amène à 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.
 */

et pour 32 bits à 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 implémentation des appels système Linux x86_64

Trichons maintenant en examinant les implémentations majeures de la libc et voyons ce qu'elles font.

Quoi de mieux que de regarder dans la glibc que j'utilise actuellement au moment où j'écris cette réponse? :-)

la glibc 2.29 définit les appels système x86_64 à sysdeps/unix/sysv/linux/x86_64/sysdep.het qui contient du code intéressant, par exemple:

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

et:

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

qui, à mon avis, sont assez explicites. Notez comment cela semble avoir été conçu pour correspondre exactement à la convention d'appel des fonctions ABI AMD64 standard du système V: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

Rappel rapide des clobbers:

  • ccsignifie registres de drapeaux. Mais Peter Cordes commente que ce n'est pas nécessaire ici.
  • memory signifie qu'un pointeur peut être passé dans l'assembly et utilisé pour accéder à la mémoire

Pour un exemple explicite minimal exécutable à partir de zéro, consultez cette réponse: Comment invoquer un appel système via sysenter dans l'assembly en ligne?

Effectuer manuellement des appels système dans l'assemblage

Pas très scientifique, mais amusant:

  • 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 en amont .

aarch64

J'ai montré un exemple utilisateur minimal exécutable à l' adresse : /reverseengineering/16917/arm64-syscalls-table/18834#18834 Le code du noyau grep TODO ici, devrait être facile.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
1
le "cc" clobber n'est pas nécessaire: les appels système Linux sauvegardent / restaurent RFLAGS (Les instructions syscall/ le sysretfont en utilisant R11, et le noyau ne modifie pas le R11 / RFLAGS enregistré autrement que via des ptraceappels système du débogueur.) Pas que cela compte jamais, car un "cc"clobber est implicite pour x86 / x86-64 dans GNU C Extended asm, vous ne pouvez donc rien gagner en l'omettant.
Peter Cordes