И esp, 0xfffffff0

Я не совсем понимаю строку с комментарием в ней ниже. Я прочитал несколько сообщений в SO и в руководстве gcc и узнал, что он предназначен для выравнивания адресов стека, но не понимает, как он это делает. Код показан ниже:

(gdb) disas main
Dump of assembler code for function main:
   0x08048414 <+0>: push   ebp
   0x08048415 <+1>: mov    ebp,esp
   0x08048417 <+3>: and    esp,0xfffffff0 ; why??
   0x0804841a <+6>: sub    esp,0x10
   0x0804841d <+9>: mov    DWORD PTR [esp],0x8048510
   0x08048424 <+16>:    call   0x8048320 <[email protected]>
   0x08048429 <+21>:    mov    DWORD PTR [esp],0x8048520
   0x08048430 <+28>:    call   0x8048330 <[email protected]>
   0x08048435 <+33>:    leave
   0x08048436 <+34>:    ret
End of assembler dump.

Код был сгенерирован с использованием gcc (версия 4.6.3) в linux. Спасибо.

Ответ 1

and esp, 0xfffffff0 выполняет побитовое И между указателем стека и константой и сохраняет результат обратно в указателе стека.

Константа выбирается так, чтобы ее низкие четыре бита были равны нулю. Поэтому операция AND устанавливает эти биты в нуль в результате и оставляет остальные биты esp неповрежденными. Это приводит к округлению указателя стека до ближайшего кратного 16.

Ответ 2

Похоже, что это часть некоторого кода для создания магазина в начале main.

Начало функции: сохранить указатель базового фрейма в стеке (требуется команда leave позже):

   0x08048414 <+0>: push   ebp

Теперь мы привязываем указатель стека к 16-байтовой привязке, потому что компилятор (по какой-либо причине) этого хочет. Это может быть так, что он всегда хочет иметь 16-байт выровненных кадров или что локальным переменным требуется выравнивание по 16 байт (возможно, кто-то использовал uint128_t или они используют тип, который использует gcc vector extensions). В принципе, поскольку результат всегда будет меньше или равен текущему указателю стека, а стек растет вниз, он просто отбрасывает байты до тех пор, пока не достигнет 16-байтовой выровненной точки.

   0x08048415 <+1>: mov    ebp,esp
   0x08048417 <+3>: and    esp,0xfffffff0

Затем мы вычитаем 16 из указателя стека, создав 16 байтов локального пространства переменных:

   0x0804841a <+6>: sub    esp,0x10

puts((const char*)0x8048510);

   0x0804841d <+9>: mov    DWORD PTR [esp],0x8048510
   0x08048424 <+16>:    call   0x8048320 <[email protected]>

system((const char*)0x8048520);

   0x08048429 <+21>:    mov    DWORD PTR [esp],0x8048520
   0x08048430 <+28>:    call   0x8048330 <[email protected]>

Выйдите из функции (см. другой ответ о том, что делает leave):

   0x08048435 <+33>:    leave
   0x08048436 <+34>:    ret

Пример "отбрасывания байтов": скажем, esp = 0x123C в начале main. Первые строки кода:

   0x08048414 <+0>: push   ebp
   0x08048415 <+1>: mov    ebp,esp

приведет к этой карте памяти:

0x123C: (start of stack frame of calling function)
0x1238: (old ebp value) <-- esp, ebp

Тогда:

   0x08048417 <+3>: and    esp,0xfffffff0

заставляет последние 4 бита esp равным 0, что делает это:

0x123C: (start of stack frame of calling function)
0x1238: (old ebp value) <-- ebp
0x1234: (undefined)
0x1230: (undefined) <-- esp

В этом случае программист не может полагаться на определенный объем памяти между esp и ebp; поэтому эта память отбрасывается и не используется.

Наконец, программа выделяет 16 байтов хранилища стека (локального):

Затем мы вычитаем 16 из указателя стека, создав 16 байтов локального пространства переменных:

   0x0804841a <+6>: sub    esp,0x10

дает нам это отображение:

0x123C: (start of stack frame of calling function)
0x1238: (old ebp value) <-- ebp
0x1234: (undefined)
0x1230: (undefined)
0x123C: (undefined local space)
0x1238: (undefined local space)
0x1234: (undefined local space)
0x1230: (undefined local space) <-- esp

В этот момент программа может быть уверена, что 16 байт с 16-байт выровненной памятью указывают на esp.