Пытаясь понять основную разборку первых инструкций

Привет, я разобрал некоторые программы (linux), которые я написал, чтобы лучше понять, как это работает, и я заметил, что основная функция всегда начинается с:

lea    ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ?
and    esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ???
push   DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ?
push   ebp                
mov    ebp,esp
push   ecx  ;why is ecx pushed too ??

поэтому мой вопрос: зачем все это работает? Я понимаю только использование:

push   ebp                
mov    ebp,esp

остальное мне кажется бесполезным...

Ответы

Ответ 1

У меня есть на это:

;# As you have already noticed, the compiler wants to align the stack
;# pointer on a 16 byte boundary before it pushes anything. That's
;# because certain instructions' memory access needs to be aligned
;# that way.
;# So in order to first save the original offset of esp (+4), it
;# executes the first instruction:
lea    ecx,[esp+0x4]

;# Now alignment can happen. Without the previous insn the next one
;# would have made the original esp unrecoverable:
and    esp,0xfffffff0

;# Next it pushes the return addresss and creates a stack frame. I
;# assume it now wants to make the stack look like a normal
;# subroutine call:
push   DWORD PTR [ecx-0x4]
push   ebp
mov    ebp,esp

;# Remember that ecx is still the only value that can restore the
;# original esp. Since ecx may be garbled by any subroutine calls,
;# it has to save it somewhere:
push   ecx

Ответ 2

Это делается для того, чтобы стек выравнивался с 16-байтной границей. Некоторые инструкции требуют, чтобы определенные типы данных были выровнены так же, как и 16-байтовая граница. Чтобы удовлетворить это требование, GCC гарантирует, что стек изначально выровнен по 16 байт и распределяет пространство стека в 16 раз. Это можно контролировать, используя опцию - mpreferred-stack-border = num. Если вы используете -mpreferred-stack-border = 2 (для выравнивания 2 2= 4 байта), этот код выравнивания не будет сгенерирован, потому что стек всегда выравнивается по крайней мере на 4 байта. Однако у вас могут возникнуть проблемы, если ваша программа использует любые типы данных, требующие более сильного выравнивания.

Согласно руководству gcc:

В Pentium и PentiumPro двойные и длинные двойные значения должны быть выровнены с 8-байтовой границей (см. -malign-double) или имеют значительные штрафы за производительность во время выполнения. На Pentium III тип данных Streaming SIMD Extension (SSE) __m128 может работать неправильно, если он не выровнен по 16 байт.

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

Это дополнительное выравнивание потребляет дополнительное пространство стека и обычно увеличивает размер кода. Код, чувствительный к использованию пространства стека, такой как встроенные системы и ядра операционной системы, может захотеть уменьшить предпочтительное выравнивание до -mpreferred-stack-border = 2.

lea загружает указатель исходного стека (от вызова до main) в ecx, так как указатель стека уже будет изменен. Это используется для двух целей:

  • для доступа к аргументам функции main, поскольку они относятся к исходному указателю стека
  • восстановить указатель стека к его исходному значению при возврате из main

Ответ 3

lea    ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of     the main...why ?
and    esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ???
push   DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ?
push   ebp                
mov    ebp,esp
push   ecx  ;why is ecx pushed too ??

Даже если каждая команда отлично работала без ограничения скорости, несмотря на произвольно выровненные операнды, выравнивание все равно увеличивало бы производительность. Представьте себе цикл, ссылающийся на 16-байтовое количество, которое просто перекрывает две строки кэша. Теперь, чтобы загрузить этот маленький wchar в кеш, нужно удалить две целые строки кэша, и что, если они вам понадобятся в одном цикле? Кэш настолько огромен быстрее, чем оперативная память, что производительность кэша всегда критическая.

Кроме того, обычно существует ограничение скорости смещения смещенных операндов в регистры. Учитывая, что стек перестраивается, мы, естественно, должны сохранить старое выравнивание, чтобы пересекать стековые кадры для параметров и возвращаться.

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