reverseenengineering:соглашения

Это старая версия документа!


Соглашения - Calling Conventions на x86 и x64 с примерами и схемами

toc

  • Calling convention — соглашение о способе передачи аргументов функции, возврате значений и очистке стека.
  • Важен при низкоуровневой разработке, реверс-инжиниринге, работе с чужим кодом, ассемблерной вставке.
  • Различие соглашений: кто и как передаёт аргументы, кто очищает стек, используется ли frame pointer.
  • x86 и Win64 кардинально отличаются!

Convention Порядок аргументовКак передаютсяКто чистит стекВозврат значенияИмя функцииГде применяется
cdecl |Cправа налево |Стек |Caller |EAX |_func |C/varargs | |stdcall Cправа налево Стек Callee EAX _func@N WinAPI

* Аргументы: справа налево через стек.

  • Стек чистит вызывающий (caller).
  • Хорошо подходит для функций с переменным числом аргументов (printf и др).
  • Имя: обычно с нижним подчёркиванием на уровне объектного файла.
  • Возврат: EAX.
int sum(int a, int b) { return a + b; }
push 2          ; b
push 1          ; a
call sum
add esp, 8      ; очищаем стек
sum:
    push ebp
    mov ebp, esp
    sub esp, 4          ; локальная переменная
    mov eax, [ebp+8]    ; a
    add eax, [ebp+12]   ; b
    mov esp, ebp
    pop ebp
    ret

__stdcall

  • Аргументы: справа налево через стек.
  • Стек чистит вызываемая функция (callee).
  • Переменные аргументы невозможны.
  • Имя: _func@N (символ + байты аргументов).
  • Возврат: EAX.
push 2
push 1
call sum@8
; нет add esp, 8!
...
sum@8:
    push ebp
    mov ebp, esp
    sub esp, 4
    ...логика...
    mov esp, ebp
    pop ebp
    ret 8              ; callee очищает стек

__fastcall

  • 1-й аргумент в ECX, 2-й в EDX, остальные через стек.
  • Стек чистит callee (ret N).
  • Имя: @func@N.
  • Возврат: EAX.
push 3          ; 3-й аргумент
mov edx, 2      ; 2-й
mov ecx, 1      ; 1-й
call sum@12
...             ; нет add esp
sum@12:
    push ebp
    mov ebp, esp
    ...
    mov esp, ebp
    pop ebp
    ret 0Ch

thiscall

  • Для методов С++.
  • Указатель this — ECX, остальные аргументы через стек.
  • Стек чистит caller.
  • Имя видоизменено по схеме C++ манглинга.
  • Используется только для x86.
push 2            ; b
push 1            ; a
mov ecx, obj      ; this
call Foo::bar
add esp, 8

Prologue (вход функции)

push ebp              ; сохранить старый frame pointer
mov ebp, esp          ; установить новый frame pointer (ebp)
sub esp, X            ; выделить X байт под локальные переменные

Epilogue (выход из функции)

mov esp, ebp
pop ebp
ret / ret N           ; ret N для stdcall/fastcall

Локальные переменные и аргументы

  • Аргументы функции: [ebp+8], [ebp+12] и т.д. (ebp+4 — return address).
  • Локальные переменные: [ebp-4], [ebp-8], …, для каждой следующей — минус по 4 байта.

Пример с комментариями

sum:
    push ebp                ; сохранить ebp
    mov ebp, esp            ; новый frame pointer
    sub esp, 8              ; 2 локальных переменных (например)
    mov eax, [ebp+8]        ; первый аргумент a
    add eax, [ebp+12]       ; второй b
    mov [ebp-4], eax        ; локалка c
    ...
    mov esp, ebp
    pop ebp
    ret / ret N

Передача аргументов

  • Первые 4 аргумента — через регистры: RCX, RDX, R8, R9 (или XMM0–XMM3 для float/double).
  • 5-й и далее — через стек ([rsp+N]).
  • Caller ОБЯЗАН выделять 32 байта «shadow space» перед вызовом (sub rsp, 28h).
  • Других соглашений под Win64 нет!

Prologue (вход функции)

sub rsp, 28h            ; shadow space (32 байта) + выравнивание (обычно)
; если нужны локальные — sub rsp, больше
  • Обычно frame pointer (rbp) не используется. Если включени дебаг-опции — возможно будет push rbp; mov rbp, rsp.
  • Аргументы сразу доступны по rcx, rdx, r8, r9.
  • Shadow space: [rsp+0], [rsp+8], [rsp+16], [rsp+24]

Epilogue (выход функции)

add rsp, 28h
ret

Локальные переменные

  • Выделяются через sub rsp, XX.
  • Доступ — [rsp+смещение] (если не используется frame pointer).

Дизассемблированный x64-пример (MSVC / x64dbg)

sum:
    sub     rsp, 28h              ; Shadow space
    mov     eax, ecx              ; 1-й аргумент
    add     eax, edx              ; 2-й
    add     eax, r8d              ; 3-й
    add     eax, r9d              ; 4-й
    shl     eax, 1                ; умножение на 2
    add     rsp, 28h
    ret
  • Если есть 5-й и далее аргумент: размещается в памяти, брать через [rsp+40h], [rsp+48h] и т.д.

Пример вызова (caller, x64)

mov     ecx, 1        ; первый аргумент
mov     edx, 2        ; второй
mov     r8d, 3        ; третий
mov     r9d, 4        ; четвертый
sub     rsp, 28h      ; shadow space
; здесь push или mov в [rsp+40h] пятый/шестой/и т.д.
call    sum
add     rsp, 28h

Важные примечания Win64 ABI

  • Перед каждым call stack должен быть выровнен на 16 байт!
  • Shadow (home) space обязателен, даже если всего 1 аргумент.
  • Все значения до 4-х аргументов необходимо сохранять там callee-стороне при необходимости (spill).
  • strconv, printf и другие vararg: после 4-го аргумента значения только через стек.
  • Возврат: через RAX (int/pointer) или XMM0 (float/double).

Отличие от Linux x64 (System V ABI)

  • Под Linux: первые шесть аргументов кладутся в RDI, RSI, RDX, RCX, R8, R9.
  • Нет shadow space.
  • Не путать с Win64!

  • cdecl: после call всегда add esp, X (caller очищает стек).
  • stdcall/fastcall: после call нет add esp, а внутри функции — ret X (callee очищает).
  • fastcall: часть аргументов переданы через ecx/edx, остальные стек.
  • thiscall: ecx = this.
  • Win64: всегда используются rcx/rdx/r8/r9 (caller), выделяется 28h (или >=28h) перед call (shadow space).
  • Внутри функции часто нет frame pointer, доступ к локальным напрямую через rsp.

Значение Комментарий
———-————–————————————————–
rsp+0 Shadow RCX 1-й аргумент (копия)
rsp+8 Shadow RDX 2-й
rsp+16 Shadow R8 3-й
rsp+24 Shadow R9 4-й
rsp+32 5-й 5-й аргумент (через стек)
rsp+40 6-й 6-й аргумент

  • Все функции вызываются с выравнием и shadow space.
  • До 4-х аргументов — через rcx/rdx/r8/r9 (или xmm0–xmm3).
  • 5+ — через стек по адресам выше shadow space.
  • Внутри функции часто нет frame pointer, локальные и временные размещаются через rsp.
  • После call caller обязан вернуть rsp к прежнему значению (add rsp, XX).

int foo(int a, int b) { return a+b; }
foo:
    mov eax, ecx   ; a
    add eax, edx   ; b
    ret
mov ecx, 5
mov edx, 6
sub rsp, 28h      ; выделил shadow space + выравнивание
call foo
add rsp, 28h
  • Первые 4 аргумента — rcx, rdx, r8, r9 + копии кладутся в shadow space.
  • 5-й и выше на стек
  • Внутри функции printf читает все аргументы из shadow space + стек.

Win64 calling convention:

  • Первые 4 аргумента — через rcx, rdx, r8, r9
  • Shadow space (32 байта) обязателен перед каждым call
  • 5+ аргументов размещаются на стеке выше shadow
  • Возврат значения — через rax
  • Frame pointer часто не используется
  • После call восстановить rsp!

  • reverseenengineering/соглашения.1752677210.txt.gz
  • Последнее изменение: 2025/07/16 21:46
  • Lex