Различия
Показаны различия между двумя версиями страницы.
| Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
| reverseenengineering:соглашения [2025/07/16 21:45] – Lex | reverseenengineering:соглашения [2025/07/16 21:46] (текущий) – [Соглашения - Calling Conventions на x86 и x64 с примерами и схемами] Lex | ||
|---|---|---|---|
| Строка 2: | Строка 2: | ||
| - | ==== Основные calling conventions (x86, x64 Windows) ==== | ||
| - | ^Convention | + | |
| - | |__cdecl | + | ==== Термины и основы ==== |
| - | |__stdcall | + | * **Calling convention** — соглашение о способе передачи |
| - | |__fastcall | + | * Важен |
| - | |thiscall | + | * Различие соглашений: кто и как передаёт аргументы, |
| - | |Win64 | + | * __x86 и Win64 кардинально отличаются!__ |
| ---- | ---- | ||
| - | ===== [[:Документация: | + | ==== x86: Основные |
| - | * [[https:// | + | ^Convention |
| - | * [[https:// | + | |__cdecl |
| - | * [[https:// | + | |__stdcall |
| - | * [[https:// | + | |__fastcall |
| + | |thiscall | ||
| - | ---- | + | --- |
| - | ==== ASCII-схема: Стек и аргументы (cdecl/ | + | === __cdecl |
| + | * Аргументы: справа налево через стек. | ||
| + | * Стек | ||
| + | * Хорошо подходит для функций с переменным числом | ||
| + | * Имя: обычно с нижним подчёркиванием на уровне объектного файла. | ||
| + | * Возврат: | ||
| - | < | + | ==== Пример |
| - | ; До входа в функцию (cdecl/stdcall): | + | <code asm> |
| - | ; (Сверху вниз — адреса растут) | + | int sum(int a, int b) { return a + b; } |
| - | [ esp+12 ] b | + | </ |
| - | [ esp+8 ] | + | <code asm> |
| - | [ esp+4 ] return address | + | push 2 ; b |
| - | [ esp ] сохранённый | + | push 1 ; a |
| - | [ esp-4 ] | + | call sum |
| - | [ esp-8 ] | + | 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 | ||
| </ | </ | ||
| - | ---- | + | --- |
| - | ==== ASCII-схема: Стек и аргументы | + | === __stdcall |
| + | * Аргументы: справа налево через стек. | ||
| + | * Стек | ||
| + | * Переменные | ||
| + | * Имя: _func@N (символ + байты аргументов). | ||
| + | * Возврат: | ||
| - | < | + | ==== Пример (stdcall, x86): ==== |
| - | ; Сразу после sub rsp,28h до вызова функции: | + | <code asm> |
| - | [ rsp+40 ] 6-й аргумент (если он есть) | + | push 2 |
| - | [ rsp+38 ] 5-й аргумент (если он есть) | + | push 1 |
| - | [ rsp+30 ] | + | call sum@8 |
| - | [ rsp+28 ] | + | ; нет |
| - | [ rsp+20 ] | + | ... |
| - | [ rsp+18 ] | + | sum@8: |
| - | [ rsp+10 ] | + | push ebp |
| - | [ rsp ] нижняя | + | mov ebp, esp |
| - | + | sub esp, 4 | |
| - | ; rcx, rdx, r8, r9 — содержат первые 4 аргумента | + | ...логика... |
| + | mov esp, ebp | ||
| + | pop ebp | ||
| + | ret 8 | ||
| </ | </ | ||
| - | ---- | + | --- |
| - | ==== Пример: Более сложная функция (x86 cdecl) ==== | + | === __fastcall |
| + | * 1-й аргумент в ECX, 2-й в EDX, остальные через стек. | ||
| + | * Стек чистит callee | ||
| + | * Имя: @func@N. | ||
| + | * Возврат: | ||
| - | < | + | ==== Пример (fastcall, x86): ==== |
| - | // Функция: | + | < |
| - | int example(int a, int b, int c) { | + | push 3 ; 3-й аргумент |
| - | | + | mov edx, 2 ; 2-й |
| - | | + | mov ecx, 1 ; 1-й |
| - | | + | call sum@12 |
| - | | + | ... ; нет add esp |
| - | | + | sum@12: |
| - | | + | |
| - | } | + | |
| + | | ||
| + | mov esp, ebp | ||
| + | | ||
| + | ret 0Ch | ||
| </ | </ | ||
| + | --- | ||
| + | |||
| + | === thiscall === | ||
| + | * Для методов С++. | ||
| + | * Указатель this — ECX, остальные аргументы через стек. | ||
| + | * Стек чистит caller. | ||
| + | * Имя видоизменено по схеме C++ манглинга. | ||
| + | * Используется только для x86. | ||
| + | |||
| + | ==== Пример (thiscall, x86): ==== | ||
| <code asm> | <code asm> | ||
| - | example: | + | push 2 ; b |
| - | push ebp | + | push 1 ; a |
| - | mov ebp, esp | + | mov ecx, obj ; this |
| - | | + | call Foo::bar |
| + | add esp, 8 | ||
| + | </ | ||
| - | mov eax, [ebp+8] | + | --- |
| - | imul eax, [ebp+12] ; a*b | + | |
| - | imul eax, [ebp+16] ; *c | + | |
| - | mov [ebp-4], eax ; prod | + | |
| - | mov eax, [ebp+8] | + | ==== Детали реализации (prologue/ |
| - | add eax, [ebp+12] | + | |
| - | add eax, [ebp+16] | + | |
| - | mov | + | |
| - | | + | === 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/ | ||
| + | </ | ||
| - | .Lelse: | + | === Локальные переменные и аргументы === |
| - | mov | + | * Аргументы функции: |
| - | .Lend: | + | * Локальные переменные: |
| - | mov | + | |
| - | pop | + | === Пример с комментариями === |
| - | ret | + | <code asm> |
| + | sum: | ||
| + | push ebp ; сохранить ebp | ||
| + | mov ebp, esp ; новый frame pointer | ||
| + | sub esp, 8 ; 2 локальных переменных (например) | ||
| + | mov eax, [ebp+8] ; первый аргумент a | ||
| + | add eax, [ebp+12] | ||
| + | mov [ebp-4], eax ; локалка c | ||
| + | ... | ||
| + | mov esp, ebp | ||
| + | pop ebp | ||
| + | ret / ret N | ||
| </ | </ | ||
| - | ---- | + | --- |
| - | ==== Пример: Более сложная функция (Win64 ABI, x64) ==== | + | ==== x64 (Win64 ABI): подробный разбор |
| - | <code c> | + | === Передача аргументов === |
| - | // Функция: возвращает среднее | + | * Первые 4 аргумента — через регистры: RCX, RDX, R8, R9 (или XMM0–XMM3 для float/ |
| - | int avg6(int a, int b, int c, int d, int e, int f) { | + | * 5-й и далее — через |
| - | | + | * 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> | <code asm> | ||
| - | avg6: | + | add rsp, 28h |
| - | sub rsp, 28h ; Shadow | + | ret |
| - | mov eax, ecx ; a | + | </ |
| - | add eax, edx ; b | + | |
| - | add eax, r8d ; c | + | === Локальные переменные === |
| - | add eax, r9d ; d | + | * Выделяются через sub rsp, XX. |
| - | add eax, [rsp+40h] | + | * Доступ — [rsp+смещение] (если не используется frame pointer). |
| - | | + | |
| - | mov ecx, 6 | + | === Дизассемблированный x64-пример (MSVC / x64dbg) === |
| - | cdq | + | <code asm> |
| - | idiv ecx ; eax = сумма / 6 | + | sum: |
| + | sub rsp, 28h ; Shadow | ||
| + | mov eax, ecx ; 1-й аргумент | ||
| + | add eax, edx ; 2-й | ||
| + | add eax, r8d ; 3-й | ||
| + | add eax, r9d ; 4-й | ||
| + | | ||
| add rsp, 28h | add rsp, 28h | ||
| ret | ret | ||
| </ | </ | ||
| - | ASCII-графика: схема памятных мест после входа в avg6 | + | * Если есть 5-й и далее аргумент: |
| - | < | + | |
| - | | + | === Пример вызова (caller, x64) === |
| - | [ rsp+48 ] ---> | f (6-й аргумент) | + | < |
| - | [ rsp+40 ] ---> | e (5-й аргумент) | + | mov ecx, 1 ; первый аргумент |
| - | [ rsp+28 ] ---> | shadow | + | mov |
| - | [ rsp+20 ] ---> | shadow r8 | | + | mov r8d, 3 ; третий |
| - | [ rsp+18 ] ---> | shadow rdx | | + | mov |
| - | [ rsp+10 ] ---> | shadow rcx | | + | sub rsp, 28h ; shadow |
| - | [ rsp ] ---> | (--- нижний rsp ---) | | + | ; здесь push или mov в [rsp+40h] пятый/ |
| - | +----------------------+ | + | call sum |
| + | add rsp, 28h | ||
| </ | </ | ||
| - | ---- | + | === Важные примечания Win64 ABI === |
| + | * Перед каждым call stack должен быть выровнен на 16 байт! | ||
| + | * Shadow (home) space обязателен, | ||
| + | * Все значения до 4-х аргументов необходимо сохранять там callee-стороне при необходимости (spill). | ||
| + | * strconv, printf и другие vararg: после 4-го аргумента значения только через стек. | ||
| + | * Возврат: | ||
| - | ==== Пример: Использование Shadow Space для сохранения регистра ==== | + | === Отличие от Linux x64 (System V ABI) === |
| + | * Под Linux: первые шесть аргументов кладутся в RDI, RSI, RDX, RCX, R8, R9. | ||
| + | * Нет shadow space. | ||
| + | * Не путать с Win64! | ||
| - | Если функция делает вызовы других функций, ей иногда нужно сохранять свои аргументы из rcx/ | + | --- |
| + | |||
| + | ==== Как узнать | ||
| + | |||
| + | * __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 | ||
| + | |||
| + | --- | ||
| + | |||
| + | ===== Мини-конспект для | ||
| + | |||
| + | * Все функции вызываются с выравнием и shadow space. | ||
| + | * До 4-х аргументов — через rcx/ | ||
| + | * 5+ — через стек по адресам | ||
| + | * Внутри функции часто нет frame pointer, локальные и временные размещаются через rsp. | ||
| + | * После | ||
| + | |||
| + | --- | ||
| + | |||
| + | ===== Примеры для практики ===== | ||
| + | |||
| + | ==== Пример 1: Простая функция (Win64) ==== | ||
| <code asm> | <code asm> | ||
| - | func: | + | int foo(int a, int b) { return a+b; } |
| - | sub rsp, 28h | + | </ |
| - | mov | + | <code asm> |
| - | | + | foo: |
| - | | + | mov eax, ecx ; a |
| - | mov [rsp+24], r9 | + | add eax, edx ; b |
| - | ... | + | |
| - | | + | |
| - | ... | + | |
| - | ; можно восстановить оригинальные аргументы отсюда | + | |
| - | add rsp, 28h | + | |
| ret | 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 + стек. | ||
| - | * [[https:// | + | --- |
| - | * [[https:// | + | |
| - | * [[https:// | + | |
| - | * [[https:// | + | |
| - | ---- | + | ===== Заключение ===== |
| - | ==== Мнемоника для | + | Win64 calling convention: |
| - | < | + | * Первые 4 аргумента — через rcx, rdx, r8, r9 |
| - | RCX 1st RDX 2nd | + | * Shadow space (32 байта) |
| - | | + | |
| - | | + | * Возврат значения — через rax |
| - | |R8 3rd |R9 4th | + | * Frame pointer часто не используется |
| - | +----------+---------------+ | + | * После call восстановить rsp! |
| - | </ | + | |
| - | Минимум 32 байта shadow space до любого вызова | + | |
| - | ---- | + | --- |
| - | ==== FAQ и заметки для реверса ==== | ||
| - | * x64dbg: Если сразу после call идёт add rsp, 28h — это Win64 calling convention. | ||
| - | * Если стек выделяется через sub esp, X и используются push — классическая x86. | ||
| - | * При анализе vararg-функций (printf) всегда смотрите стек выше shadow space — там аргументы! | ||
| - | * Если не виден frame pointer (rbp/ebp), локальные обычно через rsp/esp и смещение. | ||
| - | * Проверяйте стек на выравнивание по 16 байтам в x64. | ||
| - | |||
| - | ---- | ||