Что такое кадр стека в сборке? - PullRequest
41 голосов
/ 13 сентября 2010

Какова структура стекового фрейма и как он используется при вызове функций в сборке?

Ответы [ 4 ]

133 голосов
/ 13 сентября 2010

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

Кадр стека для каждой процедуры разделен на три части: параметры функции, обратный указатель на предыдущий кадр стека.и локальные переменные.

Часть 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 делаетЭто.Во втором случае указывается, что инструкция возврата будет выталкивать # слова (или # байтов, я не могу вспомнить, какой) из стека при возврате, тем самым удаляя параметры из стека.Если я правильно помню, это стиль, используемый Паскалем.

Это долго, но я надеюсь, что это поможет вам лучше понять стековые рамки.

14 голосов
/ 13 сентября 2010

Фрейм стека x86-32 создается путем выполнения

function_start:
    push ebp
    mov ebp, esp

, поэтому он доступен через ebp и выглядит как

ebp+00 (current_frame) : prev_frame
ebp+04                 : return_address
                         ....
prev_frame             : prev_prev_frame
prev_frame+04          : prev_return_address

Есть некоторые преимущества использования ebp для фреймов стекадизайн инструкции по сборке, поэтому доступ к аргументам и локальным объектам обычно осуществляется с помощью регистра ebp.

2 голосов
/ 13 сентября 2010

Это зависит от используемой операционной системы и языка. Потому что в ASM нет общего формата для стека, единственное, что стек делает в ASM, - это сохранение адреса возврата при выполнении подпрограммы перехода. При выполнении подпрограммы возврата из адреса адрес берется из стека и помещается в счетчик программ (область памяти, из которой следует выполнить следующую инструкцию выполнения ЦП)

Вам нужно будет обратиться к документации по используемому вами компилятору.

0 голосов
/ 13 сентября 2010

Фрейм стека x86 может использоваться компиляторами (в зависимости от компилятора) для передачи параметров (или указателей на параметры) и возврата значений. Смотри это

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...