для чего нужен перенос локальных переменных в стек (сборка) - PullRequest
0 голосов
/ 18 сентября 2018

Давайте создадим функцию:

int caller()
{
   int arg1 = 1;
   int arg2 = 2
   int a = test(&arg1, &arg2)
}
test(int *a, int *b)
{
    ...
}

, поэтому я не понимаю, почему & arg1 и & arg2 тоже нужно помещать в стек, как это

enter image description here

Я могу понять, что мы можем получить адрес arg1 и arg2 в вызываемом объекте, используя

movl  8(%ebp), %edx
movl  12(%ebp), %ecx

, но если мы не поместим эти два в стек, мы можемтакже можно указать их адрес с помощью:

leal 8(%ebp), %edx
leal 12(%ebp), %ecx 

так зачем беспокоиться о добавлении & arg1 и & arg2 в стек?

Ответы [ 2 ]

0 голосов
/ 18 сентября 2018

В общем случае, test должен работать, когда вы передаете ему произвольные указатели, в том числе extern int global_var или как угодно . Затем main должен вызвать его в соответствии с соглашением ABI / вызова.

Таким образом, определение asm test не может предполагать, где int *a указывает, например, что он указывает на фрейм стека вызывающего.

(Или вы могли бы посмотреть на это как на оптимизацию адресации при вызове по ссылке на локальных объектах, поэтому вызывающий объект должен поместить объекты, на которые указывает указатель, в слоты, передаваемые через arg, и вернуть эти 2 слова стека память содержит потенциально обновленные значения *a и *b.)

Вы скомпилированы с отключенной оптимизацией. Специально для особого случая, когда вызывающая сторона передает указатели местным жителям, решение этой проблемы состоит в том, чтобы встроить всю функцию , что будут делать компиляторы, когда включена оптимизация.

Компиляторам разрешено создавать частный клон test, который принимает свои аргументы по значению, или в регистрах, или с любым пользовательским соглашением о вызовах, которое компилятор хочет использовать. Однако большинство компиляторов на самом деле этого не делают и полагаются на встраивание вместо пользовательских соглашений о вызовах для частных функций, чтобы избавиться от накладных расходов на передачу аргументов.

Или, если бы он был объявлен static test, то компилятор уже знал бы, что он является частным и теоретически мог использовать любое соглашение о вызовах, которое ему нужно, без создания клона с именем, подобным test.clone1234. GCC иногда делает это для постоянного распространения, например, если вызывающая сторона передает константу времени компиляции, но gcc выбирает не inline. (Или не может, потому что вы использовали __attribute__((noinline)) static test() {})


И, кстати, с хорошим соглашением о вызовах регистров-аргументов, таким как x86-64 System V , вызывающий абонент будет делать lea 12(%rsp), %rdi / lea 8(%rsp), %rsi / call test или что-то в этом роде. Соглашение о вызовах i386 System V является старым и неэффективным, поскольку все данные в стеке передаются, что приводит к необходимости сохранения / перезагрузки.

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

0 голосов
/ 18 сентября 2018

если вы обращаетесь к arg1 и arg2 напрямую, это означает, что вы обращаетесь к той части стека, которая не принадлежит этой функции.Это как-то происходит, когда кто-то использует атаку переполнения буфера для доступа к дополнительным данным из стека вызовов.

Когда ваш вызов имеет аргументы, аргументы помещаются в стек (в вашем случае &arg1и &arg2) и функция может использовать их в качестве допустимого списка аргументов для этой функции.

...