Выполнение функций и стек
Тема: Что такое функции, вызов функций, stack.
Концепты
Заголовок раздела «Концепты»- Выполнимый файл (executable)
- Функция
- Адрес инструкции, Instruction Pointer
- Stack, Stack Pointer
- Адрес возврата
- Выход из функции
- Возврат значения из функции
- Локальная переменная
Анализ 1
Заголовок раздела «Анализ 1»Возможно ли (технически) перезаписать локальную переменную из другой функции?
Объясните код ниже (код начинает выполняться с функции main).
Этот код не скомпилируется как есть, проанализируйте его на концептуальном уровне.
sp означает stack pointer.
Предположите, что локальные переменные хранятся на стеке, и что адрес возврата занимает 4 байта.
Чему будет равен x и почему?
void f(){ *(sp - 8) = 0; return;}
int main(){ int x; x = 1; f(); return 0; // x = ?}Ответ
Стек будет выглядеть следующим образом.- До строки с вызовом
f();
| sp - 4 | sp |
|---|---|
| x (1) | ?? |
- На первой строке
f:
| sp - 8 | sp - 4 | sp |
|---|---|---|
| x (1) | адрес возврата к return 0; из main | ?? |
- На
return;изf
| sp - 8 | sp - 4 | sp |
|---|---|---|
| x (0) | адрес возврата к return 0; из main | ?? |
- На
return 0;изmain
| sp - 4 | sp |
|---|---|
| x (0) | адрес возврата из main |
sp - 8 при выполнении f ссылается на адрес локальной переменной x из main.
Если предположить, что при попытке возврата на адрес инструкции 0, программа крашится (завершается с ошибкой), как заставить программу выше крашнуться, сменив 1 символ в коде?
Ответ
sp - 8 --> sp - 4, что равно адресу возврата.
Таким образом, сможете перезаписать адрес возврата на 0.
При попытке возврата по этому адресу, программа крашнется.
Подобное явление - это частая ошибка в C, особенно при работе с массивами. Оно также носит имя stack corruption. Компилятор иногда вставляет в стек защиту от этого в режиме debugging, что помогает найти подобные ошибки (погуглите если интересно).
🤓 Раскладка стека в таблицах выше — концептуальное упрощение: что именно попадает в кадр стека (локальные переменные, адрес возврата, сохраненные регистры) и в каком порядке, решает реализация, стандарт этого не предписывает. На практике же на любом современном железе со стандартной раскладкой стека служебные данные кадра — включая адрес возврата — действительно лежат рядом с локальными переменными, поэтому идея упражнения выше жизнеспособна.
Анализ 2
Заголовок раздела «Анализ 2»Бесконечная рекурсия
Код начинает выполняться с функции
main.
Объясните что сделает программа ниже:
void f(){ f(); return;}int main(){ f(); return 0;}При попытке сдвига
spза пределы стека (за пределы максимального адреса, выделенного под стек), случается stack overflow. И программа обычно крашится.