Стековые кадры
Код в начале тела функции:
push %ebp
mov %esp, %ebp
предназначен для создания так называемого стекового кадра , который является "твердая земля »для ссылки на параметры и объекты, локальные для процедуры.Регистр %ebp
используется (как указывает его имя) в качестве базового указателя , который указывает на base (или низ) локального стека внутри процедуры.
После входа в процедуру регистр указателя стека (%esp
) указывает на адрес возврата , сохраненный в стеке инструкцией вызова (это адрес инструкции сразу после вызова).Если вы сейчас просто вызовете ret
, этот адрес будет извлечен из стека в %eip
(указатель инструкции), и код будет выполняться дальше от этого адреса (следующей инструкции после call
).Но мы еще не вернулись, не так ли?; -)
Затем вы нажимаете %ebp
регистр, чтобы сохранить его предыдущее значение где-нибудь и не потерять его, потому что вы будете использовать его для чего-то в ближайшее время.(Кстати, он обычно содержит базовый указатель функции вызывающей стороны, и когда вы смотрите это значение, вы найдете ранее сохраненный %ebp
, который снова будет базовым указателем функции на один уровень выше, так что вы можете отслеживатьстек вызовов таким образом.) Когда вы сохраняете %ebp
, вы можете сохранить там текущий %esp
(указатель стека), так что %ebp
будет указывать на тот же адрес: базу текущего локального стека.%esp
будет перемещаться вперед и назад внутри процедуры, когда вы будете выдвигать и выталкивать значения в стеке, а также резервировать и освобождать локальные переменные.Но %ebp
останется фиксированным, по-прежнему указывая на основание локального стекового фрейма.
Доступ к параметрам
Параметры, передаваемые в процедуру вызывающей стороной, "похоронены просто под землей"то есть они имеют положительных смещений относительно базы, потому что стек уменьшается).В %ebp
указан адрес базы локального стека, где лежит предыдущее значение %ebp
.Ниже (то есть в 4(%ebp)
лежит адрес возврата. Таким образом, первый параметр будет в 8(%ebp)
, второй в 12(%ebp)
и т. Д.
Локальные переменные
А локальные переменные могут быть размещены в стеке над базой (то есть они будут иметь отрицательных смещений относительно базы). Просто вычтите N в %esp
, и вы только что выделили N
байты в стеке для локальных переменных, перемещая вершину стека выше (или, точнее, ниже) этой области :-) Вы можете ссылаться на эту область с помощью отрицательных смещений относительно %ebp
,т. е. -4(%ebp)
- первое слово, -8(%ebp)
- второе и т. д. Помните, что (%ebp)
указывает на базу локального стека, где было сохранено предыдущее значение %ebp
.Поэтому не забудьте восстановить стек в предыдущую позицию, прежде чем пытаться восстановить значения от %ebp
до pop %ebp
в конце процедуры.Вы можете сделать это двумя способами:1. Вы можете освободить только локальные переменные, добавив обратно N
к %esp
(указатель стека), то есть переместив верхнюю часть стека, как если бы эти локальные переменные никогда не были там.(Ну, их значения останутся в стеке, но они будут считаться «освобожденными» и могут быть перезаписаны последующими нажатиями, поэтому ссылаться на них больше не безопасно. Это трупы; -J)2. Вы можете сбросить стек на землю и освободить все локальное пространство, просто восстановив %esp
из %ebp
, который был ранее зафиксирован, к основанию стека.Он восстановит указатель стека до состояния, которое он имеет сразу после ввода процедуры и сохранения %esp
в %ebp
.Это похоже на загрузку ранее сохраненной игры, когда вы что-то напутали; -)
Отключение указателей кадров
Можно получить менее грязную сборку из gcc -S
, добавив переключатель -fomit-frame-pointer
.Он говорит GCC не собирать никакого кода для установки / сброса фрейма стека, пока он действительно не понадобится для чего-либо.Просто помните, что это может сбить с толку отладчиков, потому что они обычно зависят от наличия стека фрейма, чтобы иметь возможность отслеживать стек вызовов.Но это ничего не сломает, если вам не нужно отлаживать этот двоичный файл.Он отлично подходит для целей выпуска и экономит некоторое пространство-время.
Информация о кадре вызова
Иногда вы можете встретить некоторые странные директивы ассемблера, начиная с .cfi
, чередующихся с заголовком функции.Это так называемая информация о кадре вызова .Он используется отладчиками для отслеживания вызовов функций.Но он также используется для обработки исключений в языках высокого уровня, которые требуют разматывания стека и других манипуляций на основе стека вызовов.Вы также можете отключить его в вашей сборке, добавив переключатель -fno-dwarf2-cfi-asm
.Это говорит GCC использовать старые метки вместо этих странных директив .cfi
и добавляет специальные структуры данных в конце вашей сборки, ссылаясь на эти метки.Это не отключает CFI, а просто меняет формат на более «прозрачный»: таблицы CFI становятся видимыми для программиста.