Используя этот пример из Википедии, в которой DrawSquare () вызывает DrawLine (),
(Обратите внимание, что эта диаграмма имеет высокие адреса внизу и низкие адреса вверху.)
Может ли кто-нибудь объяснить мне, что ebp
и esp
в этом контексте?
Из того, что я вижу, я бы сказал, что указатель стека всегда указывает на вершину стека, а базовый указатель - на начало текущей функции? Или что?
редактировать: я имею в виду это в контексте программ Windows
edit2: И как тоже eip
работает?
edit3: у меня есть следующий код из MSVC ++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
Все они кажутся мечами, поэтому занимают по 4 байта каждый. Таким образом, я вижу, что разрыв от hInstance до var_4 составляет 4 байта. Кто они такие? Я предполагаю, что это обратный адрес, как видно на картинке из Википедии?
(примечание редактора: удалена длинная цитата из ответа Майкла, которая не относится к данному вопросу, но дополнительный вопрос был отредактирован в):
Это потому, что поток вызова функции:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Мой вопрос (последний, я надеюсь!) Теперь таков: что именно происходит с того момента, как я выдвигаю аргументы функции, которую я хочу вызвать, до конца пролога? Я хочу знать, как ebp, esp развиваются в эти моменты (я уже понял, как работает пролог, я просто хочу знать, что происходит после того, как я поместил аргументы в стек и перед прологом).
Ответы:
esp
это, как вы говорите, вершина стека.ebp
обычно устанавливаетсяesp
в начале функции. Доступ к параметрам функции и локальным переменным осуществляется путем сложения и вычитания, соответственно, постоянного смещения изebp
. Все соглашения о вызовах x86 определяютсяebp
как сохраняемые при вызовах функций.ebp
сам по себе указывает на базовый указатель предыдущего кадра, что позволяет обходить стек в отладчике и просматривать локальные переменные других кадров для работы.Большинство прологов функций выглядят примерно так:
Затем в функции может появиться такой код (при условии, что обе локальные переменные имеют размер 4 байта)
Оптимизация пропуска FPO или указателя кадра, которую вы можете включить, фактически устранит это и будет использовать в
ebp
качестве другого регистра и напрямую получать доступ к локальным объектамesp
, но это немного затрудняет отладку, поскольку отладчик больше не может напрямую обращаться к кадрам стека предыдущих вызовов функций.РЕДАКТИРОВАТЬ:
Для вашего обновленного вопроса, пропущены две записи в стеке:
Это потому, что поток вызова функции:
hInstance
и т. Д.)ebp
источник
ESP - это текущий указатель стека, который будет меняться каждый раз, когда слово или адрес помещаются или удаляются из стека. EBP - более удобный способ для компилятора отслеживать параметры функции и локальные переменные, чем непосредственное использование ESP.
Как правило (и это может варьироваться от компилятора к компилятору), все аргументы вызываемой функции помещаются в стек вызывающей функцией (обычно в обратном порядке, который они объявлены в прототипе функции, но это варьируется) , Затем вызывается функция, которая помещает адрес возврата (EIP) в стек.
При входе в функцию старое значение EBP помещается в стек, а для EBP устанавливается значение ESP. Затем ESP уменьшается (поскольку стек увеличивается в памяти), чтобы выделить место для локальных переменных и временных переменных функции. С этого момента, во время выполнения функции, аргументы функции расположены в стеке с положительными смещениями от EBP (потому что они были переданы до вызова функции), а локальные переменные расположены с отрицательными смещениями от EBP (потому что они были размещены в стеке после входа в функцию). Вот почему EBP называется указателем кадра , потому что он указывает на центр кадра вызова функции .
После выхода все, что должна сделать функция, это установить ESP в значение EBP (которое освобождает локальные переменные из стека и выставляет запись EBP на вершине стека), затем выталкивает старое значение EBP из стека, и затем функция возвращается (вставляя адрес возврата в EIP).
Вернувшись к вызывающей функции, он может затем увеличить ESP, чтобы удалить аргументы функции, помещенные в стек непосредственно перед вызовом другой функции. На этом этапе стек вернулся в то же состояние, в котором он находился до вызова вызываемой функции.
источник
Вы правильно поняли. Указатель стека указывает на верхний элемент стека, а базовый указатель указывает на «предыдущую» вершину стека до вызова функции.
Когда вы вызываете функцию, любая локальная переменная будет храниться в стеке, а указатель стека будет увеличиваться. Когда вы возвращаетесь из функции, все локальные переменные в стеке выходят из области видимости. Вы делаете это, устанавливая указатель стека обратно на базовый указатель (который был «предыдущей» вершиной перед вызовом функции).
Распределение памяти таким способом очень , очень быстро и эффективно.
источник
РЕДАКТИРОВАТЬ: Более подробное описание см. В разделе Разборка / Функции x86 и стековые рамки в WikiBook о сборке x86. Я пытаюсь добавить информацию, которая может быть вам интересна при использовании Visual Studio.
Хранение вызывающего EBP в качестве первой локальной переменной называется стандартным стековым фреймом, и это может использоваться почти для всех соглашений о вызовах в Windows. Существуют различия, независимо от того, освобождает ли вызывающий или вызываемый объект переданные параметры и какие параметры передаются в регистрах, но они ортогональны стандартной задаче стека.
Говоря о программах Windows, вы, вероятно, могли бы использовать Visual Studio для компиляции кода C ++. Имейте в виду, что Microsoft использует оптимизацию под названием Frame Pointer Omission, что делает практически невозможным обход стека без использования библиотеки dbghlp и файла PDB для исполняемого файла.
Это опущение указателя кадра означает, что компилятор не хранит старый EBP в стандартном месте и использует регистр EBP для чего-то другого, поэтому вам трудно найти EIP вызывающего, не зная, сколько места нужно локальным переменным для данной функции. Конечно, Microsoft предоставляет API, который позволяет вам выполнять обход стека даже в этом случае, но поиск базы данных таблицы символов в файлах PDB занимает слишком много времени для некоторых случаев использования.
Чтобы избежать FPO в ваших модулях компиляции, вы должны избегать использования / O2 или явно добавлять / Oy- к флагам компиляции C ++ в ваших проектах. Вы, вероятно, ссылаетесь на среду выполнения C или C ++, которая использует FPO в конфигурации Release, поэтому вам будет сложно выполнять обход стека без dbghlp.dll.
источник
Прежде всего, указатель стека указывает на дно стека, так как стеки x86 строят от высоких значений адресов до более низких значений адресов. Указатель стека - это точка, в которой следующий вызов push (или call) установит следующее значение. Эта операция эквивалентна выражению C / C ++:
Базовый указатель является вершиной текущего кадра. EBP обычно указывает на ваш обратный адрес. EBP + 4 указывает на первый параметр вашей функции (или значение this метода класса). ebp-4 указывает на первую локальную переменную вашей функции, обычно это старое значение ebp, поэтому вы можете восстановить предыдущий указатель кадра.
источник
Давно я занимался программированием на ассемблере, но эта ссылка может быть полезной ...
Процессор имеет коллекцию регистров, которые используются для хранения данных. Некоторые из них являются прямыми значениями, в то время как другие указывают на область в оперативной памяти. Регистры обычно используются для определенных конкретных действий, и для каждого операнда в сборке требуется определенный объем данных в определенных регистрах.
Указатель стека в основном используется при вызове других процедур. В современных компиляторах куча данных сначала будет выгружена в стек, а затем обратный адрес, чтобы система знала, куда возвращаться, как только ей будет сказано вернуться. Указатель стека будет указывать на следующее место, где новые данные могут быть помещены в стек, где они будут оставаться до тех пор, пока не будут возвращены обратно.
Базовые регистры или регистры сегментов просто указывают на адресное пространство большого объема данных. В сочетании со вторым регистром базовый указатель разделит память на огромные блоки, а второй регистр будет указывать на элемент в этом блоке. Базовые указатели для этого указывают на базу блоков данных.
Имейте в виду, что сборка очень сильно зависит от процессора. Страница, на которую я ссылаюсь, содержит информацию о различных типах процессоров.
источник
редактировать Да, это в основном неправильно. Это описывает что-то совершенно другое на случай, если кому-то интересно :)
Да, указатель стека указывает на вершину стека (будь то первая пустая ячейка или последняя полная, в которой я не уверен). Базовый указатель указывает на область памяти выполняемой инструкции. Это на уровне кодов операций - самая основная инструкция, которую вы можете получить на компьютере. Каждый код операции и его параметры хранятся в ячейке памяти. Одна строка C или C ++ или C # может быть переведена в один код операции или последовательность из двух или более в зависимости от сложности. Они записываются в память программы последовательно и выполняются. При нормальных обстоятельствах базовый указатель увеличивается на одну инструкцию. Для управления программой (GOTO, IF и т. Д.) Его можно увеличить несколько раз или просто заменить следующим адресом памяти.
В этом контексте функции хранятся в памяти программ по определенному адресу. Когда функция вызывается, в стек помещается определенная информация, которая позволяет программе найти, откуда она была вызвана, а также параметры функции, а затем адрес функции в памяти программы помещается в базовый указатель. На следующем тактовом цикле компьютер начинает выполнять инструкции с этого адреса памяти. Затем в какой-то момент он вернется в ячейку памяти ПОСЛЕ инструкции, вызвавшей функцию, и продолжит оттуда.
источник
esp означает «Расширенный указатель стека» ..... ebp для «Something Base Pointer» .... и eip для «Something Instruction Pointer» ...... Указатель стека указывает на адрес смещения сегмента стека , Базовый указатель указывает на адрес смещения дополнительного сегмента. Указатель инструкций указывает на адрес смещения сегмента кода. Теперь о сегментах ... это небольшие 64КБ деления области памяти процессоров ..... Этот процесс известен как сегментация памяти. Я надеюсь, что этот пост был полезным.
источник