Распределение стека слишком велико

Я написал ниже код.

int main (int argc, char *argv[])
{
  char *uargv[3];

  uargv[0] = "echo";
  uargv[1] = "hello!";
  uargv[2] = 0;

  exec("echo", uargv);

  exit();
}

И скомпилирован с помощью gcc 5.4.

Ниже приведен ассемблерный код вышеуказанной программы.

0x00: lea    0x4(%esp),%ecx
0x04: and    $0xfffffff0,%esp
0x07: pushl  -0x4(%ecx)
0x0a: push   %ebp 
0x0b: mov    %esp,%ebp
0x0d: push   %ecx 
0x0e: sub    $0x14,%esp
0x11: movl   $0x7c3,-0x14(%ebp)
0x18: movl   $0x7c8,-0x10(%ebp)
0x1f: movl   $0x0,-0xc(%ebp)
0x26: sub    $0x8,%esp
0x29: lea    -0x14(%ebp),%eax
0x2c: push   %eax 
0x2d: push   $0x7c3
0x32: call   2ce <exec>
0x37: add    $0x10,%esp
0x3a: call   296 <exit>

Я не могу понять три части приведенного выше ассемблерного кода.

Во-первых, в коде, который находится по адресам 0x00, 0x04, 0x07, 0x0a, 0x0b, 0x0d, работает следующим образом.

0x00: save ebp+4 to ecx, and it is address of argc.
0x04: align esp with 16 byte
0x07: push ecx-4 to stack, and it is return address.
0x0a ~ 0x0b: setup ebp
0x0d: push ecx to stack, and it is address of argc.

Интересно, почему он снова нажимает обратный адрес и почему он нажимает адрес argc.

Это был мой первый вопрос.

Во-вторых, он выделяет стек в инструкции 0x0e для локальной переменной uargv.

Однако размер uargv всего 12 байт.

Но он выделяет 20 байт.

Если это из-за выравнивания по 16 байтам, то почему локальные переменные сохраняются в esp+4, а не в esp (ebp+0x14 совпадает с esp+4).

В-третьих, почему инструкция 0x26 выделяет под стек больше 8 байт?

Больше места в стеке не требуется, потому что аргументы exec сохраняются в стеке командой push.

Спасибо.


person Joontaek Oh    schedule 07.11.2018    source источник
comment
Компилятору разрешено делать со своими локальными переменными все, что он хочет, особенно если вы забыли включить оптимизацию. При вызове других функций он должен следовать соглашению о вызовах, которое действительно предписывает выравнивание по 16 байтам. Вставка адреса возврата выполняется только в main, потому что компилятор не может полагаться на выравнивание входящего указателя стека, поэтому он выполняет выравнивание сам и помещает адрес возврата сверху.   -  person Jester    schedule 07.11.2018
comment
Большое спасибо! Тогда причина всех моих вопросов - выравнивание по 16 байтам. Но почему локальные переменные сохраняются в esp+4, а не в esp? А почему адрес argc задвинут выше старого ebp? Это начальный адрес параметра? Не могли бы вы мне сказать?   -  person Joontaek Oh    schedule 07.11.2018
comment
ecx помещается, потому что это регистр, сохраняемый вызывающей стороной, и обычно он необходим для восстановления стека. Так как ваш код вызывает exit, который здесь не используется. Расположение локальных переменных остается за компилятором.   -  person Jester    schedule 07.11.2018
comment
re: 2-я часть: gcc иногда резервирует дополнительные 16 байт пространства без видимой причины, даже если вы компилируете с включенной оптимизацией. Но без оптимизации он даже не пытается быть эффективным.   -  person Peter Cordes    schedule 07.11.2018