ENTER и LEAVE в сборке?

Я читал Искусство языка ассемблера (Рэндалл Хайд, ссылка на Amazon), и я попробовал консольное приложение из этой книги. Это была программа, которая создавала себе новую консоль, используя функции Win32 API. Программа содержит процедуру LENSTR, которая сохраняет длину строки в регистре EBP. Код этой функции выглядит следующим образом:

LENSTR PROC
ENTER 0, 0
PUSH  EAX
;----------------------
CLD
MOV   EDI, DWORD PTR [EBP+08H]
MOV   EBX, EDI
MOV   ECX, 100 ; Limit the string length
XOR   AL, AL
REPNE SCASB ; Find the 0 character
SUB   EDI, EBX ; String length including 0
MOV   EBX, EDI

DEC   EBX
;----------------------
POP   EAX
LEAVE
RET   4
LENSTR ENDP

Не могли бы вы объяснить здесь использование команд enter и leave?


person devjeetroy    schedule 02.05.2011    source источник
comment
Я считаю, что есть значительно лучший более подробный ответ. Вы можете пересмотреть или выбрать то, что вы считаете лучшим.   -  person Evan Carroll    schedule 18.03.2018
comment
который хранит длину строки в регистре EBP Неверно, он сохраняет длину в ebx   -  person ecm    schedule 09.03.2021


Ответы (3)


Это настройка кадра стека (записи активации) для функции. Внутренне это обычно выглядит примерно так:

    push( ebp );         // Save a copy of the old EBP value
     
    mov( esp, ebp );     // Get ptr to base of activation record into EBP
     
    sub( NumVars, esp ); // Allocate storage for local variables.

// ENTER with a non-zero immediate does all 3 of the above things, slowly.

Затем, когда кадр стека должен быть снова уничтожен, вы должны сделать что-то вроде следующих строк:

   mov( ebp, esp );    // Deallocate locals and clean up stack.
 
   pop( ebp );         // Restore pointer to caller's activation record.
// LEAVE does the above steps; a RET instruction is separate

   ret();              // Return to the caller.

Вот лучшее объяснение это с помощью HLA. Хотя это хорошо объяснено в книге, которую вы читаете, поскольку у меня тоже есть эта книга, и я прочитал раздел, объясняющий это.

person Tony The Lion    schedule 02.05.2011
comment
Это и это может быть более правильным. - person lzutao; 26.03.2017
comment
Вы должны опустить ret() из своего примера, так как leave не выполняет ret - person Aviv; 06.04.2018

Enter создает кадр стека, а leave уничтожает кадр стека. С параметрами 0,0 на enter они в основном эквивалентны:

; enter
push ebp
mov ebp, esp

; leave
mov esp, ebp
pop ebp

Хотя он не используется в опубликованном вами коде, enter поддерживает выполнение немного большего, чем простая комбинация push/mov, показанная выше. Первый параметр enter указывает количество места, выделяемого для локальных переменных. Например, enter 5, 0 примерно эквивалентно:

push ebp
mov ebp, esp
sub esp, 5

Enter также поддерживает такие языки, как Pascal, которые могут использовать вложенные функции/процедуры:

procedure X;
    procedure Y;
    begin
        { ... }
    end
begin
   { ... }
end

В таком случае Y имеет доступ не только к своим локальным переменным, но и ко всем локальным переменным X. Они могут быть вложены до произвольной глубины, поэтому вы можете иметь Z внутри Y, который имеет доступ к своим собственным локальным переменным, а также к переменным Y и переменным X. Второй параметр enter указывает глубину вложенности, поэтому X будет использовать enter Sx, 0, Y будет использовать enter Sy, 1, а Z будет использовать enter Sz, 2 (где Sx, Sy и Sz означают размер переменных, локальных для X, Y и Z соответственно).

Это создаст цепочку кадров стека, чтобы предоставить Z доступ к переменным, локальным для Y и X, и так далее. Это становится довольно нетривиальным, если функции рекурсивны, поэтому вызов Z не может просто пройти вверх по стеку до двух самых последних кадров стека - ему нужно пропустить кадры стека от предыдущих вызовов самого себя и перейти непосредственно вернуться к кадрам стека для лексической родительской функции/процедуры, которая отличается от вызывающей в случае рекурсии.

Эта сложность также является причиной того, что C и C++ запрещают вложенные функции. Учитывая наличие ввода/вывода, их довольно легко поддерживать на процессорах Intel, но может быть значительно сложнее на многих других процессорах, в которых такая прямая поддержка отсутствует.

Это также, по крайней мере, помогает объяснить еще одну... особенность enter - для используемого здесь тривиального случая (например, enter 0, 0) это немного медленнее, чем эквивалент с использованием push/mov.

person Jerry Coffin    schedule 02.05.2011
comment
@BlackBear: Нет, это инструкции, но они своего рода стенографические инструкции — вы можете сделать то же самое и без них. - person Jerry Coffin; 02.05.2011
comment
@devjeetroy Просмотрите все ответы и выберите тот, который лучше всего отвечает на ваш вопрос. Рядом с каждым ответом есть небольшой флажок, нажмите на него, чтобы выбрать официальный ответ на ваш вопрос. - person karlphillip; 02.05.2011
comment
В случае, если вы создаете вложенные функции, стоит ли использовать свои собственные или использовать enter? Т.е. если нужен функционал, то быстрее? Как насчет локальной области видимости, например, в языках, которые локализуют переменные для операторов if (например, let) внутри, находятся ли они в своем собственном стеке и могут ли они использовать ввод? Будет ли это иметь больше смысла? - person Evan Carroll; 18.03.2018
comment
@EvanCarroll: если вы выполняете вложенные функции, вы также можете использовать enter и leave. Если память не изменяет, они примерно с той же скоростью, что и ваши собственные, но немного экономят место для кода. Для других областей обычно имеет смысл вычислить размер всей функции и выделить его при входе, а не выделять место для этой области при ее вводе. Если вы хотите, вы также можете сделать это для функций, если они не являются (даже косвенно) рекурсивными. - person Jerry Coffin; 19.03.2018
comment
enter не влияет на флаги состояния, в отличие от sub esp, x. Более точным эквивалентом является lea esp, [esp - x] - person ecm; 09.03.2021

Войдите и оставьте, просто настройте фрейм стека. Обычно компиляторы генерируют код, который напрямую манипулирует указателями фреймов стека, поскольку вход и выход не совсем быстры по сравнению с mov/sub (хотя раньше они были такими, еще в 286 дней :-)).

person Brian Knoblauch    schedule 02.05.2011
comment
Нет, они никогда не были быстрее, просто более компактны и универсальны (они поддерживают вложенные функции в стиле Pascal). - person Jerry Coffin; 02.05.2011
comment
Интересно, что MASM32 (когда используются локальные переменные) делает входную часть с помощью PUSH/MOV/ADD, но уничтожает их с помощью LEAVE... - person Brian Knoblauch; 12.09.2011
comment
справа: enter имеет накладные расходы на поддержку вложенных функций, даже если вы их не используете. leave не добавляет накладных расходов и немного меньше эквивалентного mov/pop. - person Jerry Coffin; 12.09.2011
comment
Это тот же выбор, который делает GCC: leave если бы были необходимы и mov, и pop (в противном случае просто pop ebp / rbp). Но никогда enter потому что это очень медленно. - person Peter Cordes; 09.03.2021
comment
leave составляет 3 полных мопса на семействе Sandybridge, по сравнению с 2 для mov/pop. Я тестировал на Skylake функцию, которая устанавливала RBP в качестве указателя кадра, а затем удаляла его с помощью leave вместо mov/pop, и вызов функции mov/pop в цикле действительно был меньше всего uops_issued.any для внешнего интерфейса. Так что это немного дороже, а не только вопрос подсчета uop с синхронизацией стека. - person Peter Cordes; 09.03.2021