Как стать автором
Обновить

Соглашения о вызовах

Время на прочтение3 мин
Количество просмотров15K

Что такое 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)

Главным отличием от двух соглашениях выше является то, что аргументы кладутся в регистры, если это возможно, что позволяет увеличить скорость вызова функции, потому что обратиться к регистру быстрее, чем к стеку.

Про память рекомендую посмотреть это видео от AlekOs

Стоит указать, что в 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)

Теги:
Хабы:
Всего голосов 15: ↑13 и ↓2+11
Комментарии12

Публикации

Истории

Работа

Программист С
46 вакансий

Ближайшие события