Различия
Показаны различия между двумя версиями страницы.
| Следующая версия | Предыдущая версия | ||
| reverseenengineering:соглашения [2025/06/24 00:32] – создано Lex | reverseenengineering:соглашения [2025/07/16 21:46] (текущий) – [Соглашения - Calling Conventions на x86 и x64 с примерами и схемами] Lex | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | ====== Соглашения ====== | + | ====== Соглашения |
| + | |||
| + | |||
| + | |||
| + | |||
| + | ==== Термины и основы ==== | ||
| + | * **Calling convention** — соглашение о способе передачи аргументов функции, | ||
| + | * Важен при низкоуровневой разработке, | ||
| + | * Различие соглашений: | ||
| + | * __x86 и Win64 кардинально отличаются!__ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ==== x86: Основные calling conventions ==== | ||
| + | |||
| + | ^Convention | ||
| + | |__cdecl | ||
| + | |__stdcall | ||
| + | |__fastcall | ||
| + | |thiscall | ||
| + | |||
| + | --- | ||
| + | |||
| + | === __cdecl === | ||
| + | * Аргументы: | ||
| + | * Стек чистит вызывающий (caller). | ||
| + | * Хорошо подходит для функций с переменным числом аргументов (printf и др). | ||
| + | * Имя: обычно с нижним подчёркиванием на уровне объектного файла. | ||
| + | * Возврат: | ||
| + | |||
| + | ==== Пример (cdecl, x86): ==== | ||
| + | <code asm> | ||
| + | int sum(int a, int b) { return a + b; } | ||
| + | </ | ||
| + | <code asm> | ||
| + | push 2 ; b | ||
| + | push 1 ; a | ||
| + | call sum | ||
| + | add esp, 8 ; очищаем стек | ||
| + | </ | ||
| + | <code asm> | ||
| + | sum: | ||
| + | push ebp | ||
| + | mov ebp, esp | ||
| + | sub esp, 4 ; локальная переменная | ||
| + | mov eax, [ebp+8] | ||
| + | add eax, [ebp+12] | ||
| + | mov esp, ebp | ||
| + | pop ebp | ||
| + | ret | ||
| + | </ | ||
| + | |||
| + | --- | ||
| + | |||
| + | === __stdcall === | ||
| + | * Аргументы: | ||
| + | * Стек чистит вызываемая функция (callee). | ||
| + | * Переменные аргументы невозможны. | ||
| + | * Имя: _func@N (символ + байты аргументов). | ||
| + | * Возврат: | ||
| + | |||
| + | ==== Пример (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 очищает стек | ||
| + | </ | ||
| + | |||
| + | --- | ||
| + | |||
| + | === __fastcall === | ||
| + | * 1-й аргумент в ECX, 2-й в EDX, остальные через стек. | ||
| + | * Стек чистит callee (ret N). | ||
| + | * Имя: @func@N. | ||
| + | * Возврат: | ||
| + | |||
| + | ==== Пример (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 | ||
| + | </ | ||
| + | |||
| + | --- | ||
| + | |||
| + | === 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 | ||
| + | </ | ||
| + | |||
| + | --- | ||
| + | |||
| + | ==== Детали реализации (prologue/ | ||
| + | |||
| + | === Prologue (вход функции) === | ||
| + | <code asm> | ||
| + | push ebp ; сохранить старый frame pointer | ||
| + | mov ebp, esp ; установить новый frame pointer (ebp) | ||
| + | sub esp, X ; выделить X байт под локальные переменные | ||
| + | </ | ||
| + | |||
| + | === Epilogue (выход из функции) === | ||
| + | <code asm> | ||
| + | mov esp, ebp | ||
| + | pop ebp | ||
| + | ret / ret N ; ret N для stdcall/ | ||
| + | </ | ||
| + | |||
| + | === Локальные переменные и аргументы === | ||
| + | * Аргументы функции: | ||
| + | * Локальные переменные: | ||
| + | |||
| + | === Пример с комментариями === | ||
| + | <code asm> | ||
| + | sum: | ||
| + | push ebp ; сохранить ebp | ||
| + | mov ebp, esp ; новый frame pointer | ||
| + | sub esp, 8 ; 2 локальных переменных (например) | ||
| + | mov eax, [ebp+8] | ||
| + | add eax, [ebp+12] | ||
| + | mov [ebp-4], eax ; локалка c | ||
| + | ... | ||
| + | mov esp, ebp | ||
| + | pop ebp | ||
| + | ret / ret N | ||
| + | </ | ||
| + | |||
| + | --- | ||
| + | |||
| + | ==== x64 (Win64 ABI): подробный разбор ==== | ||
| + | |||
| + | === Передача аргументов === | ||
| + | * Первые 4 аргумента — через регистры: | ||
| + | * 5-й и далее — через стек ([rsp+N]). | ||
| + | * Caller ОБЯЗАН выделять 32 байта «shadow space» перед вызовом (sub rsp, 28h). | ||
| + | * **Других соглашений под Win64 нет!** | ||
| + | |||
| + | === Prologue (вход функции) === | ||
| + | <code asm> | ||
| + | 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 (выход функции) === | ||
| + | <code asm> | ||
| + | add rsp, 28h | ||
| + | ret | ||
| + | </ | ||
| + | |||
| + | === Локальные переменные === | ||
| + | * Выделяются через 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 | ||
| + | </ | ||
| + | |||
| + | * Если есть 5-й и далее аргумент: | ||
| + | |||
| + | === Пример вызова (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 | ||
| + | </ | ||
| + | |||
| + | === Важные примечания Win64 ABI === | ||
| + | * Перед каждым call stack должен быть выровнен на 16 байт! | ||
| + | * Shadow (home) space обязателен, | ||
| + | * Все значения до 4-х аргументов необходимо сохранять там callee-стороне при необходимости (spill). | ||
| + | * strconv, printf и другие vararg: после 4-го аргумента значения только через стек. | ||
| + | * Возврат: | ||
| + | |||
| + | === Отличие от Linux x64 (System V ABI) === | ||
| + | * Под Linux: первые шесть аргументов кладутся в RDI, RSI, RDX, RCX, R8, R9. | ||
| + | * Нет shadow space. | ||
| + | * Не путать с Win64! | ||
| + | |||
| + | --- | ||
| + | |||
| + | ==== Как узнать соглашение вызова по дизассемблированному коду (x64dbg) ==== | ||
| + | |||
| + | * __cdecl__: после call всегда add esp, X (caller очищает стек). | ||
| + | * __stdcall/ | ||
| + | * __fastcall__: | ||
| + | * thiscall: ecx = this. | ||
| + | * __Win64__: всегда используются rcx/ | ||
| + | * Внутри функции часто нет frame pointer, доступ к локальным напрямую через rsp. | ||
| + | |||
| + | --- | ||
| + | |||
| + | ===== Краткая шпаргалка по стеку (Win64, вызов функции с 6 аргументами) ===== | ||
| + | |||
| + | | | Значение | ||
| + | |----------|--------------|--------------------------------------------------| | ||
| + | |rsp+0 | ||
| + | |rsp+8 | ||
| + | |rsp+16 | ||
| + | |rsp+24 | ||
| + | |rsp+32 | ||
| + | |rsp+40 | ||
| + | |||
| + | --- | ||
| + | |||
| + | ===== Мини-конспект для реверса Win64 ===== | ||
| + | |||
| + | * Все функции вызываются с выравнием и shadow space. | ||
| + | * До 4-х аргументов — через rcx/ | ||
| + | * 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 asm> | ||
| + | foo: | ||
| + | mov eax, ecx ; a | ||
| + | add eax, edx ; b | ||
| + | ret | ||
| + | </ | ||
| + | <code asm> | ||
| + | mov ecx, 5 | ||
| + | mov edx, 6 | ||
| + | sub rsp, 28h ; выделил shadow space + выравнивание | ||
| + | call foo | ||
| + | add rsp, 28h | ||
| + | </ | ||
| + | |||
| + | ==== Пример 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! | ||
| + | |||
| + | --- | ||
| + | |||
| + | |||