ВВОД и ОСТАВИТЬ в Собрании?

Я читал "Язык языка сборки" (Randall Hyde, ссылка на 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 здесь?

Ответы

Ответ 1

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

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.

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

   mov( ebp, esp );    // Deallocate locals and clean up stack.

   pop( ebp );         // Restore pointer to caller activation record.

   ret();              // Return to the caller.

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

Ответ 2

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 и С++ запрещают вложенные функции. Учитывая наличие входа/выхода, их довольно легко поддерживать на процессорах Intel, но на многих других процессорах, которым не нужна такая прямая поддержка, может быть значительно сложнее.

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

Ответ 3

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