Если esp указывает на вершину стека, куда указывает ebp? - PullRequest
2 голосов
/ 16 марта 2019

У меня возникли проблемы с пониманием того, как используются регистры esp и ebp.

Почему мы делаем:

pushl %ebp 
movl %esp, %ebp

в начале каждой функции? Что держит ebp при первом нажатии?

Ответы [ 3 ]

4 голосов
/ 16 марта 2019

Почему мы делаем:

Это имеет исторические причины.В 16-битном коде ...

  • ... процессоры x86 не разрешали использовать все регистры для адресации памяти.
  • ... адреса были относительно "сегменты "(например, 16*ss или 16*ds).

Поскольку sp не может использоваться для прямого доступа к памяти (например, 10(%sp) - это невозможно в 16-битном коде)сначала нужно было скопировать sp в другой регистр, а затем получить доступ к памяти (например, скопировать sp в bp, а затем сделать 10(%bp)).

Конечно, было бы также возможноиспользуйте bx, si или di вместо bp.

Однако вторая проблема заключается в сегментах: использование одного из этих регистров приведет к доступу к сегменту, указанному в регистре ds.Чтобы получить доступ к памяти в стеке, мы должны были бы сделать ss:10(%bx) вместо 10(%bx).Использование bp неявно обращается к сегменту, который содержит стек (что быстрее, а инструкция на один байт короче по сравнению с явным указанием сегмента).

В 32-битном (или 64-битном) коде все этобольше не нужноЯ только что скомпилировал функцию с помощью современного компилятора Си.Результат:

movl    12(%esp), %eax
imull   8(%esp), %eax
addl    4(%esp), %eax
ret

Как видите, регистр ebp не используется.

Однако есть две причины, по которым ebp все еще используется в современном коде:

  • Функцию проще создать.Вы знаете, что первый аргумент всегда находится в 8(%ebp), даже если ваша функция содержит инструкции push и pop.При использовании esp расположение первого аргумента меняется с каждой операцией push или pop.
  • Использование функции alloca: эта функция изменяет регистр esp таким образом, что можетдаже быть непредсказуемым для компилятора!Поэтому вам понадобится копия оригинального esp регистра.

Пример использования alloca:

push %ebp
mov %esp, %ebp
call GetMemorySize  # This will set %eax

# ---- Start of alloca() ----
# The alloca "function" will reserve N bytes on the
# stack while the value N is calculated during
# the run-time of the program (here: by the function
# GetMemorySize)
or $3, %al
inc %eax

# This has the same effect as multiple "push"
# instructions. However, we don't know how many
# "push" instructions!
sub %eax, %esp

mov %esp, %eax
# From this moment on, we would not be able to "restore"
# the original stack any more if we didn't have a copy
# of the stack pointer!
# ---- End of alloca() ----

push %eax
mov 8(%ebp), %eax
push %eax
call ProcessSomeData
mov %ebp, %esp
pop %ebp

# Of course we need to restore the original value
# of %esp before we can do a "ret".
ret
4 голосов
/ 16 марта 2019

в начале каждой функции ebp указывает, куда бы она ни требовала вызывающая функция, это не относится к текущей функции, пока код текущей функции не решит ее использовать.ebp - это просто указатель стекового фрейма на тот случай, если вы захотите иметь фрейм стека.Идея в том, что ВЫ МОЖЕТЕ использовать ebp, чтобы иметь постоянную ссылку на стек для вашей функции, в то время как вы можете продолжать добавлять или удалять элементы в стеке, используя esp.Если бы вы не использовали указатель стека и продолжали использовать esp в качестве ссылки на стек, то, где конкретный элемент в стеке в течение вашей функции изменяется в зависимости от esp.если ВЫ УСТАНАВЛИВАЕТЕ ebp до того, как начнете использовать стек (кроме как для сохранения ebp), то у вас будет фиксированный относительный адрес к параметрам в стеке, о которых заботится ваша функция, таким как переданные параметры, локальные переменные и т. д.

Вы можете свободно использовать eax, edx или любой другой регистр в качестве указателя фрейма стека в своей функции, ebp является регистром общего назначения, который вы можете использовать для фреймов стека, поскольку x86 исторически имел зависимость от стека (адреса возврата,и старые соглашения о вызовах были основаны на стеке).Другие наборы команд с большим количеством регистров могут просто выбрать регистр для реализации компилятора в качестве указателя на функцию / указатель фрейма стека.Если у вас есть возможность и решили использовать стек стека.Он записывает регистр, который вы могли бы использовать для других целей, записывает больше кода и времени выполнения.Как и при использовании других регистров общего назначения, ebp является энергонезависимым в соответствии с соглашениями о вызовах, используемыми сегодня, вам необходимо сохранить его и вернуть так, как вы его нашли.Так что это указывает на конкретную функцию.То, на что оно указывало, когда ваша функция была введена, было специфичным для вызывающей функции.

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

Причина, по которой вы начинаете с толчка, заключается в том, что это хороший способ использовать указатель кадра и определить согласованное местоположение.помещая его в стек как первое, что вы делаете: 1) сохраняет ebp, чтобы избежать сбоя вызывающей (ых) функции (ей). 2) определяет постоянные адреса эталонной точки.функция.Локальные переменные находятся по фиксированным адресам выше ebp для такой схемы.Компиляторы, так же как и люди, более чем способны к тому, чтобы не нуждаться в этом, мой первый параметр может быть на уровне esp-20 в одной точке кода, и я могу затем добавить еще 8 байтов в стек теперь, когда этот же параметрв esp-28 просто закодируйте его как таковой.но для целей отладки выполняется отладка созданного кода, а иногда, например, поиск адреса возврата с фиксированным смещением.запись другого регистра, IMO ленивая, но, безусловно, может помочь отладить и повысить качество вывода компилятора.Находите ошибки в выходных данных компилятора быстрее и помогите людям, пытающимся прочитать код, быстрее понять его с меньшими усилиями.при правильном использовании указателя фрейма стека все параметры и локальные переменные имеют фиксированное смещение по отношению к указателю фрейма стека на протяжении всей функции между точками, в которых указатель фрейма стека устанавливается и очищается.нажмите указатель, чтобы сохранить его, установите указатель кадра на указатель стека со смещением или без него.до появления указателя кадра перед возвратом.

2 голосов
/ 16 марта 2019

Во время выполнения функции различные объекты могут быть помещены в стек.Push уменьшает %esp (или %rsp, если вы используете 64-разрядное оборудование), чтобы указывать на следующую доступную память в стеке, в то время как %ebp (или %rbp) поддерживает неизменный указатель на началокадр стека функции, так что относительно %ebp функция может находить свои различные объекты, уже хранящиеся в стеке.

Ранние 8-битные процессоры, подобные старому 6502 1970-х и 1980-х годов, не имели%ebp.Не имея %epb, рассмотрим следующий код C:

int a = 10;
++a;
{
    int b = 20;
    --b;
    a += b;
}

a хранится в 0(%esp), за исключением того, что когда b помещается в стек, a, которыйфактически не перемещается, теперь находится на 4(%esp).Вы видите проблему?

Используя %ebp, a всегда находится в -4(%ebp) и b, когда в области действия находится в -8(%ebp).

...