Les liens suivants expliquent les conventions d’appel système x86-32 pour UNIX (version BSD) et Linux:
Mais quelles sont les conventions d’appel système x86-64 sous UNIX et Linux?
Pour en savoir plus sur l’un des sujets ici: Guide définitif pour les appels système Linux
J'ai vérifié ceux-ci en utilisant GNU Assembler (gas) sur Linux.
x86-32 aussi nommé convention d'appels système Linux avec i386:
En x86-32, les paramètres pour les appels système Linux sont transmis à l'aide de registres. %eax
Pour syscall_number. % ebx,% ecx,% edx,% esi,% edi,% ebp sont utilisés pour transmettre 6 paramètres à des 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 de Linux mais je doute de cela. Si quelqu'un peut montrer un exemple, ce serait formidable.
S'il y a plus de six arguments,
%ebx
Doit contenir l'emplacement de la mémoire où la liste d'arguments est stockée - ne vous inquiétez pas, 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, consultez http://www.int80h.org/bsdasm/#alternate-calling-convention . Un autre exemple de Hello World pour Linux i386 utilisant int 0x80
: Quelles parties de ce code d'assemblage HelloWorld sont essentielles pour écrire le programme en assembleur?
Il existe un moyen plus rapide de passer des appels système 32 bits: en utilisant sysenter
. Le noyau mappe une page de mémoire dans chaque processus (le vDSO), avec le côté espace utilisateur de la sysenter
dance, qui doit coopérer avec le noyau pour que celui-ci puisse trouver l'adresse de retour. Arg pour enregistrer le mappage est le même que pour int $0x80
. Vous devriez normalement appeler vDSO au lieu d’utiliser sysenter
directement. (Voir Le guide définitif des appels système Linux pour plus d'informations sur la liaison et l'appel au 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 (dernier paramètre en premier) sur la pile. Ensuite, envoyez 32 bits supplémentaires de données factices (il ne s'agit pas de données factices. Pour plus d'informations, reportez-vous au lien suivant), puis donnez une instruction d'appel système int $0x80
http://www.int80h.org/bsdasm/#default-calling-convention
Mac OS X x86-64 est similaire mais différent . TODO: vérifiez ce que * BSD fait.
Reportez-vous à la section: "Conventions de noyau pour A.2 AMD64 Linux " de Supplément de processeur d’architecture d’architecture AMD64 de l’interface binaire System V . Les dernières versions des psABI System V i386 et x86-64 sont disponibles liées à cette page dans le référentiel du responsable ABI . (Voir aussi la balise wiki x86 pour des liens ABI à jour et de nombreuses autres informations utiles sur x86 asm.)
Voici l'extrait de cette section:
- Les applications de niveau utilisateur utilisent comme registres entiers le passage de la séquence% rdi,% rsi,% rdx,% rcx,% r8 et% r9. L'interface du noyau utilise% rdi,% rsi,% rdx,% r10,% r8 et% r9.
- Un appel système est effectué via l'instruction
syscall
. Ceci clobbers% rcx et% r11 ainsi que la valeur de retour% rax, mais les autres registres sont préservés.- Le numéro de l'appel système doit être passé dans le registre% rax.
- Les appels système sont limités à six arguments, aucun argument n'est passé directement sur la pile.
- 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, il s'agit de
-errno
.- Seules les valeurs de classe INTEGER ou de classe MEMORY sont transmises au noyau.
N'oubliez pas que cela provient de l'annexe spécifique à Linux de l'ABI, et même pour Linux, il est informatif et non normatif. (Mais c'est en fait exact.)
Ce int $0x80
ABI 32 bits est utilisable en code 64 bits (mais vivement déconseillé). Que se passe-t-il si vous utilisez ABI Linux 32 bits int 0x80 dans un code 64 bits? Il tronque toujours ses entrées en 32 bits, ce qui ne convient pas aux pointeurs et il supprime les zéros r8-r11.
Fonction d'appel x86-32:
En x86-32, les paramètres ont été transmis sur pile. Le dernier paramètre a d'abord été placé sur la pile jusqu'à ce que tous les paramètres soient définis, puis l'instruction call
a été exécutée. Ceci est utilisé pour appeler les fonctions de la bibliothèque C (libc) sous Linux à partir de Assembly.
Les versions modernes de l'ABI System V i386 (utilisée sous Linux) nécessitent un alignement sur 16 octets de %esp
Avant un call
, comme l'a toujours demandé l'ABI System V x86-64. Les citernes sont autorisées à assumer cela et à utiliser SSE des charges/magasins de 16 octets qui échouent sans alignement. Mais, dans le passé, Linux ne nécessitait qu'un alignement de pile de 4 octets. espace aligné même pour un double
de 8 octets ou quelque chose comme ça.
Certains autres systèmes 32 bits modernes ne nécessitent toujours pas un alignement de pile de plus de 4 octets.
x86-64 System V passe les arguments dans les registres, ce qui est plus efficace que la convention d'arguments de pile de i386 System V. Cela évite la latence et les instructions supplémentaires de stocker les arguments dans la mémoire (cache), puis de les charger à nouveau dans l'appelé. Cela fonctionne bien car il y a plus de registres disponibles et convient mieux aux processeurs modernes hautes performances pour lesquels la latence et l'exécution dans le désordre sont importantes. (L’ABI i386 est très ancien).
Dans ce nouveau mécanisme: D'abord, 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, voir: "3.2 Séquence d’appel de fonction" de supplément au processeur d’architecture pour architecture AMD64 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 passer comme suit:
- Si la classe est MEMORY, passez l'argument sur la pile.
- Si la classe est INTEGER, le prochain registre disponible de la séquence% rdi,% rsi,% rdx,% rcx,% r8 et% r9 est utilisé
Donc %rdi, %rsi, %rdx, %rcx, %r8 and %r9
Sont les registres dans l'ordre utilisés pour transmettre les paramètres entier/pointeur (c'est-à-dire la classe INTEGER) à toute fonction libc de Assembly. % rdi est utilisé pour le premier paramètre INTEGER. % rsi pour le 2e,% rdx pour le 3e et ainsi de suite. Ensuite, l'instruction call
devrait être donnée. La pile (%rsp
) Doit être alignée sur 16B lorsque call
est exécuté.
S'il y a plus de 6 paramètres INTEGER, le 7ème paramètre INTEGER et les versions ultérieures sont transmis à la pile. (Appelant apparaît, identique à x86-32.)
Les 8 premiers arguments en virgule flottante sont passés dans% xmm0-7, plus tard sur la pile. Il n'y a pas de registres de vecteurs préservés des appels. (Une fonction avec un mélange de FP et les arguments entiers peuvent avoir plus de 8 arguments de registre au total.)
Les fonctions variées ( comme printf
) ont toujours besoin de %al
= Le nombre de FP arg arg. Register.
Il y a des règles pour quand insérer les structures dans les registres (rdx:rax
Au retour) vs 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 en accord avec les compilateurs sur la manière dont quelque chose doit être passé/retourné.
Notez que la convention d’appel de la fonction Windows x64 présente de nombreuses différences significatives par rapport au système V x86-64, comme l’espace ombre qui doit être réservé par l’appelant -zone) et préservé des appels xmm6-xmm15. Et des règles très différentes pour lesquelles arg va dans quel registre.
Vous recherchez peut-être le ABI x86_64?
Si ce n'est pas précisément ce que vous recherchez, utilisez "x86_64 abi" dans votre moteur de recherche préféré pour trouver des références alternatives.
Les conventions d'appel définissent la manière dont les paramètres sont transmis dans les registres lors de l'appel ou de l'appelé par un autre programme. Et la meilleure source de ces conventions est 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 noyau. Linux/Freebsd suivent le même ABI pour x86-64 et un autre jeu pour 32 bits. Mais ABI x86-64 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. Voici un exemple particulier des conventions d’appel x86_64 et il en est 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 de paramètres a, b, c, d, e, f):
La performance est l’une des raisons de ces ABI (par exemple, passer des paramètres via des registres au lieu de les sauvegarder 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
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 donne un bon aperçu de toutes les différentes conventions:
Commentaires de la source du noyau Linux 5.0
Je savais que les spécificités x86 sont sous Arch/x86
, et que les commandes d'appels se placent sous Arch/x86/entry
. Donc un rapide git grep rdi
dans ce répertoire me conduit à 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 d'appel système Linux x86_64
Maintenant, regardons les principales implémentations de la libc et voyons ce qu’elles font.
Quoi de mieux que d’examiner la glibc que j’utilise actuellement pour rédiger cette réponse? :-)
la glibc 2.29 définit les appels système x86_64 à sysdeps/unix/sysv/linux/x86_64/sysdep.h
et 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; \
})
ce qui me semble assez explicite. Notez que cela semble avoir été conçu pour correspondre exactement à la convention d'appel des fonctions ABD System V AMD64 classiques: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions
Rappel rapide des clobbers:
cc
signifie que les registres d'indicateurs. Mais commentaires de Peter Cordes que cela n’est pas nécessaire ici.memory
signifie qu'un pointeur peut être passé dans Assembly et utilisé pour accéder à la mémoirePour un exemple minimal explicite d’exécution, voir la réponse suivante: Comment appeler un appel système via sysenter dans un assemblage en ligne?
Effectuer manuellement des appels système dans Assembly
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
aarch64
J'ai montré un exemple d'utilisation minimale exécutable à l'adresse suivante: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep kernel code ici, devrait être facile .