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

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
reverseenengineering:соглашения [2025/07/16 21:45] Lexreverseenengineering:соглашения [2025/07/16 21:46] (текущий) – [Соглашения - Calling Conventions на x86 и x64 с примерами и схемами] Lex
Строка 1: Строка 1:
 ====== Соглашения - Calling Conventions на x86 и x64 с примерами и схемами ====== ====== Соглашения - Calling Conventions на x86 и x64 с примерами и схемами ======
-[[toc]] 
  
-==== Основные calling conventions (x86, x64 Windows) ==== 
  
-^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     |Оптимизация        | +  * **Calling convention** — соглашение о способе передачи аргументов функции, возврате значений и очистке стека. 
-|thiscall    |this via ECX, потом стек|ECX/стек    |Caller        |EAX             |манглинг     |C++ методы        | +  * Важен при низкоуровневой разработке, реверс-инжиниринге, работе с чужим кодом, ассемблерной вставке. 
-|Win64       |N/              |RCX/RDX/R8/R9/Stack|Caller   |RAX/XMM0        |func         |Windows x64       |+  * Различие соглашений: кто и как передаёт аргументы, кто очищает стек, используется ли frame pointer. 
 +  * __x86 и Win64 кардинально отличаются!__
  
 ---- ----
  
-===== [[:Документация:Ссылки на официальные источники|Официальные источники]] =====+==== x86Основные calling conventions ====
  
-  * [[https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention|MSDN: x64 Calling Convention]] +^Convention  ^Порядок аргументов^Как передаются^Кто чистит стек^Возврат значения^Имя функции^Где применяется^ 
-  * [[https://learn.microsoft.com/en-us/cpp/build/x86-calling-convention|MSDN: x86 Calling Conventions]] +|__cdecl     |Cправа налево     |Стек           |Caller         |EAX             |_func         |C/varargs         | 
-  * [[https://itanium-cxx-abi.github.io/cxx-abi/abi.html|Itanium C++ ABI (для общего развития)]] +|__stdcall   |Cправа налево     |Стек           |Callee         |EAX             |_func@N       |WinAPI            | 
-  * [[https://wiki.osdev.org/Calling_Conventions|OSDev: Calling conventions]]+|__fastcall  |R->L, 1 ECX, 2 EDX, потом стек|Регистр+стек|Callee|EAX|@func@N      |Оптимизация        | 
 +|thiscall    |this via ECX, потом стек|ECX/стек    |Caller         |EAX             |манглинг      |C++ методы        |
  
-----+---
  
-==== ASCII-схема: Стек и аргументы (cdecl/stdcall, x86====+=== __cdecl === 
 +  * Аргументысправа налево через стек. 
 +  * Стек чистит вызывающий (caller). 
 +  * Хорошо подходит для функций с переменным числом аргументов (printf и др)
 +  * Имя: обычно с нижним подчёркиванием на уровне объектного файла. 
 +  * Возврат: EAX.
  
-<code> +==== Пример (cdecl, x86): ==== 
-; До входа в функцию (cdecl/stdcall): +<code asm> 
-(Сверху вниз — адреса растут) +int sum(int a, int b{ return a + b; } 
-[ esp+12 ]   +</code> 
-[ esp+8 ]    +<code asm> 
-[ esp+4 ]    return address +push 2          ; b 
-esp      сохранённый ebp +push 1          ; 
-esp-4 ]    локальная переменная 1 +call sum 
-esp-8 ]    локальная переменная 2+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> </code>
  
-----+---
  
-==== ASCII-схема: Стек и аргументы (Win64, после sub rsp,28h====+=== __stdcall === 
 +  * Аргументысправа налево через стек. 
 +  * Стек чистит вызываемая функция (callee). 
 +  * Переменные аргументы невозможны. 
 +  * Имя: _func@N (символ + байты аргументов)
 +  * Возврат: EAX.
  
-<code> +==== Пример (stdcall, x86): ==== 
-; Сразу после sub rsp,28h до вызова функции: +<code asm> 
-[ rsp+40 ]   6-й аргумент (если он есть+push 2 
-[ rsp+38 ]   5-й аргумент (если он есть) +push 1 
-[ rsp+30 ]   shadow r9 +call sum@8 
-[ rsp+28 ]   shadow r8 +нет add esp, 8! 
-[ rsp+20 ]   shadow rdx +... 
-[ rsp+18 ]   shadow rcx +sum@8: 
-[ rsp+10 ]   выравнивание/локалки +    push ebp 
-[ rsp ]      нижняя граница stack frame +    mov ebp, esp 
- +    sub esp, 4 
-rcx, rdx, r8, r9 — содержат первые 4 аргумента+    ...логика... 
 +    mov esp, ebp 
 +    pop ebp 
 +    ret 8              callee очищает стек
 </code> </code>
  
-----+---
  
-==== Пример: Более сложная функция (x86 cdecl====+=== __fastcall === 
 +  * 1-й аргумент в ECX, 2-й в EDX, остальные через стек
 +  * Стек чистит callee (ret N)
 +  * Имя: @func@N. 
 +  * Возврат: EAX.
  
-<code c+==== Пример (fastcall, x86): ==== 
-// Функция: вычисляет сумму и произведение трёх чиселвозвращает сумму +<code asm
-int example(int a, int b, int c) { +push 3          ; 3-й аргумент 
-    int prod = a * b * c; +mov edx2      ; 2-й 
-    int sum  = a + b + c; +mov ecx, 1      ; 1-й 
-    if (prod > 100) +call sum@12 
-        return sum * 2; +...             ; нет add esp 
-    else +sum@12: 
-        return sum; +    push ebp 
-}+    mov ebp, esp 
 +    ... 
 +    mov esp, ebp 
 +    pop ebp 
 +    ret 0Ch
 </code> </code>
  
 +---
 +
 +=== thiscall ===
 +  * Для методов С++.
 +  * Указатель this — ECX, остальные аргументы через стек.
 +  * Стек чистит caller.
 +  * Имя видоизменено по схеме C++ манглинга.
 +  * Используется только для x86.
 +
 +==== Пример (thiscall, x86): ====
 <code asm> <code asm>
-example: +push 2            ; b 
-    push    ebp +push 1            ; a 
-    mov     ebpesp +mov ecxobj      ; this 
-    sub     esp, 8        ; 2 локалки: prod ([ebp-4]), sum ([ebp-8])+call Foo::bar 
 +add esp, 8 
 +</code>
  
-    mov     eax, [ebp+8]  ; a +---
-    imul    eax, [ebp+12] ; a*b +
-    imul    eax, [ebp+16] ; *c +
-    mov     [ebp-4], eax  ; prod+
  
-    mov     eax, [ebp+8] +==== Детали реализации (prologue/epilogue) на x86 ====
-    add     eax, [ebp+12] +
-    add     eax, [ebp+16] +
-    mov     [ebp-8], eax  ; sum+
  
-    mov     eax[ebp-4] +=== Prologue (вход функции) === 
-    cmp     eax100 +<code asm> 
-    jle     short .Lelse+push ebp              ; сохранить старый frame pointer 
 +mov ebpesp          ; установить новый frame pointer (ebp) 
 +sub espX            ; выделить X байт под локальные переменные 
 +</code>
  
-    mov     eax[ebp-8] +=== Epilogue (выход из функции) === 
-    add     eax, [ebp-8]  sum*2 +<code asm> 
-    jmp     short .Lend+mov esp, ebp 
 +pop ebp 
 +ret / ret N           ret N для stdcall/fastcall 
 +</code>
  
-.Lelse+=== Локальные переменные и аргументы === 
-    mov     eax, [ebp-8]  sum +  * Аргументы функции: [ebp+8], [ebp+12] и т.д. ([[ebp+4]] — return address). 
-.Lend: +  * Локальные переменные: [ebp-4], [ebp-8], ..., для каждой следующей — минус по 4 байта. 
-    mov     esp, ebp + 
-    pop     ebp +=== Пример с комментариями === 
-    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]       ; второй b 
 +    mov [ebp-4], eax        ; локалка c 
 +    ..
 +    mov esp, ebp 
 +    pop ebp 
 +    ret / ret N
 </code> </code>
  
-----+---
  
-==== ПримерБолее сложная функция (Win64 ABI, x64) ====+==== x64 (Win64 ABI)подробный разбор ====
  
-<code c> +=== Передача аргументов === 
-// Функция: возвращает среднее арифметическое аргументов +  * Первые 4 аргумента — через регистры: RCX, RDX, R8, R9 (или XMM0–XMM3 для float/double). 
-int avg6(int a, int b, int c, int d, int e, int f{ +  * 5-й и далее — через стек ([rsp+N]). 
-    return (a + b c + d + e + f/ 6; +  * Caller ОБЯЗАН выделять 32 байта «shadow space» перед вызовом (sub rsp, 28h). 
-}+  * **Других соглашений под Win64 нет!** 
 + 
 +=== Prologue (вход функции=== 
 +<code asm> 
 +sub rsp, 28h            ; shadow space (32 байта) выравнивание (обычно
 +; если нужны локальные — sub rsp, больше
 </code> </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> <code asm>
-avg6+add rsp, 28h 
-    sub     rsp, 28h            ; Shadow Space +ret 
-    mov     eax, ecx            a +</code> 
-    add     eax, edx            b + 
-    add     eax, r8d            c +=== Локальные переменные === 
-    add     eax, r9d            +  * Выделяются через sub rsp, XX. 
-    add     eax, [rsp+40h]      ; e (5) +  * Доступ — [rsp+смещение] (если не используется frame pointer). 
-    add     eax, [rsp+48h]      f (6-й) + 
-    mov     ecx, 6 +=== Дизассемблированный x64-пример (MSVC / x64dbg) === 
-    cdq +<code asm> 
-    idiv    ecx                 ; eax = сумма / 6+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     add     rsp, 28h
     ret     ret
 </code> </code>
  
-ASCII-графика: схема памятных мест после входа в avg6 +  * Если есть 5-й и далее аргумент: размещается в памяти, брать через [rsp+40h], [rsp+48h] и т.д. 
-<code> + 
-                +----------------------+ +=== Пример вызова (caller, x64) === 
-[ rsp+48 ] ---> | f (6-й аргумент    | +<code asm
-[ rsp+40 ] ---> | e (5-й аргумент    | +mov     ecx, 1        ; первый аргумент 
-rsp+28 ] ---> | shadow r9            | +mov     edx, 2        ; второй 
-[ rsp+20 ---> | shadow r8            | +mov     r8d, 3        ; третий 
-[ rsp+18 ] ---> | shadow rdx           | +mov     r9d, 4        ; четвертый 
-rsp+10 ] ---> | shadow rcx           | +sub     rsp, 28h      ; shadow space 
-[ rsp ]    ---> | (--- нижний rsp ---) | +; здесь push или mov в [rsp+40hпятый/шестой/и т.д. 
-                +----------------------++call    sum 
 +add     rsp, 28h
 </code> </code>
  
-----+=== Важные примечания Win64 ABI === 
 +  * Перед каждым call stack должен быть выровнен на 16 байт! 
 +  * Shadow (home) space обязателен, даже если всего 1 аргумент. 
 +  * Все значения до 4-х аргументов необходимо сохранять там callee-стороне при необходимости (spill). 
 +  * strconv, printf и другие vararg: после 4-го аргумента значения только через стек. 
 +  * Возврат: через RAX (int/pointer) или XMM0 (float/double).
  
-==== Пример: Использование Shadow Space для сохранения регистра ====+=== Отличие от Linux x64 (System V ABI) === 
 +  * Под Linux: первые шесть аргументов кладутся в RDI, RSI, RDX, RCX, R8, R9. 
 +  * Нет shadow space. 
 +  * Не путать с Win64!
  
-Если функция делает вызовы других функций, ей иногда нужно сохранять свои аргументы из rcx/rdx/r8/r9 в shadow space (напримердля их восстановления после вызова, который мог бы их затереть):+--- 
 + 
 +==== Как узнать соглашение вызова по дизассемблированному коду (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 rspXX). 
 + 
 +--- 
 + 
 +===== Примеры для практики ===== 
 + 
 +==== Пример 1: Простая функция (Win64====
  
 <code asm> <code asm>
-func: +int foo(int aint b) { return a+b} 
-    sub     rsp28h +</code> 
-    mov     [rsp], rcx          сохранить rcx в shadow space +<code asm> 
-    mov     [rsp+8], rdx +foo: 
-    mov     [rsp+16], r8 +    mov eaxecx   a 
-    mov     [rsp+24]r9 +    add eaxedx   ; b
-    ... +
-    вызовы других функций, которые могут менять rcx, rdx, r8, r9 +
-    ... +
-    ; можно восстановить оригинальные аргументы отсюда +
-    add     rsp28h+
     ret     ret
 +</code>
 +<code asm>
 +mov ecx, 5
 +mov edx, 6
 +sub rsp, 28h      ; выделил shadow space + выравнивание
 +call foo
 +add rsp, 28h
 </code> </code>
  
-----+==== Пример 2: С vararg (Win64, printf) ====
  
-==== Важные ссылки ====+  * Первые 4 аргумента — rcx, rdx, r8, r9 + копии кладутся в shadow space. 
 +  * 5-й и выше на стек 
 +  * Внутри функции printf читает все аргументы из shadow space + стек.
  
-  * [[https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention|MSDN: Win64 Calling Convention (официально)]] +---
-  * [[https://learn.microsoft.com/en-us/cpp/build/x86-calling-convention|MSDN: x86 Calling Conventions (официально)]] +
-  * [[https://reverseengineering.stackexchange.com/questions/15722/what-is-the-purpose-of-shadow-space|ReverseEngineering.SE: Shadow Space объяснение]] +
-  * [[https://wiki.osdev.org/Calling_Conventions|OSDev: Calling conventions]] — большая сводка+
  
-----+===== Заключение =====
  
-==== Мнемоника для запоминания Win64 convention ==== +Win64 calling convention:   
-<code> +  * Первые 4 аргумента — через rcx, rdx, r8, r9 
-RCX 1st           RDX 2nd +  * Shadow space (32 байта) обязателен перед каждым call 
-                    | +  5аргументов размещаются на стеке выше shadow 
-   +----------+---------------+ +  * Возврат значения — через rax 
-   |R8 3rd    |R9 4th                5-й и далее — только на стеке! +  * Frame pointer часто не используется 
-   +----------+---------------+ +  * После call восстановить rsp!
-</code> +
-Минимум 32 байта shadow space до любого вызова (для callee).+
  
-----+---
  
-==== 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. 
- 
----- 
  
  
  • reverseenengineering/соглашения.1752677139.txt.gz
  • Последнее изменение: 2025/07/16 21:45
  • Lex