Intel x86 Assembly Language & Microarchitecture
Условные обозначения
Поиск…
замечания
Ресурсы
Обзор / сопоставления: справочник по условному названию Agner Fog . Кроме того, x86 ABIs (wikipedia) : вызов соглашений для функций, включая x86-64 Windows и System V (Linux).
SystemV x86-64 ABI (официальный стандарт) . Используется всеми ОС, но Windows. ( Эта страница github wiki , обновленная HJ Lu, имеет ссылки на 32bit, 64bit и x32. Также ссылки на официальный форум для разработчиков / вкладчиков ABI.) Также обратите внимание, что clang / gcc sign / zero расширяет узкие аргументы до 32bit , хотя ABI, как написано, не требует этого. От него зависит код, генерируемый Кланом.
SystemV 32bit (i386) ABI (официальный стандарт) , используемый Linux и Unix. ( старая версия ).
OS X 32bit x86, вызывающая конвенцию, со ссылками на другие . 64-битное соглашение о вызове - это система V. Сайт Apple просто ссылается на файл FreeBSD для этого.
Windows
__vectorcall: документирует 32-битную и 64-битную версииWindows 32bit
__stdcall: используется для вызова функций API Win32. Эта страница ссылается на другие документы соглашения о вызовах (например,__cdecl).Почему Windows64 использует другое соглашение о вызове от всех других ОС на x86-64? : некоторая интересная история, особенно. для SysV ABI, где архивы списков рассылки являются общедоступными и возвращаются до выпуска AMD первого кремния.
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.)