Соглашения о вызовах
Что такое Calling Conventions?
Это стандартизированные методы реализации и вызова функций.
Соглашение о вызовах опредяют как функция вызывается, как функция управляет стеком и стековым кадром, как аргументы передаются в функцию, как функция возвращает значения.
Я разберу несколько наиболее часто используемых
stdcall (Standart Calling Convention)
STDCALL это стандартное соглашение для Win32 API. В данном соглашение, аргументы передаются справа налево и очистка стека ложится на вызываемую функцию. Для передачи аргументов используется стек, т.е. перед вызовом нужно положить аргументы на стек. Возвращаемое значение записывается в регистр eax.
Пример вызова абстрактной функции, которая принимает 3 4-байтовых аргумента.
PUSH 0x5
PUSH 0x4
PUSH 0x8
CALL _func@12
В вызываемой функции:
...
mov eax, 0x75 ; Записываем 0x75 в eax, т.е. в регистр, который отвечает за возвращаемое значение
...
ret 12 ; Очищаем стек на 12 байт (3 аргумента по 4 байта)
Давайте рассмотрим вызов функции MessageBox из Win32 API, используя NASM.
; Hello World with winapi messagebox using stdcall calling convention
extern _MessageBoxA@16
extern _ExitProcess@4
global _main
section .data
message db "Hello, Habr!", 0
title db "Habr!", 0
section .text
_main:
push dword 0x00
push dword title
push dword message
push dword 0
call _MessageBoxA@16
push 0
call _ExitProcess@4
Приведу пример вызова на языке программирования C.
MessageBox(0, message, title, MB_OK);
Разберем то, как мы передаем аргументы:
Первые 4 строки - передача аргументов, согласно STDCALL, то есть справа налево.
Рассмотрим, какие аргументы требуются для вызова функции MessageBox:
int MessageBox(
[in, optional] HWND hWnd,
[in, optional] LPCTSTR lpText,
[in, optional] LPCTSTR lpCaption,
[in] UINT uType
);
MessageBox принимает 4 аргумента:
HWND - Дескриптор родительского окна. Мы его оставляем 0, так как у нас его нет.
lpText - Строка, которая будет выведена в окне. В нашем случае это "Hello, Habr!"
lpCaption - Заголовок окна. В нашем случае это "Habr!"
uType - Тип MessageBox'a. Мы указываем 0x00, то есть MESSAGEBOX_OK. В окошке будет только одна кнопка - ОК.
С другими типами MessageBox вы можете ознакомиться в официальной документации
Мы видим в каком порядке принимаются параметры при вызове функции, но на стек мы кладем в обратном порядке, то есть первый аргумент, который мы кладем на стек - 0x00 - uType, второй ttl - lpCaption. Это и есть одна из особенностей данного соглашения о вызовах, вторая особенность - eax используется как регистр для возвращаемого значения.
Еще стоит упомянуть имена функций. Они начинаются с символа нижнего подчеркивания и заканчиваются на такую конструкцию "@[Количество байт, которые нужно выделить для аргументов.]"
cdecl (C calling convention)
Стандартное соглашение о вызовах для программ на C/C++.
В данном соглашение аргументы передаются справа налево и кладутся на стек, как и в [[#STDCALL Standart Calling Convention|stdcall]], возвращаемое значение кладется в регистр EAX. Но вот стек уже очищается функцией, которая вызывает. Имена функций начинаются с символа нижнего подчеркивания, без указания количества байт для аргументов к конце.
Пример:
push 18
push 19
call _add
add esp, 8
fastcall (Fast calling convention)
Главным отличием от двух соглашениях выше является то, что аргументы кладутся в регистры, если это возможно, что позволяет увеличить скорость вызова функции, потому что обратиться к регистру быстрее, чем к стеку.
Стоит указать, что в x86 только 2 регистра могут быть задействованы для передачи аргументов, остальные будут храниться в стеке.
Аргументы также передаются справа налево. Стек чистится вызываемой функцией.
Пример:
...
mov ecx, 0x3
mov edx, 0x1
call @sum@8
...
К названием функций добавляется @ в начале и следующая конструкция в конце "@[Количество байт, которые нужно выделить для аргументов.]"
thiscall
Это соглашение о вызовах используется для вызова нестатических функций-членов C++.
Так как используется только для нестатических функций-членов, то у нас есть указатель this, который передается в ECX, стек очищается вызываемой функцией, аргументы передаются справа налево на стек, возвращаемое значение помещается в регистр EAX.
Пример:
mov ecx, SomeObj
push b
push a
call _MyMethod
vectorcall
Это соглашение о вызовах, которое было выпущено для увеличения эффективности и скорости обработки, позволяя передавать векторные типы данных в регистры. (RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 + XMM0-XMM5/YMM0-YMM5).
Стек очищается функцией, которая вызывает, аргументы передаются справа налево.
Итого
Соглашение о вызове - описание правил вызова функций, в которое входит способы передачи аргументов, способы вызова, способы возврата значения, именования функций и очистки памяти после завершения работы функции.
В компиляторе от Microsoft (cl) их можно использовать вот так:
stdcall (/Gz)
fastcall (/Gr)
cdecl (/Gd)
vectorcall (/Gv)