Показать страницуИстория страницыСсылки сюдаНаверх Эта страница только для чтения. Вы можете посмотреть её исходный текст, но не можете его изменить. Сообщите администратору, если считаете, что это неправильно. ====== Соглашения - Calling Conventions на x86 и x64 с примерами и схемами ====== ==== Термины и основы ==== * **Calling convention** — соглашение о способе передачи аргументов функции, возврате значений и очистке стека. * Важен при низкоуровневой разработке, реверс-инжиниринге, работе с чужим кодом, ассемблерной вставке. * Различие соглашений: кто и как передаёт аргументы, кто очищает стек, используется ли frame pointer. * __x86 и Win64 кардинально отличаются!__ ---- ==== x86: Основные calling conventions ==== ^Convention ^Порядок аргументов^Как передаются^Кто чистит стек^Возврат значения^Имя функции^Где применяется^ |__cdecl |Cправа налево |Стек |Caller |EAX |_func |C/varargs | |__stdcall |Cправа налево |Стек |Callee |EAX |_func@N |WinAPI | |__fastcall |R->L, 1 ECX, 2 EDX, потом стек|Регистр+стек|Callee|EAX|@func@N |Оптимизация | |thiscall |this via ECX, потом стек|ECX/стек |Caller |EAX |манглинг |C++ методы | --- === __cdecl === * Аргументы: справа налево через стек. * Стек чистит вызывающий (caller). * Хорошо подходит для функций с переменным числом аргументов (printf и др). * Имя: обычно с нижним подчёркиванием на уровне объектного файла. * Возврат: EAX. ==== Пример (cdecl, x86): ==== <code asm> int sum(int a, int b) { return a + b; } </code> <code asm> push 2 ; b push 1 ; a call sum add esp, 8 ; очищаем стек </code> <code asm> 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 </code> --- === __stdcall === * Аргументы: справа налево через стек. * Стек чистит вызываемая функция (callee). * Переменные аргументы невозможны. * Имя: _func@N (символ + байты аргументов). * Возврат: EAX. ==== Пример (stdcall, x86): ==== <code asm> 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 очищает стек </code> --- === __fastcall === * 1-й аргумент в ECX, 2-й в EDX, остальные через стек. * Стек чистит callee (ret N). * Имя: @func@N. * Возврат: EAX. ==== Пример (fastcall, x86): ==== <code asm> 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 </code> --- === thiscall === * Для методов С++. * Указатель this — ECX, остальные аргументы через стек. * Стек чистит caller. * Имя видоизменено по схеме C++ манглинга. * Используется только для x86. ==== Пример (thiscall, x86): ==== <code asm> push 2 ; b push 1 ; a mov ecx, obj ; this call Foo::bar add esp, 8 </code> --- ==== Детали реализации (prologue/epilogue) на x86 ==== === Prologue (вход функции) === <code asm> push ebp ; сохранить старый frame pointer mov ebp, esp ; установить новый frame pointer (ebp) sub esp, X ; выделить X байт под локальные переменные </code> === Epilogue (выход из функции) === <code asm> mov esp, ebp pop ebp ret / ret N ; ret N для stdcall/fastcall </code> === Локальные переменные и аргументы === * Аргументы функции: [ebp+8], [ebp+12] и т.д. ([[ebp+4]] — return address). * Локальные переменные: [ebp-4], [ebp-8], ..., для каждой следующей — минус по 4 байта. === Пример с комментариями === <code asm> 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 </code> --- ==== x64 (Win64 ABI): подробный разбор ==== === Передача аргументов === * Первые 4 аргумента — через регистры: RCX, RDX, R8, R9 (или XMM0–XMM3 для float/double). * 5-й и далее — через стек ([rsp+N]). * Caller ОБЯЗАН выделять 32 байта «shadow space» перед вызовом (sub rsp, 28h). * **Других соглашений под Win64 нет!** === Prologue (вход функции) === <code asm> sub rsp, 28h ; shadow space (32 байта) + выравнивание (обычно) ; если нужны локальные — sub rsp, больше </code> * Обычно frame pointer (rbp) не используется. Если включени дебаг-опции — возможно будет push rbp; mov rbp, rsp. * Аргументы сразу доступны по rcx, rdx, r8, r9. * Shadow space: [rsp+0], [rsp+8], [rsp+16], [rsp+24] === Epilogue (выход функции) === <code asm> add rsp, 28h ret </code> === Локальные переменные === * Выделяются через sub rsp, XX. * Доступ — [rsp+смещение] (если не используется frame pointer). === Дизассемблированный x64-пример (MSVC / x64dbg) === <code asm> 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 </code> * Если есть 5-й и далее аргумент: размещается в памяти, брать через [rsp+40h], [rsp+48h] и т.д. === Пример вызова (caller, x64) === <code asm> 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 </code> === Важные примечания 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! --- ==== Как узнать соглашение вызова по дизассемблированному коду (x64dbg) ==== * __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. --- ===== Краткая шпаргалка по стеку (Win64, вызов функции с 6 аргументами) ===== | | Значение | Комментарий | |----------|--------------|--------------------------------------------------| |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-й аргумент | --- ===== Мини-конспект для реверса Win64 ===== * Все функции вызываются с выравнием и shadow space. * До 4-х аргументов — через rcx/rdx/r8/r9 (или xmm0–xmm3). * 5+ — через стек по адресам выше shadow space. * Внутри функции часто нет frame pointer, локальные и временные размещаются через rsp. * После call caller обязан вернуть rsp к прежнему значению (add rsp, XX). --- ===== Примеры для практики ===== ==== Пример 1: Простая функция (Win64) ==== <code asm> int foo(int a, int b) { return a+b; } </code> <code asm> foo: mov eax, ecx ; a add eax, edx ; b ret </code> <code asm> mov ecx, 5 mov edx, 6 sub rsp, 28h ; выделил shadow space + выравнивание call foo add rsp, 28h </code> ==== Пример 2: С vararg (Win64, printf) ==== * Первые 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/соглашения.txt Последнее изменение: 2025/07/16 21:46 — Lex Войти