Динамическое изменение стека вызовов с помощью указателей

Можем ли мы в библиотеке C++ Arduino изменить стек вызовов изнутри функции, намеренно уменьшив указатель на переменную стека так, чтобы он вышел за пределы допустимого диапазона? Вот так:

void FooBar()
{
  char a;
  char *ptr = &a;
  ptr -= 4;
  // Можем ли мы теперь редактировать стек вызовов с помощью ptr?
}

Можем ли мы использовать это, например, для изменения того, какая функция «вызывает» FooBar(), заменив указатель вызывающей функции в стеке вызовов на другую функцию? Или мы можем злонамеренно изменить значения переменных функции в вызывающей функции?

, 👍1

Обсуждение

Да, ты можешь. Сначала изучите ABI, чтобы знать, что где. https://gcc.gnu.org/wiki/avr-gcc#Frame_Layout Однако я бы не рекомендовал этого делать — могут случиться неприятные вещи, если вы возитесь с фреймом стека., @Majenko

Да, можно, но это не сработает. Компилятор умеет делать множество оптимизаций, поэтому очень сомнительно, что это сработает. Если вы серьезно относитесь к написанию программного обеспечения, вам не следует **никогда** делать такие вещи. Держитесь подальше от вещей, которые обязательно пойдут не так. Каждый инженер-программист, увидевший ваш фрагмент кода, теперь чувствует себя очень некомфортно и бьется головой о стену, громко крича: нет, нет, нет., @Jot

Рекомендую вам сгенерировать файл .lst и посмотреть сборку, @Juraj


1 ответ


1

Я отвечаю в контексте соглашений о вызовах AVR, поскольку они это единственные, кого я знаю достаточно хорошо, чтобы иметь возможность ответить здесь.

Можем ли мы использовать это, например, для изменения того, какую функцию «вызывает»? FooBar() путем замены указателя функции вызывающей функции в стеке вызовов другой функции?

В C++ это, вероятно, будет невозможно. Допустим, у вас есть следующая цепочка вызовов: main()f()FooBar(). В теле FooBar(), стек будет выглядеть так (самый старый материал вверху):

return address from f() to main()
registers saved by the f() prologue
return address from FooBar() to f()
registers saved by the FooBar() prologue

Получить указатель стека очень просто. Нет необходимости объявлять фиктивную переменную, просто:

char *stack_pointer = (char *) SP;

Теперь, чтобы изменить необходимый обратный адрес, нужно пропустить регистры, сохраненные в прологе FooBar(). И это большая проблема: вы не знаете, сколько там сохранено регистров. Прологи функций пишутся компилятором, и что бы они ни сохранили, зависит от того, сколько регистры необходимы внутри функции, что, в свою очередь, зависит от сложность этой функции. Даже незначительные изменения в функции могут изменить количество регистров, которые сохраняет пролог.

Единственное решение этой проблемы, которое я могу себе представить, — это отказаться от созданный компилятором пролог и напишите FooBar() на ассемблере. Если вы хотите сделать это в файле C++, вы можете прикрепить атрибут "naked" к функцию и напишите ее тело, используя встроенный ассемблер.

Или можем ли мы изменить значения переменных функции при вызове действовать вредно?

Для этого не нужно вмешиваться в стек. Компилятор стремится сохранить как можно больше локальных переменных в регистрах ЦП, а не в куча. Локальные переменные вызывающей стороны, скорее всего, все еще находятся в этих регистры и вы можете изменять их по своему желанию (в сборке). Вот почему ты иметь этот пролог с сохранением регистра и эпилог с восстановлением регистра в первое место. Действительно, если сделать функцию «голой» и не делать спасая/восстанавливая себя, вы, скорее всего, в конечном итоге испортите местные жители звонящего.

,