Перейти к содержимому

Выполнение функций и стек

Тема: Что такое функции, вызов функций, stack.

Сопровождающее видео 1

Сопровождающее видео 2

  • Выполнимый файл (executable)
  • Функция
  • Адрес инструкции, Instruction Pointer
  • Stack, Stack Pointer
  • Адрес возврата
  • Выход из функции
  • Возврат значения из функции
  • Локальная переменная

Возможно ли (технически) перезаписать локальную переменную из другой функции?

Объясните код ниже (код начинает выполняться с функции main). Этот код не скомпилируется как есть, проанализируйте его на концептуальном уровне. sp означает stack pointer. Предположите, что локальные переменные хранятся на стеке, и что адрес возврата занимает 4 байта. Чему будет равен x и почему?

void f()
{
*(sp - 8) = 0;
return;
}
int main()
{
int x;
x = 1;
f();
return 0; // x = ?
}
Ответ Стек будет выглядеть следующим образом.
  1. До строки с вызовом f();
sp - 4sp
x (1)??
  1. На первой строке f:
sp - 8sp - 4sp
x (1)адрес возврата к return 0; из main??
  1. На return; из f
sp - 8sp - 4sp
x (0)адрес возврата к return 0; из main??
  1. На return 0; из main
sp - 4sp
x (0)адрес возврата из main

sp - 8 при выполнении f ссылается на адрес локальной переменной x из main.

Если предположить, что при попытке возврата на адрес инструкции 0, программа крашится (завершается с ошибкой), как заставить программу выше крашнуться, сменив 1 символ в коде?

Ответ

sp - 8 --> sp - 4, что равно адресу возврата. Таким образом, сможете перезаписать адрес возврата на 0. При попытке возврата по этому адресу, программа крашнется.

Подобное явление - это частая ошибка в C, особенно при работе с массивами. Оно также носит имя stack corruption. Компилятор иногда вставляет в стек защиту от этого в режиме debugging, что помогает найти подобные ошибки (погуглите если интересно).

🤓 Раскладка стека в таблицах выше — концептуальное упрощение: что именно попадает в кадр стека (локальные переменные, адрес возврата, сохраненные регистры) и в каком порядке, решает реализация, стандарт этого не предписывает. На практике же на любом современном железе со стандартной раскладкой стека служебные данные кадра — включая адрес возврата — действительно лежат рядом с локальными переменными, поэтому идея упражнения выше жизнеспособна.

Бесконечная рекурсия

Код начинает выполняться с функции main.

Объясните что сделает программа ниже:

void f()
{
f();
return;
}
int main()
{
f();
return 0;
}

При попытке сдвига sp за пределы стека (за пределы максимального адреса, выделенного под стек), случается stack overflow. И программа обычно крашится.