Szukaj…


Uwagi

Zasoby

Przegląd / porównania: ładny przewodnik po konwencjach telefonicznych Agner Fog . Ponadto, x86 ABI (wikipedia) : konwencje wywoływania funkcji, w tym Windows x86-64 i System V (Linux).


32-bitowy cdecl

cdecl to 32-bitowa konwencja wywoływania funkcji systemu Windows, która jest bardzo podobna do konwencji wywoływania stosowanej w wielu systemach operacyjnych POSIX (udokumentowanej w i386 System V ABI ). Jedną z różnic jest zwracanie małych struktur.

Parametry

Parametry są przekazywane na stos, z pierwszym argumentem pod najniższym adresem na stosie w momencie wywołania (wypychanym na końcu, więc jest tuż nad adresem zwrotnym przy wejściu do funkcji). Dzwoniący jest odpowiedzialny za usunięcie parametrów ze stosu po wywołaniu.

Zwracana wartość

W przypadku skalarnych typów zwrotów wartość zwracana jest umieszczana w EAX lub EDX: EAX dla liczb całkowitych 64-bitowych. Typy zmiennoprzecinkowe są zwracane w st0 (x87). Zwracanie większych typów, takich jak struktury, odbywa się przez odwołanie, a wskaźnik jest przekazywany jako domyślny pierwszy parametr. (Ten wskaźnik jest zwracany w EAX, więc osoba dzwoniąca nie musi pamiętać, co przeszła).

Zapisane i wyrejestrowane rejestry

EBX, EDI, ESI, EBP i ESP (i ustawienia trybu zaokrąglania FP / SSE) muszą być zachowane przez odbiorcę, tak aby dzwoniący mógł polegać na tych rejestrach, które nie zostały zmienione przez połączenie.

Wszystkie inne rejestry (EAX, ECX, EDX, FLAGS (inne niż DF), x87 i rejestry wektorowe) mogą być dowolnie modyfikowane przez odbiorcę; jeśli rozmówca chce zachować wartość przed i po wywołaniu funkcji, musi zapisać wartość w innym miejscu (np. w jednym z zapisanych rejestrów lub na stosie).

64-bitowy system V

Jest to domyślna konwencja wywoływania dla aplikacji 64-bitowych w wielu systemach operacyjnych POSIX.

Parametry

Pierwsze osiem parametrów skalarnych przekazuje się (w kolejności) RDI, RSI, RDX, RCX, R8, R9, R10, R11. Parametry powyżej pierwszych ośmiu są umieszczane na stosie, przy czym wcześniejsze parametry znajdują się bliżej góry stosu. Dzwoniący jest odpowiedzialny za usunięcie tych wartości ze stosu po wywołaniu, jeśli nie jest już potrzebne.

Zwracana wartość

W przypadku skalarnych typów zwrotów wartość zwracana jest umieszczana w RAX. Zwracanie większych typów, takich jak struktury, odbywa się poprzez koncepcyjną zmianę podpisu funkcji w celu dodania parametru na początku listy parametrów, który jest wskaźnikiem do miejsca, w którym należy umieścić wartość zwracaną.

Zapisane i wyrejestrowane rejestry

RBP, RBX i R12 – R15 są zachowywane przez callee. Wszystkie pozostałe rejestry mogą być modyfikowane przez odbiorcę, a osoba dzwoniąca musi zachować samą wartość rejestru (np. Na stosie), jeśli chce później użyć tej wartości.

32-bitowe połączenie standardowe

Stdcall jest używany do 32-bitowych wywołań interfejsu API systemu Windows.

Parametry

Parametry są przekazywane na stos, przy czym pierwszy parametr znajduje się najbliżej góry stosu. Odbiorca usunie te wartości ze stosu przed powrotem.

Zwracana wartość

Skalarne wartości zwracane są umieszczane w EAX.

Zapisane i wyrejestrowane rejestry

EAX, ECX i EDX mogą być dowolnie modyfikowane przez odbiorcę i muszą zostać zapisane przez dzwoniącego, jeśli jest to pożądane. EBX, ESI, EDI i EBP muszą zostać zapisane przez odbiorcę, jeśli zostaną zmodyfikowane i przywrócone do oryginalnych wartości po powrocie.

32-bit, cdecl - Radzenie sobie z liczbami całkowitymi

Jako parametry (8, 16, 32 bity)

Liczby całkowite 8, 16, 32-bitowe są zawsze przekazywane na stosie jako wartości 32-bitowe o pełnej szerokości 1 .
Nie jest wymagane rozszerzenie, podpisane lub wyzerowane.
Odbiorca użyje tylko dolnej części wartości pełnej szerokości.

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

Jako parametry (64 bity)

Wartości 64-bitowe są przekazywane na stos za pomocą dwóch naciśnięć, z zachowaniem małej konwencji Endian 2 , przesuwając najpierw wyższe 32 bity, a następnie niższe.

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

Jako wartość zwracana

8 bitów liczb całkowitych jest zwracanych w AL , ostatecznie blokując cały eax .
16-bitowe liczby całkowite są zwracane w AX , ostatecznie blokując cały eax .
32-bitowe liczby całkowite są zwracane w EAX .
64-bitowe liczby całkowite są zwracane w EDX:EAX , gdzie EAX przechowuje niższe 32 bity, a EDX górne.

//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 Dzięki temu stos będzie wyrównany do 4 bajtów, czyli naturalnego rozmiaru słowa. Również procesor x86 może przesuwać 2 lub 4 bajty, gdy nie jest w trybie długim.

2 Obniż DWORD pod dolnym adresem

32-bit, cdecl - Radzenie sobie z zmiennoprzecinkowym

Jako parametry (zmiennoprzecinkowe, podwójne)

Pływaki mają rozmiar 32 bitów, są przekazywane naturalnie na stos.
Podwójne mają rozmiar 64 bitów, są przekazywane na stos, zgodnie z konwencją Little Endian 1 , pchając najpierw górne 32 bity, a następnie dolne.

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

Jako parametry (długie podwójne)

Długie kopie mają szerokość 80 bitów i szerokość 2 , podczas gdy na stosie można przechowywać TBYTE z dwoma 32-bitowymi przesunięciami i jednym 16-bitowym przesunięciem (dla 4 + 4 + 2 = 10), aby utrzymać wyrównanie stosu na 4 bajtach, kończy to zajmowanie 12 bajtów, wykorzystując w ten sposób trzy 32-bitowe wypychania.
Zgodnie z konwencją Little Endian, bity 79-64 są wypychane najpierw 3 , a następnie bity 63-32, a następnie bity 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

Jako wartość zwracana

Wartości zmiennoprzecinkowe, niezależnie od ich wielkości, są zwracane w 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 Obniż DWORD pod dolnym adresem.

2 Znany jako TBYTE, z dziesięciu bajtów.

3 Używając wypychacza o pełnej szerokości z dowolnym przedłużeniem, wyższe WORD nie jest używane.

4 Który jest szeroki w TBYE, zauważ, że w przeciwieństwie do liczb całkowitych, FP są zawsze zwracane z większą precyzją, niż jest to wymagane.

64-bitowy system Windows

Parametry

Pierwsze 4 parametry są przekazywane (w kolejności) RCX, RDX, R8 i R9. XMM0 do XMM3 są używane do przekazywania parametrów zmiennoprzecinkowych.

Wszelkie dalsze parametry są przekazywane na stos.

Parametry większe niż 64 bity są przekazywane przez adres.

Rozlana przestrzeń

Nawet jeśli funkcja wykorzystuje mniej niż 4 parametry, wywołujący zawsze zapewnia miejsce na 4 parametry wielkości QWORD na stosie. Odbiorca może ich używać do dowolnego celu, często kopiuje się tam parametry, jeśli zostałyby rozlane przez inne wywołanie.

Zwracana wartość

W przypadku skalarnych typów zwrotów wartość zwracana jest umieszczana w RAX. Jeśli typ zwracany jest większy niż 64 bity (np. Dla struktur), RAX jest wskaźnikiem do tego.

Zapisane i wyrejestrowane rejestry

Wszystkie rejestry używane do przekazywania parametrów (RCX, RDX, R8, R9 i XMM0 do XMM3), RAX, R10, R11, XMM4 i XMM5 mogą być rozlane przez odbiorcę. Wszystkie pozostałe rejestry muszą być zachowane przez dzwoniącego (np. Na stosie).

Wyrównanie stosu

Stos musi być wyrównany 16 bajtów. Ponieważ instrukcja „wywołania” przesuwa 8-bajtowy adres zwrotny, oznacza to, że każda funkcja niebędąca liściem dostosuje stos o wartość postaci 16n + 8 w celu przywrócenia wyrównania 16 bajtów.
Zadaniem dzwoniącego jest czyszczenie stosu po zakończeniu połączenia.


Źródło: Historia zwoływania konwencji, część 5: amd64 Raymond Chen

32-bit, cdecl - Radzenie sobie ze strukturami

Wyściółka

Pamiętaj, że członkowie struktury są zwykle dopełniani, aby upewnić się, że są wyrównani do swojej naturalnej granicy:

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)
};

Jako parametry (przekazane przez odniesienie)

Po przekazaniu przez referencję wskaźnik do struktury w pamięci jest przekazywany jako pierwszy argument na stosie. Jest to równoważne z przekazaniem wartości liczbowej naturalnej (32-bitowej); szczegółowe informacje można znaleźć w 32-bitowym pliku cdecl .

Jako parametry (przekazane przez wartość)

Po przekazaniu przez wartość struktury są w całości kopiowane na stos, z zachowaniem oryginalnego układu pamięci ( tzn . Pierwszy element będzie pod dolnym adresem).

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

Jako wartość zwracana

O ile nie są trywialne 1 , struktury są kopiowane do bufora dostarczanego przez program wywołujący przed zwróceniem. Jest to równoważne z posiadaniem ukrytego pierwszego parametru struct S *retval (gdzie struct S jest rodzajem struct).

Funkcja musi powrócić z tym wskaźnikiem do wartości zwracanej w eax ; Osoba dzwoniąca może polegać na tym, że eax przytrzymuje wskaźnik do wartości zwracanej, którą wypchnął tuż przed call .

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

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

Parametr ukryty nie jest dodawany do liczby parametrów w celu czyszczenia stosu, ponieważ musi być obsługiwany przez odbiorcę.

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

W powyższym przykładzie struktura zostanie zapisana na górze stosu.

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 „Trywialna” struktura to taka, która zawiera tylko jednego członka typu niestrukturalnego, niebędącego macierzą (do 32 bitów). W przypadku takich struktur wartość tego elementu jest po prostu zwracana do rejestru eax . (To zachowanie zaobserwowano w przypadku GCC atakujących Linuksa)

Wersja cdecl dla systemu Windows różni się od konwencji wywoływania ABI w Systemie V: „Trywialna” struktura może zawierać do dwóch elementów niestrukturalnego typu innego niż macierz (do 32 bitów). Wartości te są zwracane w eax i edx , podobnie jak 64-bitowa liczba całkowita. (To zachowanie zaobserwowano w przypadku MSVC i Clanga atakujących Win32.)



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow