Почему для нахождения параметров в стеке лучше использовать ebp, чем регистр esp? - PullRequest
0 голосов
/ 24 января 2019

Я новичок в MASM.У меня путаница в отношении этих регистров указателей.Я был бы очень признателен, если бы вы, ребята, помогли мне.

Спасибо

1 Ответ

0 голосов
/ 24 января 2019

Кодирование режима адресации с использованием [ebp + disp8] на один байт короче [esp+disp8], поскольку для использования ESP в качестве базового регистра требуется байт SIB.Подробнее см. rbp в качестве базы SIB? .(Заголовок этого вопроса спрашивает о том факте, что [ebp] должен быть закодирован как [ebp+0].)

В первый раз [esp + disp8] используется после нажатия или нажатия или после call,потребует стековой синхронизации на процессорах Intel.( Что такое механизм стека в микроархитектуре Sandybridge? ).Конечно, mov ebp, esp для создания кадра стека в первую очередь также запускает стек-синхронизацию: любая явная ссылка на ESP в ядре не по порядку (не только в режимах адресации) вызывает стек-синхронизацию, еслиМеханизм стека может иметь смещение, о котором не знает неработающий сервер.


Традиционная установка фрейма стека с помощью ebp создает связанный список стековых фреймов (каждыйсохраненный EBP, указывающий на сохраненный EBP родителя (прямо под адресом возврата), удобный для профилирования и иногда отладки, если ваш код не имеет альтернативных метаданных, которые позволяют вашему отладчику раскручивать стек, чтобы показать обратные следы стека.


Но несмотря на эти недостатки использования ESP, часто не лучше (для производительности) использовать EBP в качестве указателя кадра, поскольку он использует дополнительный один из 8 регистров GP для стекаоставляя вам 6 вместо 7, вы можете использовать его для других вещей, кроме стека. Современные компиляторы по умолчанию имеют значение -fomit-frame-pointer, когда оптимизация выполняется

Компиляторам легко отслеживать, сколько ESP сместилось относительно того, где они что-то хранили, потому что они знают, насколько sub esp,28 перемещает указатель стека.Даже после push аргумента функции они все еще знают правильное смещение ESP относительно всего, что они сохранили в стеке ранее в функции.

Люди тоже могут это делать, но легко сделатьошибка, когда вы модифицируете функцию, чтобы зарезервировать дополнительное пространство и забыть обновить все смещения от ESP до ваших локальных и стековых аргументов, если таковые имеются.(Однако обычно не стоит писать большие функции, которые не могут хранить большинство своих переменных в регистрах. Оставьте это компилятору и тратите свое время только на написание горячих циклов в asm, если вообще).

Исключение составляют случаи, когда ваша функция выделяет переменное количество стекового пространства (например, массивы переменной длины C alloca или C99, например int arr[n]) ;в этом случае компиляторы создадут традиционный фрейм стека с EBP.Или в рукописном asm, если вы push в цикле используете стек вызовов в качестве структуры данных стека.


Например, x86 MSVC 19.14 компилирует этот C

int foo() {
    volatile int i = 0;  // force it to be stored to memory
    return i;
}

В этот асм MASM.( Посмотрите сами в проводнике компилятора Godbolt )

;;; MSVC -O2
_i$ = -4                                                ; size = 4
int foo(void) PROC                                        ; foo, COMDAT
        push    ecx
        mov     DWORD PTR _i$[esp+4], 0           ; note this is actually [esp+0] ; _i$ = -4
        mov     eax, DWORD PTR _i$[esp+4]
        pop     ecx
        ret     0
int foo(void) ENDP                                        ; foo

Обратите внимание, что он резервирует место для i с push вместо sub esp, 4потому что это экономит размер кода и обычно примерно одинаковой производительности.Это то же количество мопов для внешнего интерфейса, без дополнительных стековых синхронизаций, потому что push перед любой явной ссылкой на esp, а pop - после последней.

(Если бы он резервировал более 4 байтов, я думаю, что он просто использовал бы обычный sub esp, 8 или что-то еще.)

Здесь явно пропущена оптимизация;push 0 будет хранить значение, которое он на самом деле хочет, вместо того, чтобы мусор был в ECX.( Какой компилятор C / C ++ может использовать команды push pop для создания локальных переменных вместо простого увеличения esp один раз? ).И pop eax будет очищать стек и load i в качестве возвращаемого значения.

против.это с отключенной оптимизацией. Обратите внимание, что _i$ = -4 - это то же смещение от "стекового фрейма", но оптимизированный код использовал esp+4 в качестве базы, тогда как при этом используется ebp.Это в основном просто забавный факт внутренних компонентов MSVC, который, кажется, думает с точки зрения того, где был бы EBP, если бы он не оптимизировал создание указателя кадра.Выбор опорной точки имеет смысл, и выстраиваются в очередь с его каркасно-указатель включен выбор является очевидным выбором.

;;; MSVC -O0
_i$ = -4                                                ; size = 4
int foo(void) PROC                                        ; foo
        push    ebp
        mov     ebp, esp                     ; make a stack frame
        push    ecx
        mov     DWORD PTR _i$[ebp], 0
        mov     eax, DWORD PTR _i$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     0
int foo(void) ENDP                                        ; foo

Интересно, что до сих пор использует пуш / поп резервировать 4 байта стека.На этот раз это вызывает одну дополнительную синхронизацию стека на процессорах Intel, потому что push ecx после mov ebp,esp перезагружает механизм стека до mov esp, ebp.Но это довольно тривиально.

...