Каждая подпрограмма использует часть стека, и мы называем это стековым фреймом.Хотя программист на ассемблере не обязан следовать следующему стилю, это настоятельно рекомендуется в качестве хорошей практики.
Кадр стека для каждой процедуры разделен на три части: параметры функции, обратный указатель на предыдущий кадр стека.и локальные переменные.
Часть 1: Параметры функции
Эта часть стекового фрейма подпрограммы устанавливается вызывающей стороной.Используя инструкцию «push», вызывающая сторона помещает параметры в стек.Разные языки могут задавать параметры в разных порядках.С, если я правильно помню, толкает их справа налево.То есть, если вы звоните ...
foo (a, b, c);
Звонящий преобразует это в ...
push c
push b
push a
call foo
Когда каждый элемент помещается в стек, стек уменьшается,То есть регистр указателя стека уменьшается на четыре (4) байта (в 32-битном режиме), и элемент копируется в область памяти, указанную регистром указателя стека.Обратите внимание, что инструкция call неявно помещает адрес возврата в стек.Очистка параметров будет рассмотрена в Части 5.
Часть 2. Обратный указатель стекового кадра
В этот момент была выпущена инструкция вызова имы сейчас находимся в начале вызванной рутины.Если мы хотим получить доступ к нашим параметрам, мы можем получить к ним доступ, например ...
[esp + 0] - return address
[esp + 4] - parameter 'a'
[esp + 8] - parameter 'b'
[esp + 12] - parameter 'c'
Однако это может стать неуклюжим после того, как мы выделим место для локальных переменных и прочего.Итак, мы используем регистр указателя стека в дополнение к регистру указателя стека.Однако мы хотим, чтобы регистр указателя стека был установлен в нашем текущем кадре, а не в предыдущей функции.Таким образом, мы сохраняем старый в стеке (который изменяет смещения параметров в стеке), а затем копируем текущий регистр указателя стека в регистр указателя стека.
push ebp ; save previous stackbase-pointer register
mov ebp, esp ; ebp = esp
Иногда вы можетепосмотрите, как это делается, используя только инструкцию 'ENTER'.
Часть 3: Вырезание пространства для локальных переменных
Локальные переменные хранятся в стеке.Поскольку размер стека уменьшается, мы вычитаем несколько # байтов (достаточно для хранения наших локальных переменных):
sub esp, n_bytes ; n_bytes = number of bytes required for local variables
Часть 4 : Собираем все вместе.Доступ к параметрам осуществляется через регистр указателя стека ...
[ebp + 16] - parameter 'c'
[ebp + 12] - parameter 'b'
[ebp + 8] - parameter 'a'
[ebp + 4] - return address
[ebp + 0] - saved stackbase-pointer register
Доступ к локальным переменным осуществляется через регистр указателя стека ...
[esp + (# - 4)] - top of local variables section
[esp + 0] - bottom of local variables section
Часть 5: Stackframecleanup
Когда мы покидаем подпрограмму, необходимо очистить кадр стека.
mov esp, ebp ; undo the carving of space for the local variables
pop ebp ; restore the previous stackbase-pointer register
Иногда вы можете увидеть инструкцию 'LEAVE', заменяющую эти две инструкции.
В зависимости от языка, который вы использовали, вы можете увидеть одну из двух форм инструкции «RET».
ret
ret <some #>
Какой бы язык вы ни выбрали, будет зависеть от выбора языка (или стиля, которому вы хотите следоватьесли писать на ассемблере).Первый случай указывает, что вызывающая сторона отвечает за удаление параметров из стека (в примере с foo (a, b, c) это будет сделано с помощью ... add esp, 12), и именно так C делаетЭто.Во втором случае указывается, что инструкция возврата будет выталкивать # слова (или # байтов, я не могу вспомнить, какой) из стека при возврате, тем самым удаляя параметры из стека.Если я правильно помню, это стиль, используемый Паскалем.
Это долго, но я надеюсь, что это поможет вам лучше понять стековые рамки.