Поиск…


замечания

Ресурсы

Обзор / сопоставления: справочник по условному названию Agner Fog . Кроме того, x86 ABIs (wikipedia) : вызов соглашений для функций, включая x86-64 Windows и System V (Linux).


32-битный cdecl

cdecl - это соглашение о назначении 32-битной функции Windows, которое очень похоже на соглашение о вызове, используемое во многих операционных системах POSIX (задокументировано в i386 System V ABI ). Одно из отличий заключается в возвращении небольших структур.

параметры

Параметры передаются в стеке, причем первый аргумент имеет наименьший адрес в стеке во время вызова (нажата последняя, ​​поэтому она находится чуть выше адреса возврата при входе в функцию). Вызывающий отвечает за сброс параметров после стека после вызова.

Возвращаемое значение

Для скалярных типов возврата возвращаемое значение помещается в EAX или EDX: EAX для 64-битных целых чисел. Тип плавающей точки возвращается в st0 (x87). Возвращение больших типов, таких как структуры, выполняется по ссылке, причем указатель передается как неявный первый параметр. (Этот указатель возвращается в EAX, поэтому вызывающему абоненту не нужно помнить, что он передал).

Сохраненные и сгруппированные регистры

EBK, EDI, ESI, EBP и ESP (и настройки режима округления FP / SSE) должны быть сохранены вызываемым абонентом, так что вызывающий может полагаться на те регистры, которые не были изменены вызовом.

Все остальные регистры (EAX, ECX, EDX, FLAGS (кроме DF), x87 и векторные регистры) могут быть свободно изменены вызываемым пользователем; если вызывающий абонент хочет сохранить значение до и после вызова функции, он должен сохранить значение в другом месте (например, в одном из сохраненных регистров или в стеке).

64-битная система V

Это соглашение о вызове по умолчанию для 64-разрядных приложений во многих операционных системах POSIX.

параметры

Первые восемь скалярных параметров передаются (по порядку) RDI, RSI, RDX, RCX, R8, R9, R10, R11. Параметры, прошедшие первые восемь, помещаются в стек, причем более ранние параметры ближе к вершине стека. Вызывающий отвечает за выведение этих значений из стека после вызова, если он больше не нужен.

Возвращаемое значение

Для скалярных типов возврата возвращаемое значение помещается в RAX. Возвращение более крупных типов, таких как структуры, осуществляется путем концептуального изменения сигнатуры функции для добавления параметра в начале списка параметров, который является указателем на местоположение, в которое нужно поместить возвращаемое значение.

Сохраненные и сгруппированные регистры

RBP, RBX и R12-R15 сохраняются вызываемым. Все остальные регистры могут быть изменены вызываемым абонентом, и вызывающий должен сохранить значение регистра (например, в стеке), если он захочет использовать это значение позже.

32-битный stdcall

stdcall используется для 32-битных вызовов Windows API.

параметры

Параметры передаются в стек, причем первый параметр находится ближе всего к вершине стека. Вызывающий выталкивает эти значения из стека перед возвратом.

Возвращаемое значение

Скалярные значения возвращаются в EAX.

Сохраненные и сгруппированные регистры

EAX, ECX и EDX могут быть свободно изменены вызываемым абонентом и, если необходимо, должны быть сохранены вызывающим абонентом. EBX, ESI, EDI и EBP должны быть сохранены вызываемым пользователем, если они будут восстановлены и возвращены к исходным значениям.

32-бит, cdecl - Работа с целыми

Поскольку параметры (8, 16, 32 бита)

8, 16, 32 бита всегда передаются в стеке как значения ширины 32 бита 1 .
Не требуется расширение, подпись или обнуление.
Вызов будет использовать только нижнюю часть значений полной ширины.

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

Поскольку параметры (64 бит)

Значения 64 бит передаются в стеке с использованием двух нажатий, в соответствии с условным соглашением littel 2 , нажимая сначала более высокие 32 бита, а затем нижние.

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

В качестве возвращаемого значения

8-битные целые числа возвращаются в AL , в конечном итоге сбивая весь eax .
16-битные целые числа возвращаются в AX , в конечном итоге сбивая весь eax .
32-битные целые числа возвращаются в EAX .
В EDX:EAX возвращаются 64-битные целые числа EDX:EAX , где EAX содержит более низкие 32 бита, а EDX - верхние.

//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 Это приведет к выравниванию стека на 4 байта, размер натурального слова. Также процессор x86 может вызывать только 2 или 4 байта, если не в длинном режиме.

2 Нижний DWORD по нижнему адресу

32-бит, cdecl - Работа с плавающей точкой

В качестве параметров (float, double)

Поплавки имеют размер 32 бита, они естественно передаются в стек.
Удваивается 64-битный размер, они передаются в стеке, соблюдая соглашение Little Endian 1 , нажимая сначала верхние 32 бита и нижние.

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

В качестве параметров (длинный двойной)

Длинные удваивания - 80 бит 2 в ширину, в то время как в стеке TBYTE может быть сохранен с двумя 32-битными нажатиями и одним 16-битным нажатием (для 4 + 4 + 2 = 10), чтобы поддерживать выравнивание стека на 4 байта, он заканчивается занятием 12 байт, таким образом, используя три 32 бита.
В соответствии с соглашением Little Endian биты 79-64 толкаются сначала 3 , затем бит 63-32, за которым следуют биты 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

В качестве возвращаемого значения

Значения с плавающей запятой, независимо от их размера, возвращаются в 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 с нижним адресом.

2 Известен как TBYTE, от десяти байтов.

3 Используя толчок толщины с любым расширением, более высокое значение WORD не используется.

4 Что является TBYE широким, обратите внимание, что вопреки целым числам FP всегда возвращаются с большей точностью, что требуется.

64-разрядная версия Windows

параметры

Первые 4 параметра передаются (в порядке) RCX, RDX, R8 и R9. XMM0-XMM3 используются для передачи параметров с плавающей запятой.

Любые дополнительные параметры передаются в стек.

Параметры, превышающие 64 бит, передаются по адресу.

Проливные пространства

Даже если функция использует менее 4 параметров, вызывающий всегда предоставляет пространство для 4 параметров QWORD в стеке. Вызов может использовать их для любых целей, обычно копировать параметры там, если они будут разлиты другим вызовом.

Возвращаемое значение

Для скалярных типов возврата возвращаемое значение помещается в RAX. Если тип возврата больше 64 бит (например, для структур), то RAX является указателем на это.

Сохраненные и сгруппированные регистры

Все регистры, используемые при передаче параметров (RCX, RDX, R8, R9 и XMM0 до XMM3), RAX, R10, R11, XMM4 и XMM5 могут разливаться вызываемым абонентом. Все остальные регистры должны быть сохранены вызывающим абонентом (например, в стеке).

Выравнивание стека

Стек должен храниться в 16 байт. Так как команда «вызов» подталкивает 8-байтовый обратный адрес, это означает, что каждая нелистная функция собирается настроить стек на значение формы 16n + 8, чтобы восстановить выравнивание по 16 байт.
Это задание вызывающих абонентов для очистки стека после вызова.


Источник: история призвания конвенций, часть 5: amd64 Раймонд Чен

32-бит, cdecl - Работа с структурами

набивка

Помните, что элементы структуры обычно дополняются, чтобы обеспечить их выравнивание по их естественной границе:

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

В качестве параметров (проход по ссылке)

При передаче по ссылке указатель на структуру в памяти передается как первый аргумент в стеке. Это эквивалентно передаче натурального размера (32-разрядного) целочисленного значения; см. 32-разрядную спецификацию cdecl .

В качестве параметров (перейдите по значению)

Когда они передаются по значению, структуры полностью копируются в стек, соблюдая исходный формат памяти ( т. Е. Первый член будет на нижнем адресе).

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

В качестве возвращаемого значения

Если они не являются тривиальными 1 , структуры возвращаются в буфер, предоставляемый вызывающим абонентом. Это эквивалентно наличию скрытого первого параметра struct S *retval (где struct S - тип структуры).

Функция должна возвращать с этим указателем на возвращаемое значение в eax ; Вызывающему абоненту разрешено зависеть от eax удерживающего указатель на возвращаемом значении, которое оно нажало прямо перед call .

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

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

Скрытый параметр не добавляется к счету параметров для очистки стека, так как он должен обрабатываться вызываемым пользователем.

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

В приведенном выше примере структура будет сохранена в верхней части стека.

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 «Тривиальная» структура - это та, которая содержит только один элемент неструктурного типа без массива (до 32 бит). Для таких структур значение этого элемента просто возвращается в регистре eax . (Такое поведение наблюдается при настройке GCC для Linux)

Версия cdecl для Windows отличается от соглашения о вызове System V ABI: «Тривиальная» структура допускает содержать до двух членов неструктурного типа без массива (до 32 бит). Эти значения возвращаются в eax и edx , как и 64-битное целое число. (Это поведение наблюдается для MSVC и Clang, предназначенных для Win32.)



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow