Привіт і ласкаво просимо назад.
У цьому новому розділі ми вивчатимемо стек викликів.
Стек викликів - це історія пам'яті всіх викликів функції.
Що таке стек викликів?
Кожного разу, коли викликається функція, в пам'яті створюється кадр.
Оскільки кожна функція викликає іншу функцію і так далі, буде
створено багато фреймів.
Створюється список кадрів, починаючи з першого кадру, батьківського кадру, і закінчуючи наступними кадрами,
дочірніми
кадрами.
Зв'язком усіх цих фреймів є те, що ми називаємо стеком викликів.
Він складається з пов'язаного списку.
Що знаходиться всередині кожного кадру?
Як ми вже вивчали у попередніх уроках,
всередині кожного фрейму ви знайдете аргументи функції,
адресу повернення,
старе - та локальні змінні.
Наприклад, виклик функції.
Як створюється стек викликів?
Тож давайте подивимось на це.
Стек викликів створюється наступним чином.
Кожного разу, коли ви викликаєте функцію, вона матиме власний фрейм, який складається
з параметрів, що були
їй передані, адреси повернення, -, та деяких локальних змінних.
Наприклад, якщо тепер функція X збирається викликати функцію Y, перше, що вона зробить, це виштовхне аргументи
функції Y до стеку.
Тож - переїдемо сюди.
Візьмемо,
наприклад, ось тут ми виштовхуємо аргументи до стеку.
Натисніть 20 і натисніть 10.
Це аргументи для функції Y.
Тож він виштовхує їх у зворотному порядку до стеку.
Далі буде здійснено виклик функції Y.
Ось цей дзвінок.
Отже, коли вона викликає функцію Y, вона виштовхує адресу повернення до стеку.
Таким чином, зворотна адреса також потрапить до стеку.
Це повинно піти вгору.
Це буде зворотна адреса.
Зворотна адреса, як ми вже дізналися раніше, - це адреса після дзвінка, ось ця адреса.
Далі він перейде до функції Y.
Після того, як ви опинитеся у функції
Y, функція Y виконає те, що називається прологом функції.
Тепер кожна функція має пролог функції, який складається
з цих двох інструкцій: push - і move -, -, а також епілог функції, який складається з цих двох інструкцій:
move -, - і pop -, що фактично є протилежністю прологу функції.
Отже, коли ми викликаємо Y, функцію Y, перше,
що робить
функція Y, це виконує свій пролог.
Так ви створюєте рамку.
Перше, що відбувається у пролозі функції, - це виштовхування - до стеку.
Чому?
Тому що він повинен відстежувати зворотну адресу попереднього кадру.
Ось чому він штовхає до стека.
Тож у нас є копія в стосі.
Тож коли він натисне на штабель, ця штука підніметься вгору, а стара - вниз.
- виштовхується на стік.
Тоді він буде рухатися -, -.
Таким чином, ефект переміщення - до - полягає в тому, що - до вказує на ту саму адресу, на яку зараз вказує
-.
Після цього буде створено локальні змінні.
Якщо у вас одна локальна змінна, ви виконаєте sub -, 4.
Якщо дві локальні змінні, ви виконуєте sub -, 8 і так далі, як ми
вивчали раніше.
Отже, у вас є одна локальна змінна.
Отже, ви виконуєте
sub -, 4. Тож - піднімається вгору. Після цього вона виштовхне аргументи для функції S.
Зверніть увагу, що всі ці функції є вкладеними.
Отже, функція Y вкладена у функцію X.
Функція S вкладена у функцію
Y, а функція T вкладена у функцію S.
Функція X викликає функцію Y.
Функція Y викликає функцію S.
Функція S викликає функцію T.
Отже, в цей момент часу ми можемо сказати, що ця область є рамкою функції X,
а ця область є рамкою функції Y.
Ось так створюється стек викликів.
Давайте зробимо спостереження.
Ми знаємо, що "-" зберігається у своєму поштовху до
стека тут, ось тут. Щоразу, коли ви вводите нову функцію, перше, що вона робить, це виштовхує її до стеку.
Отже, це адреса - яка знаходиться тут.
Він вказує на це місце.
Адреса всередині ось тут, вказує сюди.
Таким чином, два фрейми пов'язані між собою через -адресу.
Таким чином, стара адреса зберігається у новому фреймі тут, і, подивившись
на вміст, слово dword тут, ви дізнаєтеся, де знаходиться старий -, і зможете
відстежити, де знаходиться старий фрейм стека тут.
І так можна продовжувати і продовжувати.
Кожного разу, коли ви викликаєте функцію з попередньої функції, батьківської функції, дочірня функція буде
мати старий -.
Наприклад, у вас
є 1, 2, 3, 4 функції: функція X викликає
функцію Y, функція Y викликає функцію S, а функція S викликає функцію T.
Це створить у пам'яті таку ситуацію, як
кадр стека.
Тож давайте спробуємо зрозуміти, як це працює, подивившись на цей реальний код ось тут.
І тоді функція X тепер буде виштовхувати параметри для функції Y у стек.
Таким чином, він виштовхне 20 і 10 до стеку.
Тож стек піде сюди, локальні аргументи.
Для цієї нової функції я поставив три пунктирні лінії, щоб зробити її коротшою.
Насправді, внизу знаходяться аргументи функції.
Наступне, що вона зробить, це після того, як вона помістить ці два аргументи в стек у стані готовності до виклику, а потім
викличе Y.
Отже, наслідком виклику Y є виштовхування адреси повернення до стеку.
Тож - тепер переїдемо сюди.
Далі ви перейдете до функції Y.
Як тільки ви потрапляєте
у функцію Y, перше, що робить функція Y, це натискає -.
Тож він буде штовхати - до штабеля.
Після цього ви створите місце для локальної змінної. Таким чином, ви віднімаєте всі необхідні байти для створення локальної
змінної, у цьому випадку однієї локальної змінної.
Потім ти робиш все, що хочеш.
Тепер сталося так, що функція Y також хоче викликати функцію S.
Таким чином, вона виштовхне параметри функції S до стеку.
Тож це спонукатиме до того, щоб вирости тут.
А вже після цього ви викличете функцію S.
Отже, виклик функції S призведе до того, що вона виштовхне адресу повернення, якою тут є ця адреса, до стеку.
Таким чином, зворотна адреса тепер виштовхується до стеку.
Зараз він вказує сюди.
Потім він перейде до функції S.
Коли ви перебуваєте у функції S, функція S буде повторювати те саме.
Штовхаєш - до стопки, штовхаєш - до стопки.
Це означає, що адреса - знаходиться там, а потім вона переміститься - до -.
Отже, ви пересуваєте -, - і - буде вказувати сюди.
Потім він виконає підпрограму для створення локальних змінних.
Потім він буде передавати параметри для функції T, тому що він буде викликати функцію T.
Тож ми вводимо туди параметри.
Потім викликається функція T.
Тепер він виштовхне зворотну адресу до стеку.
Тоді він викличе T.
Після виклику T функція T виштовхне - у стек.
Ми штовхаємо адресу цього до стеку, і тоді він - ми зараз
тут - переміститься
- до -.
Отже - підніметься сюди, а потім створить локальні змінні ось тут, локальні
змінні.
Після цього він буде віднімати.
Він буде робити все, що захоче.
Коли він закінчить робити все, він зробить все навпаки.
Тож тепер
стік розкручується.
Тож, щоб розмотати її, вона буде рухатися від - до -.
Це призведе до того, що це прийде сюди.
А потім вискочить -.
Отже, ви переносите стару адресу в -.
Стара адреса вказує сюди,
тож - приїжджайте сюди.
Коли ви натиснете, "-" також переміститься вниз, і він повернеться назад.
Коли ви ret, ефект
ret полягає в тому, що з'являється зворотна адреса.
Ви вводите зворотну адресу в поле - зареєструватися, щоб ви могли повернутися до цієї адреси тут, зворотної
адреси.
Отже, коли ви натискаєте на нього, він переходить на зворотну
адресу тут, і - знову спускається вниз на dword.
І тепер ви знаходитесь всередині функції S. Функція S тепер тут.
Потім ви очистите стек, додавши 8 до -.
Тож додайте -, -, -, - піде сюди.
Потім ви переміститеся - до -, -.
- призведе до того, що ми вкажемо сюди.
Тож ефект фактично однаковий.
Це зайве, коли ви робите це.
Насправді ви вже очищаєте стік,
тому ми просто додали це для того, щоб вам було зрозуміло, що нам потрібно очистити стік.
Але у реальному коді ви цього не побачите, оскільки ця частина епілогу функції вже очищує
стек.
Наступне, що ми робимо, це ти натискаєш "-".
Тепер вони рухаються -.
А тепер ти натиснеш "-".
Тож - вискочить.
Ви натискаєте це значення, і це призведе до того, що - перейде в - регістр.
Тепер регістр - буде вказувати вниз.
- також просунулася вгору завдяки слову "dword", тому що ти вискакуєш. Тоді він повернеться.
Таким чином, ефект ret полягає в тому, що він змінює зворотну адресу на -.
Щоб ви могли повернутися до адреси повернення у функції Y, яка знаходиться тут.
Тож, коли ти її натискаєш, ти падаєш. - зменшиться, і ви повернетесь
до функції Y.
Потім ви знову чистите стопку.
Тож ти чистиш стопку.
Після цього ви пересуваєте його.
Ви переміщуєте - на -, що є тим самим, що і чистий стек.
Знову ж таки, це зайве.
Ця частина надлишкова.
Власне, в коді тільки це, як я вже згадував раніше.
Отже, ви пересуваєте -, -.
Тож він залишається там, де він є, без змін.
Потім ти натискаєш "-". Коли ви натиснете -, - перейде
на цю адресу, яка знаходиться тут, а - буде рухатися вниз
по d- по d-слову.
Ось так.
І нарешті ви вводите зворотну адресу через ret.
Таким чином, ret змусить програму повернутися за цією зворотною адресою.
Тож коли ти вискакуєш, то потрапляєш сюди.
Тепер ви знаходитесь всередині функції X, за цією адресою. Потім ви знову виконаєте чистий стек.
Тож він знову почистить це.
Знову ж таки, це зайве, тому що наступна інструкція робить те саме.
Він буде рухатися від - до -.
Отже -, -, тепер рухайтеся, і
ви вказуєте на ту саму адресу.
Потім ти натискаєш "-".
- зараз спуститься сюди.
І, нарешті, він повернеться.
Тож ти повертаєшся до цього
місця, ось сюди.
Тож це завершено, це.
На цьому
ви завершили розмотування стопки.
Ось так створюється і розмотується стек.
І процес виконання, відстеження цього називається обхід стеку, стеку
викликів, обхід
стеку викликів.
Отже, в наступному відео я покажу вам практичну частину всього цього коду.
Дякуємо за перегляд.
Побачимося в наступному.