Jump to content

соглашения о вызовах 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 ]

Очистка звонящего

[ редактировать ]

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

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-битной части ниже) и, следовательно, в основном для особых случаев. Другие языки могут использовать в своих реализациях другие форматы и соглашения.

Archi­tecture Имя Операционная система, компилятор Параметры Очистка стека Примечания
Регистры Порядок стека
8086 CDEC РТЛ (К) Звонящий
Паскаль LTR (Паскаль) Callee
быстрый вызов (не член) Майкрософт АХ, ДХ, ВХ LTR (Паскаль) Callee Указатель возврата в BX.
быстрый вызов (функция-член) Майкрософт ТОПОР, ДХ LTR (Паскаль) Callee this в стеке с младшим адресом. Указатель возврата в AX.
быстрый вызов Турбо С [ 29 ] АХ, ДХ, ВХ LTR (Паскаль) Callee this в стеке с младшим адресом. Указатель возврата по старшему адресу стека.
Ватком АХ, ДХ, ВХ, СХ РТЛ (К) Callee Указатель возврата в СИ.
ИА-32 CDEC Unix-подобный ( GCC ) РТЛ (К) Звонящий При возврате структуры/класса вызывающий код выделяет пространство и передает указатель на это пространство через скрытый параметр в стеке. Вызванная функция записывает возвращаемое значение по этому адресу.

Стек выровнен по границе 16 байт из-за ошибки.

CDEC Майкрософт РТЛ (К) Звонящий При возврате структуры/класса
  • Возвращаемые значения простых старых данных (POD) размером 32 бита или меньше находятся в регистре EAX.
  • Возвращаемые значения POD размером 33–64 бита возвращаются через регистры EAX:EDX.
  • Если возвращаются значения, отличные от POD, или значения размером более 64 бит, вызывающий код выделит пространство и передаст указатель на это пространство через скрытый параметр в стеке. Вызванная функция записывает возвращаемое значение по этому адресу.

Стек выровнен по границе 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 это первый параметр.
  1. ^ Jump up to: а б с д и Туман, Агнер (16 февраля 2010 г.). Соглашения о вызовах для разных компиляторов и операционных систем C++ (PDF) .
  2. ^ Поллард, Джонатан де Бойн (2010). «Генерация соглашений о вызове функций» . Часто встречающиеся ответы .
  3. ^ «GCC Bugzilla — ошибка 40838 — gcc не должен предполагать, что стек выровнен» . 2009.
  4. ^ «Двоичный интерфейс приложения System V: дополнение к процессору с архитектурой Intel 386» (PDF) (4-е изд.).
  5. ^ "__stdcall (C++)" . MSDN . Майкрософт. Архивировано из оригинала 10 апреля 2008 г. Проверено 13 февраля 2019 г.
  6. ^ "__fastcall" . MSDN . Проверено 26 сентября 2013 г.
  7. ^ Озе, Уве. «Обзор атрибутов gcc: функция fastcall» . ohse.de. ​Проверено 27 сентября 2010 г.
  8. ^ «Атрибуты в Clang: fastcall» . Кланг Документация . 2022 . Проверено 15 декабря 2022 г.
  9. ^ Паточка, Микулаш (11 августа 2009 г.). «Соглашение о вызовах Fastcall несовместимо с Windows» . Проверено 15 декабря 2022 г.
  10. ^ «Представляем «Соглашение о векторных вызовах» » . MSDN. 11 июля 2013 года . Проверено 31 декабря 2014 г.
  11. ^ Jump up to: а б с д "__векторный вызов" . MSDN . Проверено 31 декабря 2014 г.
  12. ^ «Атрибуты в Clang: векторный вызов» . Кланг Документация . 2022 . Проверено 15 декабря 2022 г.
  13. ^ «Соглашения о вызовах C/C++» . 16 декабря 2022 г. Проверено 15 декабря 2022 г.
  14. ^ «_vectorcall и __regcall развенчаны» . программное обеспечение.intel.com . 7 июня 2017 г.
  15. ^ «Управление программой: соглашение о регистрации» . docwiki.embarcadero.com. 01.06.2010 . Проверено 27 сентября 2010 г.
  16. ^ «_fastcall, __fastcall» . docwiki.embarcadero.com.
  17. ^ "__msfastcall" . docwiki.embarcadero.com.
  18. ^ «Атрибуты функций x86» . Использование коллекции компиляторов GNU (GCC) .
  19. ^ «i386: всегда включать regparm» .
  20. ^ «Calling_Conventions: Specifying_Calling_Conventions_the_Watcom_Way» . openwatcom.org. 27 апреля 2010 г. Архивировано из оригинала 8 марта 2021 г. Проверено 31 августа 2018 г.
  21. ^ Jump up to: а б «Соглашения о программном обеспечении x64: соглашения о вызовах» . msdn.microsoft.com. 2010 . Проверено 27 сентября 2010 г.
  22. ^ «Архитектура x64» . msdn.microsoft.com. 6 января 2023 г.
  23. ^ «Соглашение о вызовах x64: возвращаемые значения» . docs.microsoft.com . Проверено 17 января 2020 г.
  24. ^ «Соглашения о программном обеспечении x64 — распределение стека» . Майкрософт . Проверено 31 марта 2010 г.
  25. ^ Jump up to: а б «Сохраненные регистры вызывающего/вызываемого абонента» . Документы Майкрософт . Майкрософт. 18 мая 2022 г.
  26. ^ «Модель кода x86-64» . Библиотека разработчиков Mac . Apple Inc. Архивировано из оригинала 10 марта 2016 г. Проверено 06 апреля 2016 г. Среда x86-64 в OS X имеет только одну модель кода для кода пользовательского пространства. Она больше всего похожа на небольшую модель PIC, определенную в x86-64 System V ABI.
  27. ^ «Стандарт вызовов VSI OpenVMS» (PDF) . vmssoftware.com . Май 2020 года . Проверено 21 декабря 2020 г.
  28. ^ Jump up to: а б с д и ж г час я дж Лу, HJ; Мац, Майкл; Гиркар, Милинд; Губичка, Ян; Йегер, Андреас; Митчелл, Марк, ред. (23 мая 2023 г.). «Двоичный интерфейс приложения System V: дополнение к процессору архитектуры AMD64 (с моделями программирования LP64 и ILP32), версия 1.0» (PDF) . ГитЛаб . 1.0.
  29. ^ Руководство пользователя Borland C/C++ версии 3.1 (PDF) . Борланд. 1992. стр. 158, 189–191.
  30. ^ «Соглашения о вызовах в 32-разрядной версии Linux» . Проверено 22 апреля 2024 г.
  31. ^ «Использование регистра» . Документы Майкрософт . Майкрософт. Архивировано из оригинала 15 сентября 2017 года . Проверено 15 сентября 2017 г.
  32. ^ «Соглашения о вызовах в 64-битной версии Linux» . Проверено 22 апреля 2024 г.

Другие источники

[ редактировать ]

Дальнейшее чтение

[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: b9ce6183ee594215181670a4a24fa782__1718561520
URL1:https://arc.ask3.ru/arc/aa/b9/82/b9ce6183ee594215181670a4a24fa782.html
Заголовок, (Title) документа по адресу, URL1:
x86 calling conventions - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)