Попытка понять сложное выравнивание стека в gcc вверху main, которое копирует адрес возврата - PullRequest
17 голосов
/ 18 июля 2009

привет, я разобрал некоторые программы (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

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

Ответы [ 3 ]

27 голосов
/ 18 июля 2009

Я попробовал:

;# 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
7 голосов
/ 18 июля 2009

Это сделано для того, чтобы стек был выровнен по 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, так как указатель стека собирается изменить. Это используется для двух целей:

  1. для доступа к аргументам функции main, поскольку они относятся к исходному указателю стека
  2. , чтобы вернуть указатель стека к исходному значению при возврате из main
4 голосов
/ 18 июля 2009
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 - это временный регистр, поэтому его необходимо сохранить. Кроме того, в зависимости от уровня оптимизации некоторые операции связывания фреймов, которые не кажутся строго необходимыми для запуска программы, вполне могут быть важны для создания цепочки фреймов, готовых к трассировке.

...