Recherche…


Remarques

Ressources

Aperçus / comparaisons: le guide de convention d'appel agréable d'Agner Fog . Aussi, x86 ABIs (wikipedia) : conventions d'appel pour les fonctions, y compris x86-64 Windows et System V (Linux).


32 bits cdecl

cdecl est une convention d'appel de fonction Windows 32 bits très similaire à la convention d'appel utilisée sur de nombreux systèmes d'exploitation POSIX (documentée dans l' ABI i386 System V ). L'une des différences réside dans le retour de petites structures.

Paramètres

Les paramètres sont passés sur la pile, avec le premier argument à l'adresse la plus basse de la pile au moment de l'appel (dernier appui, donc juste au-dessus de l'adresse de retour à l'entrée de la fonction). L'appelant est responsable de la suppression des paramètres de la pile après l'appel.

Valeur de retour

Pour les types de retour scalaires, la valeur de retour est placée dans EAX ou EDX: EAX pour les entiers 64 bits. Les types à virgule flottante sont renvoyés dans st0 (x87). Renvoyer des types plus grands comme les structures se fait par référence, avec un pointeur passé en tant que premier paramètre implicite. (Ce pointeur est retourné dans EAX, donc l'appelant n'a pas à se souvenir de ce qu'il a passé).

Registres enregistrés et obstrués

EBX, EDI, ESI, EBP et ESP (et les paramètres du mode d’arrondi FP / SSE) doivent être conservés par l’appelé de sorte que l’appelant puisse compter sur ces registres qui n’ont pas été modifiés par un appel.

Tous les autres registres (EAX, ECX, EDX, FLAGS (autres que DF), registres x87 et vectoriels) peuvent être librement modifiés par l'appelé; Si un appelant souhaite conserver une valeur avant et après l'appel de la fonction, il doit enregistrer la valeur ailleurs (comme dans l'un des registres enregistrés ou sur la pile).

Système V 64 bits

Ceci est la convention d'appel par défaut pour les applications 64 bits sur de nombreux systèmes d'exploitation POSIX.

Paramètres

Les huit premiers paramètres scalaires sont passés (dans l’ordre) RDI, RSI, RDX, RCX, R8, R9, R10, R11. Les paramètres au-delà des huit premiers sont placés sur la pile, les paramètres antérieurs étant plus proches du sommet de la pile. L'appelant est responsable de supprimer ces valeurs de la pile après l'appel si ce n'est plus nécessaire.

Valeur de retour

Pour les types de retour scalaires, la valeur de retour est placée dans RAX. Renvoyer des types plus grands comme les structures se fait en modifiant conceptuellement la signature de la fonction pour ajouter un paramètre au début de la liste de paramètres qui est un pointeur vers un emplacement dans lequel placer la valeur de retour.

Registres enregistrés et obstrués

RBP, RBX et R12 – R15 sont conservés par l'appelé. Tous les autres registres peuvent être modifiés par l'appelé et l'appelant doit conserver lui-même la valeur d'un registre (par exemple sur la pile) s'il souhaite utiliser cette valeur ultérieurement.

Appel de 32 bits

stdcall est utilisé pour les appels d'API Windows 32 bits.

Paramètres

Les paramètres sont transmis à la pile, le premier paramètre étant le plus proche du haut de la pile. L'appelé va faire sortir ces valeurs de la pile avant de revenir.

Valeur de retour

Les valeurs de retour scalaires sont placées dans EAX.

Registres enregistrés et obstrués

EAX, ECX et EDX peuvent être librement modifiés par l'appelé et doivent être sauvegardés par l'appelant si vous le souhaitez. EBX, ESI, EDI et EBP doivent être enregistrés par l'appelé s'ils ont été modifiés et restaurés à leurs valeurs d'origine lors du retour.

32 bits, cdecl - Gestion des nombres entiers

Comme paramètres (8, 16, 32 bits)

Les entiers de 8, 16, 32 bits sont toujours passés, sur la pile, en valeurs de 32 bits de largeur totale 1 .
Aucune extension, signée ou mise à zéro, n'est nécessaire.
Le destinataire utilisera simplement la partie inférieure des valeurs de largeur complète.

//C prototype of the callee
void  __attribute__((cdecl)) foo(char a, short b, int c, long d);

foo(-1, 2, -3, 4);


;Call to foo in assembly

push DWORD 4             ;d, long is 32 bits, nothing special here
push DWORD 0fffffffdh    ;c, int is 32 bits, nothing special here
push DWORD 0badb0002h    ;b, short is 16 bits, higher WORD can be any value
push DWORD 0badbadffh    ;a, char is 8 bits, higher three bytes can be any value
call foo
add esp, 10h             ;Clean up the stack

Comme paramètres (64 bits)

Les valeurs de 64 bits sont passées sur la pile en utilisant deux poussées, en respectant la convention conventionnelle 2 , poussant d'abord les 32 bits supérieurs puis les bits inférieurs.

//C prototype of the callee
void  __attribute__((cdecl)) foo(char a, short b, int c, long d);

foo(0x0123456789abcdefLL);

;Call to foo in assembly

push DWORD 89abcdefh        ;Higher DWORD of 0123456789abcdef
push DWORD 01234567h        ;Lower DWORD of 0123456789abcdef
call foo
add esp, 08h

Comme valeur de retour

8 bits entiers sont renvoyées dans AL , démolir finalement l'ensemble eax .
16 bits sont des nombres entiers de retournés dans AX , éventuellement démolir l'ensemble eax .
Les entiers 32 bits sont renvoyés dans EAX .
Les entiers 64 bits sont retournés dans EDX:EAX , où EAX contient les 32 bits inférieurs et EDX les bits supérieurs.

//C
char foo() { return -1; }

;Assembly
mov al, 0ffh
ret

//C
unsigned short foo() { return 2; }

;Assembly
mov ax, 2
ret

//C
int foo() { return -3; }

;Assembly
mov eax, 0fffffffdh
ret

//C
int foo() { return 4; }

;Assembly
xor edx, edx            ;EDX = 0
mov eax, 4            ;EAX = 4
ret

1 Gardez la pile alignée sur 4 octets, la taille de mot naturelle. De plus, un processeur x86 ne peut pousser que 2 ou 4 octets lorsqu'il n'est pas en mode long.

2 DWORD inférieur à l'adresse inférieure

32 bits, cdecl - Traitement des virgules flottantes

Comme paramètres (float, double)

Les flotteurs ont une taille de 32 bits, ils sont transmis naturellement sur la pile.
Les doublons ont une taille de 64 bits, ils sont passés, sur la pile, en respectant la convention Little Endian 1 , en poussant d'abord les 32 bits supérieurs et les inférieurs.

//C prototype of callee
double foo(double a, float b);

foo(3.1457, 0.241);

;Assembly call

;3.1457 is 0x40092A64C2F837B5ULL
;0.241 is 0x3e76c8b4

push DWORD  3e76c8b4h        ;b, is 32 bits, nothing special here
push DWORD 0c2f837b5h        ;a, is 64 bits, Higher part of 3.1457
push DWORD  40092a64h        ;a, is 64 bits, Lower part of 3.1457
call foo
add esp, 0ch

;Call, using the FPU
;ST(0) = a, ST(1) = b
sub esp, 0ch
fstp QWORD PTR [esp]        ;Storing a as a QWORD on the stack
fstp DWORD PTR [esp+08h]    ;Storing b as a DWORD on the stack
call foo
add esp, 0ch

Comme paramètres (double long)

Les doubles longs ont une largeur de 80 bits 2 , tandis que sur la pile, un TBYTE peut être stocké avec deux poussées de 32 bits et une poussée de 16 bits (pour 4 + 4 + 2 = 10). 12 octets, utilisant ainsi trois poussées de 32 bits.
En respectant la convention Little Endian, les bits 79 à 64 sont d'abord poussés 3 , puis les bits 63 à 32 suivis des bits 31 à 0.

//C prototype of the callee
void  __attribute__((cdecl)) foo(long double a);

foo(3.1457);

;Call to foo in assembly
;3.1457 is 0x4000c9532617c1bda800

push DWORD 4000h        ;Bits 79-64, as 32 bits push
push DWORD 0c9532617h        ;Bits 63-32
push DWORD 0c1bda800h        ;Bits 31-0
call foo
add esp, 0ch

;Call to foo, using the FPU
;ST(0) = a

sub esp, 0ch
fstp TBYTE PTR [esp]        ;Store a as ten byte on the stack
call foo
add esp, 0ch

Comme valeur de retour

Une valeur à virgule flottante, quelle que soit sa taille, est renvoyée dans ST(0) 4 .

//C
float one() { return 1; }

;Assembly
fld1            ;ST(0) = 1
ret

//C
double zero() { return 0; }

;Assembly
fldz            ;ST(0) = 0
ret

//C
long double pi() { return PI; }

;Assembly
fldpi            ;ST(0) = PI
ret

1 DWORD inférieur à l'adresse inférieure.

2 Connu sous le nom de TBYTE, à partir de dix octets.

3 En utilisant une poussée pleine largeur avec une extension, le mot supérieur n'est pas utilisé.

4 Ce qui est large TBYE, notez que contrairement aux entiers, FP sont toujours renvoyés avec plus de précision que nécessaire.

Windows 64 bits

Paramètres

Les 4 premiers paramètres sont passés dans (dans l'ordre) RCX, RDX, R8 et R9. XMM0 à XMM3 sont utilisés pour transmettre des paramètres à virgule flottante.

Tous les paramètres supplémentaires sont passés sur la pile.

Les paramètres supérieurs à 64 bits sont transmis par adresse.

Espace de déversement

Même si la fonction utilise moins de 4 paramètres, l'appelant fournit toujours de l'espace pour 4 paramètres de taille QWORD sur la pile. L'appelant est libre de les utiliser pour n'importe quel but, il est courant de copier les paramètres là-bas s'ils étaient renversés par un autre appel.

Valeur de retour

Pour les types de retour scalaires, la valeur de retour est placée dans RAX. Si le type de retour est supérieur à 64bits (par exemple pour les structures), RAX est un pointeur sur cette valeur.

Registres enregistrés et obstrués

Tous les registres utilisés dans le passage des paramètres (RCX, RDX, R8, R9 et XMM0 à XMM3), RAX, R10, R11, XMM4 et XMM5 peuvent être déversés par l'appelé. Tous les autres registres doivent être conservés par l'appelant (par exemple sur la pile).

Alignement de pile

La pile doit être alignée sur 16 octets. Puisque l'instruction "call" pousse une adresse de retour de 8 octets, cela signifie que chaque fonction non-feuille va ajuster la pile d'une valeur de la forme 16n + 8 afin de restaurer l'alignement sur 16 octets.
C'est le travail des appelants pour nettoyer la pile après un appel.


Source: L'histoire des conventions d'appel, partie 5: amd64 Raymond Chen

32 bits, cdecl - Traitement des structures

Rembourrage

Rappelez-vous que les membres d'une structure sont généralement remplis pour garantir qu'ils sont alignés sur leur limite naturelle:

struct t
{
    int a, b, c, d;    // a is at offset 0, b at 4, c at 8, d at 0ch
    char e;            // e is at 10h
    short f;           // f is at 12h (naturally aligned)
    long g;            // g is at 14h
    char h;            // h is at 18h
    long i;            // i is at 1ch (naturally aligned)
};

Comme paramètres (passe par référence)

Lorsqu'il est transmis par référence, un pointeur sur la structure en mémoire est transmis en tant que premier argument de la pile. Cela équivaut à transmettre une valeur entière de taille naturelle (32 bits); voir cdecl 32 bits pour plus de détails.

Comme paramètres (passe par valeur)

Si transmis par valeur, structs sont entièrement copiés sur la pile, en respectant la mise en page de mémoire d' origine ( à savoir, le premier élément sera à l'adresse inférieure).

int __attribute__((cdecl)) foo(struct t a);

struct t s = {0, -1, 2, -3, -4, 5, -6, 7, -8};
foo(s);
; Assembly call

push DWORD 0fffffff8h    ; i (-8)
push DWORD 0badbad07h    ; h (7), pushed as DWORD to naturally align i, upper bytes can be garbage
push DWORD 0fffffffah    ; g (-6)
push WORD 5              ; f (5)
push WORD 033fch         ; e (-4), pushed as WORD to naturally align f, upper byte can be garbage
push DWORD 0fffffffdh    ; d (-3)
push DWORD 2             ; c (2)
push DWORD 0ffffffffh    ; b (-1)
push DWORD 0             ; a (0)
call foo
add esp, 20h

Comme valeur de retour

À moins qu'ils ne soient triviaux 1 , les structures sont copiées dans un tampon fourni par l'appelant avant de retourner. Cela équivaut à avoir un premier paramètre caché struct S *retval (où struct S est le type de la structure).

La fonction doit retourner avec ce pointeur à la valeur de retour dans eax ; L'appelant est autorisé à dépendre de eax maintenant le pointeur sur la valeur de retour, qu'il a enfoncée juste avant l' call .

struct S
{
    unsigned char a, b, c;
};

struct S foo();         // compiled as struct S* foo(struct S* _out)

Le paramètre masqué n'est pas ajouté au nombre de paramètres à des fins de nettoyage de pile, car il doit être géré par l'appelé.

sub esp, 04h        ; allocate space for the struct

; call to foo
push esp            ; pointer to the output buffer
call foo
add esp, 00h        ; still as no parameters have been passed

Dans l'exemple ci-dessus, la structure sera enregistrée en haut de la pile.

struct S foo()
{
    struct S s;
    s.a = 1; s.b = -2; s.c = 3;
    return s;
}
; Assembly code
push ebx
mov eax, DWORD PTR [esp+08h]   ; access hidden parameter, it is a pointer to a buffer
mov ebx, 03fe01h               ; struct value, can be held in a register
mov DWORD [eax], ebx           ; copy the structure into the output buffer 
pop ebx
ret 04h                        ; remove the hidden parameter from the stack
                               ; EAX = pointer to the output buffer

1 Une structure "triviale" est une structure qui ne contient qu'un seul membre d'un type non struct, non-array (jusqu'à 32 bits). Pour de telles structures, la valeur de ce membre est simplement renvoyée dans le registre eax . (Ce comportement a été observé avec GCC ciblant Linux)

La version Windows de cdecl est différente de la convention d'appel de System V ABI: une structure "triviale" est autorisée à contenir jusqu'à deux membres d'un type non struct, non-array (jusqu'à 32 bits). Ces valeurs sont renvoyées dans eax et edx , tout comme un entier 64 bits. (Ce comportement a été observé pour MSVC et Clang ciblant Win32.)



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow