Почему gcc генерирует дополнительный обратный адрес?

В настоящее время я изучаю основы сборки и столкнулся с чем-то странным, глядя на инструкции, сгенерированные gcc (6.1.1).

Вот источник:

#include <stdio.h>

int foo(int x, int y){
    return x*y;
}

int main(){
    int a = 5;
    int b = foo(a, 0xF00D);
    printf("0x%X\n", b);
    return 0;
}

Команда, используемая для компиляции: gcc -m32 -g test.c -o test

При рассмотрении функций в GDB я получаю это:

(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
   0x080483f7 <+0>:     lea    ecx,[esp+0x4]
   0x080483fb <+4>:     and    esp,0xfffffff0
   0x080483fe <+7>:     push   DWORD PTR [ecx-0x4]
   0x08048401 <+10>:    push   ebp
   0x08048402 <+11>:    mov    ebp,esp
   0x08048404 <+13>:    push   ecx
   0x08048405 <+14>:    sub    esp,0x14
   0x08048408 <+17>:    mov    DWORD PTR [ebp-0xc],0x5
   0x0804840f <+24>:    push   0xf00d
   0x08048414 <+29>:    push   DWORD PTR [ebp-0xc]
   0x08048417 <+32>:    call   0x80483eb <foo>
   0x0804841c <+37>:    add    esp,0x8
   0x0804841f <+40>:    mov    DWORD PTR [ebp-0x10],eax
   0x08048422 <+43>:    sub    esp,0x8
   0x08048425 <+46>:    push   DWORD PTR [ebp-0x10]
   0x08048428 <+49>:    push   0x80484d0
   0x0804842d <+54>:    call   0x80482c0 <printf@plt>
   0x08048432 <+59>:    add    esp,0x10
   0x08048435 <+62>:    mov    eax,0x0
   0x0804843a <+67>:    mov    ecx,DWORD PTR [ebp-0x4]
   0x0804843d <+70>:    leave  
   0x0804843e <+71>:    lea    esp,[ecx-0x4]
   0x08048441 <+74>:    ret    
End of assembler dump.
(gdb) disas foo
Dump of assembler code for function foo:
   0x080483eb <+0>:     push   ebp
   0x080483ec <+1>:     mov    ebp,esp
   0x080483ee <+3>:     mov    eax,DWORD PTR [ebp+0x8]
   0x080483f1 <+6>:     imul   eax,DWORD PTR [ebp+0xc]
   0x080483f5 <+10>:    pop    ebp
   0x080483f6 <+11>:    ret    
End of assembler dump.

Меня смущает то, что он пытается сделать со стеком. Насколько я понимаю, это то, что он делает:

Сначала требуется ссылка на некоторый адрес памяти на 4 байта выше в стеке, который, насколько мне известно, должен быть переменными, передаваемыми в main, поскольку esp в данный момент указывает на адрес возврата в памяти.

Во-вторых, он выравнивает стек по границе 0 по соображениям производительности.

В-третьих, он выталкивает в новую область стека ecx + 4, что должно переводить в проталкивание адреса, к которому мы должны вернуться в стеке.

В-четвертых, он помещает старый указатель кадра в стек и устанавливает новый.

В-пятых, он помещает ecx (который все еще указывает на то, что должно быть аргументом main) в стек.

Программа делает то, что должна, и начинает процесс возврата.

Сначала он восстанавливает ecx, используя смещение -0x4 для ebp, которое должно получить доступ к первой локальной переменной.

Во-вторых, он выполняет инструкцию выхода, которая на самом деле просто устанавливает esp в ebp, а затем выталкивает ebp из стека.

Итак, теперь следующая вещь в стеке - это адрес возврата, и регистры esp и ebp должны вернуться к тому, что им нужно для правильного возврата?

Очевидно, не потому, что следующая вещь, которую он делает, это загружает esp с ecx-0x4, который, так как ecx все еще указывает на эту переменную, переданную в main, должен поместить ее по адресу адреса возврата в стеке.

Это работает просто отлично, но поднимает вопрос о том, почему он потрудился поместить адрес возврата в стек на шаге 3, так как он возвратил стек в исходное положение в конце непосредственно перед фактическим возвратом из функции.

Ответы на вопрос(1)

Ваш ответ на вопрос