соглашения о вызовах x86
В этой статье есть несколько проблем. Пожалуйста, помогите улучшить его или обсудите эти проблемы на странице обсуждения . ( Узнайте, как и когда удалять эти шаблонные сообщения )
|
В этой статье описаны соглашения о вызовах, используемые при программировании x86 с архитектурой микропроцессоров .
Соглашения о вызовах описывают интерфейс вызываемого кода:
- Порядок выделения атомарных (скалярных) параметров или отдельных частей комплексного параметра.
- Как передаются параметры (помещаются в стек, помещаются в регистры или и то, и другое)
- Какие регистры вызываемая функция должна сохранить для вызывающей стороны (также известные как регистры, сохраняемые вызываемым абонентом или энергонезависимые регистры)
- Как задача подготовки стека для вызова функции и восстановления после него распределяется между вызывающим и вызываемым объектом
Это тесно связано с присвоением размеров и форматов типам языка программирования. Еще одна тесно связанная тема — искажение имен , которое определяет, как имена символов в коде сопоставляются с именами символов, используемыми компоновщиком. Соглашения о вызовах, представления типов и искажение имен — все это части так называемого двоичного интерфейса приложения (ABI).
Существуют тонкие различия в том, как разные компиляторы реализуют эти соглашения, поэтому часто бывает сложно связать код, скомпилированный разными компиляторами. С другой стороны, соглашения, используемые в качестве стандарта API (например, stdcall), реализованы очень единообразно.
Историческая справка
[ редактировать ]До появления микрокомпьютеров производитель машин обычно поставлял операционную систему и компиляторы для нескольких языков программирования . Соглашение (я) о вызовах для каждой платформы определялось инструментами программирования производителя.
Первые микрокомпьютеры до Commodore Pet и Apple II обычно выпускались без ОС или компиляторов. IBM PC поставлялся с предшественницей Windows от Microsoft, дисковой операционной системой ( DOS ), но не имел компилятора. Единственный аппаратный стандарт для IBM PC-совместимых машин определялся процессорами Intel (8086, 80386) и аппаратным обеспечением, поставляемым IBM. Расширения аппаратного обеспечения и все стандарты программного обеспечения (за исключением соглашения о вызовах BIOS ) были открыты для рыночной конкуренции.
Множество независимых фирм-разработчиков программного обеспечения предлагали операционные системы, компиляторы для многих языков программирования и приложения. Фирмы реализовали множество различных схем вызова, часто взаимоисключающих, основанных на разных требованиях, исторической практике и творческих способностях программистов.
После перетряски рынка IBM-совместимых систем преобладали операционные системы и инструменты программирования Microsoft (с разными соглашениями), в то время как фирмы второго уровня, такие как Borland и Novell , и проекты с открытым исходным кодом, такие как GNU Compiler Collection (GCC), все еще поддерживали свои собственные стандарты. В конечном итоге были приняты положения о совместимости между поставщиками и продуктами, что упростило проблему выбора жизнеспособного соглашения. [1]
Очистка звонящего
[ редактировать ]В этих типах соглашений о вызовах вызывающая сторона очищает аргументы из стека (сбрасывает состояние стека таким, каким оно было до вызова вызываемой функции).
CDEC
[ редактировать ]The cdecl
(что означает объявление C ) — это соглашение о вызовах для языка программирования C и используется многими компиляторами C для архитектуры x86 . [1] В cdecl аргументы подпрограммы передаются в стек . Если возвращаемые значения являются целочисленными значениями или адресами памяти, они помещаются в регистр вызываемым объектом EAX, тогда как значения с плавающей запятой помещаются в регистр ST0 x87 . Регистры EAX, ECX и EDX сохраняются вызывающим абонентом, а остальные — вызываемым. Регистры с плавающей запятой x87 от ST0 до ST7 должны быть пустыми (выталкиваются или освобождаются) при вызове новой функции, а от ST1 до ST7 должны быть пусты при выходе из функции. ST0 также должен быть пустым, если он не используется для возврата значения.
В контексте языка C аргументы функции помещаются в стек в порядке справа налево (RTL), то есть последний аргумент помещается первым.
Рассмотрим следующий фрагмент исходного кода C:
int callee(int, int, int);
int caller(void)
{
return callee(1, 2, 3) + 5;
}
На x86 он может создать следующий ассемблерный код ( синтаксис Intel ):
caller:
; make new call frame
; (some compilers may produce an 'enter' instruction instead)
push ebp ; save old call frame
mov ebp, esp ; initialize new call frame
; push call arguments, in reverse
; (some compilers may subtract the required space from the stack pointer,
; then write each argument directly, see below.
; The 'enter' instruction can also do something similar)
; sub esp, 12 : 'enter' instruction could do this for us
; mov [ebp-4], 3 : or mov [esp+8], 3
; mov [ebp-8], 2 : or mov [esp+4], 2
; mov [ebp-12], 1 : or mov [esp], 1
push 3
push 2
push 1
call callee ; call subroutine 'callee'
add esp, 12 ; remove call arguments from frame
add eax, 5 ; modify subroutine result
; (eax is the return value of our callee,
; so we don't have to move it into a local variable)
; restore old call frame
; (some compilers may produce a 'leave' instruction instead)
mov esp, ebp ; most calling conventions dictate ebp be callee-saved,
; i.e. it's preserved after calling the callee.
; it therefore still points to the start of our stack frame.
; we do need to make sure
; callee doesn't modify (or restore) ebp, though,
; so we need to make sure
; it uses a calling convention which does this
pop ebp ; restore old call frame
ret ; return
Вызывающая сторона очищает стек после возврата из вызова функции.
The cdecl
Соглашение о вызовах обычно является соглашением о вызовах по умолчанию для компиляторов C x86 , хотя многие компиляторы предоставляют опции для автоматического изменения используемых соглашений о вызовах. Чтобы вручную определить функцию cdecl, некоторые поддерживают следующий синтаксис:
return_type __cdecl func_name();
Вариации
[ редактировать ]Существуют некоторые различия в интерпретации cdecl. В результате программы x86, скомпилированные для разных платформ операционных систем и/или разными компиляторами, могут быть несовместимы, даже если они оба используют соглашение «cdecl» и не обращаются к базовой среде.
Что касается возврата значений, некоторые компиляторы возвращают простые структуры данных длиной 2 регистра или меньше в паре регистров EAX:EDX, а также более крупные структуры и объекты классов, требующие специальной обработки обработчиком исключений (например, определенный конструктор, деструктор или присваивание) возвращаются в память. Для передачи «в памяти» вызывающая сторона выделяет память и передает на нее указатель в качестве скрытого первого параметра; вызываемый объект заполняет память и возвращает указатель, извлекая скрытый указатель при возврате. [2]
В Linux GCC . устанавливает фактический стандарт соглашений о вызовах Начиная с версии GCC 4.5, при вызове функции стек должен быть выровнен по 16-байтовой границе (предыдущие версии требовали только 4-байтового выравнивания). [1] [3]
Версия cdecl
описано в System V ABI для систем i386. [4]
системный вызов
[ редактировать ]Это похоже на cdecl тем, что аргументы передаются справа налево. EAX, ECX и EDX не сохраняются. Размер списка параметров в двойных словах передается в AL.
Системный вызов — это стандартное соглашение о вызовах для 32-разрядного OS/2 API .
ссылка вверх
[ редактировать ]Аргументы перемещаются справа налево. Три первых (крайних левых) аргумента передаются в EAX, EDX и ECX, а до четырех аргументов с плавающей запятой передаются в ST0–ST3, хотя место для них зарезервировано в списке аргументов в стеке. Результаты возвращаются в формате EAX или ST0. Регистры EBP, EBX, ESI и EDI сохраняются.
Optlink используется компиляторами IBM VisualAge .
Очистка вызываемого абонента
[ редактировать ]В этих соглашениях вызываемый объект очищает аргументы из стека. Функции, использующие эти соглашения, легко распознать в коде ASM, поскольку после возврата они раскручивают стек . x86 ret
Инструкция допускает дополнительный 16-битный параметр, который определяет количество байтов стека, которые необходимо освободить после возврата к вызывающей стороне. Такой код выглядит следующим образом:
ret 12
Конвенции, названные fastcall
или register
не стандартизированы и реализованы по-разному, в зависимости от поставщика компилятора. [1] Обычно соглашения о вызовах на основе регистров передают один или несколько аргументов в регистрах, что уменьшает количество обращений к памяти, необходимых для вызова, и, таким образом, обычно делает их быстрее.
Паскаль
[ редактировать ]В соответствии с соглашением о вызовах языка Borland Turbo Pascal параметры помещаются в стек в порядке слева направо (LTR) (в противоположность cdecl), и вызываемый объект несет ответственность за их удаление из стека.
Возврат результата работает следующим образом:
- Порядковые значения возвращаются в форматах AL ( 8-битные значения), AX (16-битные значения), EAX ( 32-битные значения) или DX:AX (32-битные значения в 16-битных системах).
- Реальные значения возвращаются в DX:BX:AX.
- Значения с плавающей запятой (8087) возвращаются в ST0.
- Указатели возвращаются в EAX в 32-битных системах и в AX в 16-битных системах.
- Строки возвращаются во временном месте, указанном символом @Result.
Это соглашение о вызовах было распространено в следующих 16-битных API: OS/2 1.x, Microsoft Windows 3.x и Borland Delphi версии 1.x. Современные версии Windows API используют stdcall , в котором вызываемый объект по-прежнему восстанавливает стек, как в соглашении Pascal, но параметры теперь перемещаются справа налево.
стандартный вызов
[ редактировать ]Стандартный вызов [5] Соглашение о вызовах — это вариант соглашения о вызовах Pascal, в котором вызываемый объект отвечает за очистку стека, но параметры помещаются в стек в порядке справа налево, как в соглашении о вызовах _cdecl. Регистры EAX, ECX и EDX предназначены для использования внутри функции. Возвращаемые значения сохраняются в регистре EAX.
stdcall — это стандартное соглашение о вызовах для Microsoft Win32 API и Open Watcom C++ .
Microsoft быстрый вызов
[ редактировать ]Майкрософт __fastcall (также известное как Соглашение __msfastcall ) передает первые два подходящих аргумента (оцениваются слева направо) в ECX и EDX. [6] Остальные аргументы помещаются в стек справа налево. Когда компилятор компилирует для IA64 или AMD64 , он игнорирует Ключевое слово __fastcall (или любое другое ключевое слово соглашения о вызовах, кроме __vectorcall ) и вместо этого использует стандартное 64-битное соглашение о вызовах Microsoft.
Другие компиляторы, такие как GCC , [7] Кланг , [8] и МТП [ нужна ссылка ] предоставляют аналогичные соглашения о вызовах «fastcall», хотя они не обязательно совместимы друг с другом или с Microsoft fastcall. [9]
Рассмотрим следующий фрагмент кода C:
__attribute__((fastcall)) void printnums(int num1, int num2, int num3){
printf("The numbers you sent are: %d %d %d", num1, num2, num3);
}
int main(){
printnums(1, 2, 3);
return 0;
}
Декомпиляция основной функции x86 будет выглядеть так (в синтаксисе Intel):
main:
; stack setup
push ebp
mov ebp, esp
push 3 ; immediate 3 (third argument is pushed to the stack)
mov edx, 0x2 ; immediate 2 (second argument) is copied to edx register.
mov ecx, 0x1 ; immediate 1 (first argument) is copied to ecx register.
call printnums
mov eax, 0 ; return 0
leave
retn
Первые два аргумента передаются слева направо, а третий аргумент помещается в стек. Очистка стека не выполняется, поскольку очистка стека выполняется вызываемым объектом. Дизассемблирование вызываемой функции:
printnums:
; stack setup
push ebp
mov ebp, esp
sub esp, 0x08
mov [ebp-0x04], ecx ; in x86, ecx = first argument.
mov [ebp-0x08], edx ; arg2
push [ebp+0x08] ; arg3 is pushed to stack.
push [ebp-0x08] ; arg2 is pushed
push [ebp-0x04] ; arg1 is pushed
push 0x8065d67 ; "The numbers you sent are %d %d %d"
call printf
; stack cleanup
add esp, 0x10
nop
leave
retn 0x04
Поскольку два аргумента были переданы через регистры, а в стек был помещен только один параметр, переданное значение очищается инструкцией retn, поскольку в системах x86 размер int составляет 4 байта.
Microsoft векторный вызов
[ редактировать ]В Visual Studio 2013 Microsoft представила Соглашение о вызовах __vectorcall в ответ на проблемы эффективности со стороны разработчиков игр, графики, видео/аудио и кодеков. Схема позволяет использовать более крупные типы векторов ( плавать , двойной , __m128 , __m256 ), который будет передаваться в регистрах, а не в стеке. [10]
Для кода IA-32 и x64: __vectorcall похож на __fastcall и исходные соглашения о вызовах x64 соответственно, но расширяют их для поддержки передачи векторных аргументов с использованием регистров SIMD . В IA-32 целочисленные значения передаются как обычно, а первые шесть регистров SIMD ( XMM / YMM 0-5) содержат до шести значений с плавающей запятой, векторов или HVA последовательно слева направо, независимо от фактических позиций. вызвано, например, появлением между ними аргумента int. Однако в x64 правило исходного соглашения x64 по-прежнему применяется, так что XMM/YMM0-5 содержит аргументы с плавающей запятой, вектор или HVA только тогда, когда они находятся с первого по шестой. [11]
__vectorcall добавляет поддержку передачи значений однородного векторного агрегата (HVA), которые представляют собой составные типы (структуры), состоящие исключительно из четырех идентичных векторных типов, с использованием тех же шести регистров. После того как регистры выделены для аргументов векторного типа, неиспользуемые регистры распределяются по аргументам HVA слева направо. Правила позиционирования по-прежнему применяются. Результирующий тип вектора и значения HVA возвращаются с использованием первых четырех регистров XMM/YMM. [11]
Компилятор Clang и компилятор Intel C++ также реализуют векторный вызов. [12] У ICC есть аналогичная, более ранняя конвенция под названием __regcall ; [13] он также поддерживается Clang. [14]
Регистр Борланд
[ редактировать ]Оценивая аргументы слева направо, он передает три аргумента через EAX, EDX, ECX. Остальные аргументы помещаются в стек также слева направо. [15] Это соглашение о вызовах по умолчанию в 32-битном компиляторе Delphi , где оно известно как Register . Это соглашение о вызовах также используется C++Builder от Embarcadero, где оно называется __fastcall . [16] В этом компиляторе от Microsoft fastcall можно использовать как __msfastcall . [17]
GCC и Clang можно заставить использовать аналогичное соглашение о вызовах, используя __stdcall
с regparm
атрибут функции или -mregparm=3
выключатель. (Порядок стека инвертирован.) Также можно создать вариант очистки вызывающего объекта, используя cdecl
или расширить это, чтобы также использовать регистры SSE. [18] А cdecl
-based версия используется ядром Linux на i386, начиная с версии 2.6.20 (выпущенной в феврале 2007 г.). [19]
Регистрация Ваткома
[ редактировать ]Watcom не поддерживает ключевое слово __fastcall, за исключением присвоения ему нулевого значения. Соглашение о вызове регистра может быть выбрано с помощью переключателя командной строки. (Однако IDA в любом случае использует __fastcall для единообразия.)
Аргументам присваивается до 4 регистров в порядке EAX, EDX, EBX, ECX. Аргументы присваиваются регистрам слева направо. Если какой-либо аргумент не может быть присвоен регистру (скажем, он слишком велик), он и все последующие аргументы назначаются в стек. Аргументы, назначенные в стек, перемещаются справа налево. Имена искажаются добавлением подчеркивания.
Вариативные функции возвращаются к соглашению о вызовах на основе стека Watcom.
Компилятор Watcom C/C++ также использует #прагма [20] директива, которая позволяет пользователю указать собственное соглашение о вызовах. Как говорится в руководстве: «Этот метод, скорее всего, понадобится очень немногим пользователям, но если он необходим, он может спасти жизнь».
TopSpeed, Clarion, JPI
[ редактировать ]Первые четыре целочисленных параметра передаются в регистрах eax, ebx, ecx и edx. Параметры с плавающей запятой передаются в стек с плавающей запятой – регистры st0, st1, st2, st3, st4, st5 и st6. Параметры структуры всегда передаются в стек. Добавленные параметры передаются в стек после исчерпания регистров. Целочисленные значения возвращаются в eax, указатели в edx и типы с плавающей запятой в st0.
безопасный вызов
[ редактировать ]В Delphi и Free Pascal в Microsoft Windows соглашение о вызовах SafeCall инкапсулирует обработку ошибок COM ( компонентная объектная модель ), поэтому исключения не передаются вызывающей стороне, а сообщаются в возвращаемом значении HRESULT , как того требует COM/OLE. При вызове функции безопасного вызова из кода Delphi Delphi также автоматически проверяет возвращаемый HRESULT и при необходимости вызывает исключение.
Соглашение о вызовах SafeCall такое же, как и соглашение о вызовах stdcall, за исключением того, что исключения передаются обратно вызывающей стороне в EAX как HResult (вместо FS:[0]), а результат функции передается по ссылке в стеке как хотя это был последний «выходной» параметр. При вызове функции Delphi из Delphi это соглашение о вызовах будет выглядеть так же, как и любое другое соглашение о вызовах, поскольку, хотя исключения передаются обратно в EAX, они автоматически преобразуются вызывающей стороной обратно в правильные исключения. При использовании COM-объектов, созданных на других языках, HResults автоматически вызывается как исключение, а результат для функций Get находится в результате, а не в параметре. При создании COM-объектов в Delphi с помощью SafeCall не нужно беспокоиться о HResults, поскольку исключения могут вызываться как обычно, но на других языках они будут рассматриваться как HResults.
function function_name(a: DWORD): DWORD; safecall;
Возвращает результат и вызывает исключения, как обычная функция Delphi, но передает значения и исключения, как если бы это было так:
function function_name(a: DWORD; out Result: DWORD): HResult; stdcall;
Очистка вызывающего или вызываемого абонента
[ редактировать ]этот звонок
[ редактировать ]Это соглашение о вызовах используется для вызова нестатических функций-членов C++. Существуют две основные версии thiscall
используется в зависимости от компилятора и от того, использует ли функция переменное количество аргументов.
Для компилятора GCC: thiscall
почти идентичен cdecl
: вызывающая сторона очищает стек, и параметры передаются в порядке справа налево. Разница заключается в добавлении this
указатель , который помещается в стек последним, как если бы он был первым параметром в прототипе функции.
В компиляторе Microsoft Visual C++ this
указатель передается в ECX, и вызываемый объект очищает стек, зеркально отображая stdcall
соглашение, используемое в C для этого компилятора и в функциях Windows API. Когда функции используют переменное количество аргументов, именно вызывающая сторона очищает стек (см. cdecl
).
The thiscall
Соглашение о вызовах можно явно указать только в Microsoft Visual C++ 2005 и более поздних версиях. В любом другом компиляторе этот вызов не является ключевым словом. (Однако дизассемблеры, такие как IDA , должны указать это. Поэтому IDA использует для этого ключевое слово __thiscall .)
Сохранение реестра
[ редактировать ]Другая часть соглашения о вызовах заключается в том, какие регистры гарантированно сохраняют свои значения после вызова подпрограммы.
Регистры, сохраняемые вызывающим абонентом (изменчивые)
[ редактировать ]Согласно Intel ABI, которому соответствует подавляющее большинство компиляторов, EAX, EDX и ECX должны быть бесплатными для использования внутри процедуры или функции и не должны сохраняться. [ нужна ссылка ]
Как следует из названия, эти регистры общего назначения обычно содержат временную (изменчивую) информацию, которая может быть перезаписана любой подпрограммой.
Следовательно, ответственность за помещение каждого из этих регистров в стек лежит на вызывающей стороне, если она хочет восстановить их значения после вызова подпрограммы.
Регистры, сохраняемые вызываемым абонентом (энергонезависимые)
[ редактировать ]Другие регистры используются для хранения долговременных значений (энергонезависимых), которые должны сохраняться при вызовах.
Другими словами, когда вызывающий объект выполняет вызов процедуры, он может ожидать, что эти регистры будут содержать то же значение после возврата вызываемого объекта.
Таким образом, на вызываемую сторону возлагается ответственность как сохранять (нажимать в начале), так и восстанавливать (соответственно выталкивать) их перед возвратом к вызывающей стороне. Как и в предыдущем случае, эту практику следует применять только к регистрам, которые изменяет вызываемый абонент.
Соглашения о вызовах x86-64
[ редактировать ]Соглашения о вызовах x86-64 используют добавленное пространство регистров для передачи большего количества аргументов в регистры. Кроме того, было уменьшено количество несовместимых соглашений о вызовах. Есть два широко используемых.
Соглашение о вызовах Microsoft x64
[ редактировать ]Соглашение о вызовах Microsoft x64 [21] [22] следует в Windows и предварительной загрузке UEFI (для длительного режима на x86-64 ). Первые четыре аргумента помещаются в регистры. Это означает RCX, RDX, R8, R9 (в указанном порядке) для целочисленных аргументов, структур или указателей и XMM0, XMM1, XMM2, XMM3 для аргументов с плавающей запятой. Добавленные аргументы помещаются в стек (справа налево). Целочисленные возвращаемые значения (аналогично x86) возвращаются в RAX, если их длина составляет 64 бита или меньше. Возвращаемые значения с плавающей запятой возвращаются в XMM0. Параметры длиной менее 64 бит не расширяются до нуля; старшие биты не обнуляются.
Структуры и объединения с размерами, соответствующими целым числам, передаются и возвращаются, как если бы они были целыми числами. В противном случае они заменяются указателем при использовании в качестве аргумента. Когда требуется возврат структуры слишком большого размера, в качестве первого аргумента добавляется еще один указатель на пространство, предоставленное вызывающей стороной, сдвигая все остальные аргументы вправо на одно место. [23]
При компиляции для архитектуры x64 в контексте Windows (с использованием инструментов Microsoft или сторонних производителей) stdcall, thiscall, cdecl и fastcall разрешают использовать это соглашение.
В соглашении о вызовах Microsoft x64 вызывающая сторона обязана выделить 32 байта «теневого пространства» в стеке непосредственно перед вызовом функции (независимо от фактического количества используемых параметров) и очистить стек после вызова. Теневое пространство используется для разгрузки RCX, RDX, R8 и R9. [24] но должен быть доступен всем функциям, даже тем, которые имеют менее четырех параметров.
Регистры RAX, RCX, RDX, R8, R9, R10, R11 считаются энергозависимыми (сохраняемыми вызывающим абонентом). [25]
Регистры RBX, RBP, RDI, RSI, RSP, R12, R13, R14 и R15 считаются энергонезависимыми. [25]
Например, функция, принимающая 5 целочисленных аргументов, будет принимать регистры с первого по четвертый, а пятый будет помещен поверх теневого пространства. Таким образом, при входе в вызываемую функцию стек будет состоять из (в порядке возрастания) адреса возврата, за которым следует теневое пространство (32 байта) и пятый параметр.
В x86-64 Visual Studio 2008 хранит числа с плавающей запятой в XMM6 и XMM7 (а также от XMM8 до XMM15); следовательно, для x86-64 написанные пользователем процедуры языка ассемблера должны сохранять XMM6 и XMM7 (по сравнению с x86 , где написанные пользователем процедуры языка ассемблера не должны сохранять XMM6 и XMM7). Другими словами, написанные пользователем процедуры языка ассемблера должны быть обновлены для сохранения/восстановления XMM6 и XMM7 до/после функции при портировании с x86 на x86-64 .
Начиная с Visual Studio 2013, Microsoft представила Соглашение о вызовах __vectorcall , которое расширяет соглашение x64.
Система V AMD64 ABI
[ редактировать ]Соглашение о вызовах System V AMD64 ABI соблюдается в Solaris , Linux , FreeBSD , macOS , [26] и является стандартом де-факто среди Unix и Unix-подобных операционных систем. Стандарт вызовов OpenVMS для x86-64 основан на System V ABI с некоторыми расширениями, необходимыми для обратной совместимости. [27] Первые шесть целочисленных аргументов или аргументов-указателей передаются в регистрах RDI, RSI, RDX, RCX, R8, R9 (R10 используется как указатель статической цепочки в случае вложенных функций). [28] : 21 ), а XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 и XMM7 используются для первых аргументов с плавающей запятой. [28] : 22 Как и в соглашении о вызовах Microsoft x64, добавленные аргументы передаются в стек. [28] : 22 Целочисленные возвращаемые значения размером до 64 бит хранятся в RAX, а значения до 128 бит хранятся в RAX и RDX. Возвращаемые значения с плавающей запятой аналогичным образом сохраняются в XMM0 и XMM1. [28] : 25 Более широкие регистры YMM и ZMM используются для передачи и возврата более широких значений вместо XMM, если они существуют. [28] : 26, 55
Тип аргумента | Регистры |
---|---|
Целые числа/аргументы указателя 1–6 | РДИ, РСИ, RDX, RCX, R8, R9 |
Аргументы с плавающей запятой 1–8 | ХММ0–ХММ7 |
Лишние аргументы | Куча |
Статический указатель цепочки | 10 рэндов |
Параметры структур и объединений размером в два (восьми, если только поля SSE) указателей или меньше, выровненные по 64-битным границам, разлагаются на «восемь байтов», и каждый из них классифицируется и передается как отдельный параметр. [28] : 24 В противном случае они заменяются указателем при использовании в качестве аргумента. Возвращаемые типы структур и объединений с размерами двух указателей или меньше возвращаются в RAX и RDX (или XMM0 и XMM1). Когда требуется возврат структуры слишком большого размера, в качестве первого аргумента добавляется еще один указатель на пространство, предоставленное вызывающей стороной, сдвигая все остальные аргументы вправо на одно место, и значение этого указателя возвращается в RAX. [28] : 27
Если вызываемая сторона желает использовать регистры RBX, RSP, RBP и R12–R15, она должна восстановить их исходные значения, прежде чем вернуть управление вызывающей стороне. Все остальные регистры должны быть сохранены вызывающей стороной, если она желает сохранить свои значения. [28] : 16
Для функций листового узла (функций, которые не вызывают никаких других функций) 128-байтовое пространство сохраняется сразу под указателем стека функции. Это пространство называется красной зоной . Эта зона не будет перезаписана никакими обработчиками сигналов или прерываний. Таким образом, компиляторы могут использовать эту зону для сохранения локальных переменных. Используя эту зону, компиляторы могут опустить некоторые инструкции при запуске функции (регулировка RSP, RBP). Однако другие функции могут перезаписать эту зону. Поэтому эту зону следует использовать только для функций конечного узла. gcc
и clang
предложить -mno-red-zone
флаг, чтобы отключить оптимизацию красной зоны.
Если вызываемая функция является переменной переменной , то количество аргументов с плавающей запятой, передаваемых функции в векторных регистрах, должно быть предоставлено вызывающей стороной в регистре AL. [28] : 55
В отличие от соглашения о вызовах Microsoft, теневое пространство не предусмотрено; при входе в функцию адрес возврата находится рядом с седьмым целочисленным аргументом в стеке.
Список соглашений о вызовах x86
[ редактировать ]Это список соглашений о вызовах x86. [1] Эти соглашения в первую очередь предназначены для компиляторов C/C++ (особенно для 64-битной части ниже) и, следовательно, в основном для особых случаев. Другие языки могут использовать в своих реализациях другие форматы и соглашения.
Architecture | Имя | Операционная система, компилятор | Параметры | Очистка стека | Примечания | |
---|---|---|---|---|---|---|
Регистры | Порядок стека | |||||
8086 | CDEC | РТЛ (К) | Звонящий | |||
Паскаль | LTR (Паскаль) | Callee | ||||
быстрый вызов (не член) | Майкрософт | АХ, ДХ, ВХ | LTR (Паскаль) | Callee | Указатель возврата в BX. | |
быстрый вызов (функция-член) | Майкрософт | ТОПОР, ДХ | LTR (Паскаль) | Callee | this в стеке с младшим адресом. Указатель возврата в AX.
| |
быстрый вызов | Турбо С [29] | АХ, ДХ, ВХ | LTR (Паскаль) | Callee | this в стеке с младшим адресом. Указатель возврата по старшему адресу стека.
| |
Ватком | АХ, ДХ, ВХ, СХ | РТЛ (К) | Callee | Указатель возврата в СИ. | ||
ИА-32 | CDEC | Unix-подобный ( GCC ) | РТЛ (К) | Звонящий | При возврате структуры/класса вызывающий код выделяет пространство и передает указатель на это пространство через скрытый параметр в стеке. Вызванная функция записывает возвращаемое значение по этому адресу.
Стек выровнен по границе 16 байт из-за ошибки. | |
CDEC | Майкрософт | РТЛ (К) | Звонящий | При возврате структуры/класса
Стек выровнен по границе 4 байт. | ||
стандартный вызов | Майкрософт | РТЛ (К) | Callee | Также поддерживается GCC. | ||
быстрый вызов | Майкрософт | ЭКХ, EDX | РТЛ (К) | Callee | Возвращает указатель на стек, если это не функция-член. Также поддерживается GCC. | |
зарегистрироваться | Дельфи , Бесплатный Паскаль , Linux [30] | ЕАХ, EDX, ЭКХ | LTR (Паскаль) | Callee | ||
этот звонок | Windows ( Майкрософт Visual С++ ) | ЭКХ | РТЛ (К) | Callee | По умолчанию для функций-членов. | |
векторный вызов | Windows ( Майкрософт Visual С++ ) | ECX, EDX, [XY]MM0–5 | РТЛ (К) | Callee | Расширено из fastcall. Также поддерживается ICC и Clang. [11] | |
Компилятор Ваткома | ЕАХ, EDX, EBX, ЭКХ | РТЛ (К) | Callee | Указатель возврата в ESI. | ||
х86-64 | Соглашение о вызовах Microsoft x64 [21] | Windows ( Microsoft Visual C++ , GCC , компилятор Intel C++ , Delphi ), UEFI | RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 | РТЛ (К) | Звонящий | Стек выровнен по 16 байтам. 32 байта теневого пространства в стеке. Указанные 8 регистров можно использовать только для параметров с 1 по 4. Для классов C++ скрытые this Параметр является первым параметром и передается в RCX. [31]
|
векторный вызов | Windows ( Microsoft Visual C++ , Clang, ICC) | RCX/[XY]MM0, RDX/[XY]MM1, R8/[XY]MM2, R9/[XY]MM3 + [XY]MM4–5 | РТЛ (К) | Звонящий | Расширено из MS x64. [11] | |
Система V AMD64 ABI [28] | Солярис , Линукс , [32] BSD , macOS , OpenVMS ( GCC , компилятор Intel C++ , Clang , Delphi ) | RDI, RSI, RDX, RCX, R8, R9, [XYZ]MM0–7 | РТЛ (К) | Звонящий | Стек выровнен по границе 16 байт. 128-байтовая красная зона под стеком. Интерфейс ядра использует RDI, RSI, RDX, R10, R8 и R9. В С++ this это первый параметр.
|
Ссылки
[ редактировать ]Сноски
[ редактировать ]- ^ Jump up to: а б с д и Туман, Агнер (16 февраля 2010 г.). Соглашения о вызовах для разных компиляторов и операционных систем C++ (PDF) .
- ^ Поллард, Джонатан де Бойн (2010). «Генерация соглашений о вызове функций» . Часто встречающиеся ответы .
- ^ «GCC Bugzilla — ошибка 40838 — gcc не должен предполагать, что стек выровнен» . 2009.
- ^ «Двоичный интерфейс приложения System V: дополнение к процессору с архитектурой Intel 386» (PDF) (4-е изд.).
- ^ "__stdcall (C++)" . MSDN . Майкрософт. Архивировано из оригинала 10 апреля 2008 г. Проверено 13 февраля 2019 г.
- ^ "__fastcall" . MSDN . Проверено 26 сентября 2013 г.
- ^ Озе, Уве. «Обзор атрибутов gcc: функция fastcall» . ohse.de. Проверено 27 сентября 2010 г.
- ^ «Атрибуты в Clang: fastcall» . Кланг Документация . 2022 . Проверено 15 декабря 2022 г.
- ^ Паточка, Микулаш (11 августа 2009 г.). «Соглашение о вызовах Fastcall несовместимо с Windows» . Проверено 15 декабря 2022 г.
- ^ «Представляем «Соглашение о векторных вызовах» » . MSDN. 11 июля 2013 года . Проверено 31 декабря 2014 г.
- ^ Jump up to: а б с д "__векторный вызов" . MSDN . Проверено 31 декабря 2014 г.
- ^ «Атрибуты в Clang: векторный вызов» . Кланг Документация . 2022 . Проверено 15 декабря 2022 г.
- ^ «Соглашения о вызовах C/C++» . 16 декабря 2022 г. Проверено 15 декабря 2022 г.
- ^ «_vectorcall и __regcall развенчаны» . программное обеспечение.intel.com . 7 июня 2017 г.
- ^ «Управление программой: соглашение о регистрации» . docwiki.embarcadero.com. 01.06.2010 . Проверено 27 сентября 2010 г.
- ^ «_fastcall, __fastcall» . docwiki.embarcadero.com.
- ^ "__msfastcall" . docwiki.embarcadero.com.
- ^ «Атрибуты функций x86» . Использование коллекции компиляторов GNU (GCC) .
- ^ «i386: всегда включать regparm» .
- ^ «Calling_Conventions: Specifying_Calling_Conventions_the_Watcom_Way» . openwatcom.org. 27 апреля 2010 г. Архивировано из оригинала 8 марта 2021 г. Проверено 31 августа 2018 г.
- ^ Jump up to: а б «Соглашения о программном обеспечении x64: соглашения о вызовах» . msdn.microsoft.com. 2010 . Проверено 27 сентября 2010 г.
- ^ «Архитектура x64» . msdn.microsoft.com. 6 января 2023 г.
- ^ «Соглашение о вызовах x64: возвращаемые значения» . docs.microsoft.com . Проверено 17 января 2020 г.
- ^ «Соглашения о программном обеспечении x64 — распределение стека» . Майкрософт . Проверено 31 марта 2010 г.
- ^ Jump up to: а б «Сохраненные регистры вызывающего/вызываемого абонента» . Документы Майкрософт . Майкрософт. 18 мая 2022 г.
- ^ «Модель кода x86-64» . Библиотека разработчиков Mac . Apple Inc. Архивировано из оригинала 10 марта 2016 г. Проверено 6 апреля 2016 г.
Среда x86-64 в OS X имеет только одну модель кода для кода пользовательского пространства. Она больше всего похожа на небольшую модель PIC, определенную в x86-64 System V ABI.
- ^ «Стандарт вызовов VSI OpenVMS» (PDF) . vmssoftware.com . Май 2020 года . Проверено 21 декабря 2020 г.
- ^ Jump up to: а б с д и ж г час я дж Лу, HJ; Мац, Майкл; Гиркар, Милинд; Губичка, Ян; Йегер, Андреас; Митчелл, Марк, ред. (23 мая 2023 г.). «Двоичный интерфейс приложения System V: дополнение к процессору архитектуры AMD64 (с моделями программирования LP64 и ILP32), версия 1.0» (PDF) . ГитЛаб . 1.0.
- ^ Руководство пользователя Borland C/C++ версии 3.1 (PDF) . Борланд. 1992. стр. 158, 189–191.
- ^ «Соглашения о вызовах в 32-разрядной версии Linux» . Проверено 22 апреля 2024 г.
- ^ «Использование регистра» . Документы Майкрософт . Майкрософт. Архивировано из оригинала 15 сентября 2017 года . Проверено 15 сентября 2017 г.
- ^ «Соглашения о вызовах в 64-битной версии Linux» . Проверено 22 апреля 2024 г.
Другие источники
[ редактировать ]- Двоичный интерфейс приложения System V: Дополнение к процессору с архитектурой Intel386 (PDF) (4-е изд.). Операция Санта-Крус, Inc., 19 марта 1997 г.
- Трифунович, Неманья (22 июля 2001 г.). Юингтон, Шон (ред.). «Разоблачение условностей вызова» . Проект Кодекса .
- Фридл, Стивен Дж. «Соглашения о вызовах функций Intel x86 – представление сборки» . Технические советы Стива Фридла по Unixwiz.net .
- «Visual Studio 2010 — Соглашение о вызовах Visual C++» . Библиотека MSDN . Майкрософт. 2010.
- Йонссон, Андреас (13 февраля 2005 г.). «Соглашения о вызовах на платформе x86» .
- Чен, Раймонд (2 января 2004 г.). «История конвенций о вызовах, часть 1» . Старая новая вещь .
- Чен, Раймонд (7 января 2004 г.). «История конвенций о вызовах, часть 2» . Старая новая вещь .
- Чен, Раймонд (8 января 2004 г.). «История конвенций о вызовах, часть 3» . Старая новая вещь .
- Чен, Раймонд (13 января 2004 г.). «История соглашений о вызовах, часть 4: ia64» . Старая новая вещь .
- Чен, Раймонд (14 января 2004 г.). «История соглашений о вызовах, часть 5; amd64» . Старая новая вещь .
Дальнейшее чтение
[ редактировать ]- де Бойн Поллард, Джонатан (2010). «Генерация соглашений о вызове функций» . Часто встречающиеся ответы .
- Ирвин, Кип Р. (2011). «Расширенные процедуры (глава 8)». Язык ассемблера для процессоров x86 (6-е изд.). Прентис Холл. ISBN 978-0-13-602212-1 .
- Руководство пользователя Borland C/C++ версии 3.1 (PDF) . Борланд. 1992. стр. 158, 189–191.
- Лауэр, Томас (1995). «Новая последовательность вызовов __stdcall». Портирование на Win32: руководство по подготовке ваших приложений к 32-битному будущему Windows . Спрингер. ISBN 978-0-387-94572-9 .