Что такое указатель на базовый указатель и указатель стека? На что они указывают?
Используя этот пример, исходящий из wikipedia, в котором DrawSquare() вызывает DrawLine(),
![alt text]()
(Обратите внимание, что эта диаграмма имеет верхние адреса внизу и низкие адреса вверху.)
Может ли кто-нибудь объяснить мне, что ebp
и esp
в этом контексте?
Из того, что я вижу, я бы сказал, что указатель стека всегда находится в верхней части стека, а базовый указатель на начало текущей функции? Или что?
edit: Я имею в виду это в контексте оконных программ
edit2: И как работает eip
?
edit3: У меня есть следующий код из MSVС++:
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 развиваются в те моменты (я уже понял, как работает пролог, я просто хочу знать, что происходит после того, как я нажал аргументы в стеке и перед прологом).
Ответы
Ответ 1
esp
, как вы говорите, это вершина стека.
ebp
обычно устанавливается в esp
в начале функции. Доступ к функциональным параметрам и локальным переменным осуществляется путем добавления и вычитания, соответственно, постоянного смещения от ebp
. Все соглашения о вызовах x86 определяют ebp
как сохраняющиеся во всех вызовах функций. ebp
сам на самом деле указывает на предыдущий указатель базы данных, который позволяет стеку ходить в отладчике и просматривать другие локальные переменные фреймов.
Большинство прологов функций выглядят примерно так:
push ebp ; Preserve current frame pointer
mov ebp, esp ; Create new frame pointer pointing to current stack top
sub esp, 20 ; allocate 20 bytes worth of locals on stack.
Затем в функции у вас может быть такой код (предполагая, что обе локальные переменные составляют 4 байта)
mov [ebp-4], eax ; Store eax in first local
mov ebx, [ebp - 8] ; Load ebx from second local
FPO или оптимизация пропуска указателя фрейма, который вы можете включить, фактически устранит это и будет использовать ebp
как другой регистр и локальные локаторы доступа непосредственно из esp
, но это делает отладку немного сложнее, поскольку отладчик больше не может напрямую доступ к кадрам стека более ранних вызовов функций.
EDIT:
В вашем обновленном вопросе недостающие две записи в стеке:
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
*savedFramePointer = dword ptr 0*
*return address = dword ptr 4*
hInstance = dword ptr 8h
PrevInstance = dword ptr 0C
hlpCmdLine = dword ptr 10h
nShowCmd = dword ptr 14h
Это связано с тем, что поток вызова функции:
- Нажмите параметры (
hInstance
и т.д.)
- Функция вызова, которая выталкивает адрес возврата
- Нажмите
ebp
- Выделить место для локальных пользователей
Ответ 2
ESP - это текущий указатель стека, который будет меняться каждый раз, когда слово или адрес помещаются или удаляются из стека. EBP является более удобным способом для компилятора отслеживать параметры функции и локальные переменные, чем непосредственное использование ESP.
Как правило (и это может варьироваться от компилятора к компилятору), все аргументы вызываемой функции помещаются в стек вызывающей функцией (обычно в обратном порядке, который они объявлены в прототипе функции, но это варьируется), Затем вызывается функция, которая помещает адрес возврата (EIP) в стек.
При входе в функцию старое значение EBP помещается в стек, а для EBP устанавливается значение ESP. Затем ESP уменьшается (поскольку стек увеличивается в памяти), чтобы выделить пространство для локальных переменных и временных переменных функции. С этого момента во время выполнения функции аргументы функции расположены в стеке с положительными смещениями от EBP (потому что они были переданы до вызова функции), а локальные переменные расположены с отрицательными смещениями от EBP (потому что они были размещены в стеке после входа в функцию). Вот почему EBP называется указателем кадра, потому что он указывает на центр кадра вызова функции.
После выхода все, что должна сделать функция, это установить ESP в значение EBP (которое освобождает локальные переменные из стека и выставляет запись EBP на вершине стека), затем выталкивает старое значение EBP из стека, и затем функция возвращается (вставляя адрес возврата в EIP).
Вернувшись к вызывающей функции, он может затем увеличить ESP, чтобы удалить аргументы функции, помещенные в стек непосредственно перед вызовом другой функции. На этом этапе стек вернулся в то же состояние, в котором он находился до вызова вызываемой функции.
Ответ 3
У вас все в порядке. Указатель стека указывает на верхний элемент в стеке, а базовый указатель указывает на "предыдущую" вершину стека перед вызовом функции.
При вызове функции любая локальная переменная будет сохранена в стеке, и указатель стека будет увеличен. Когда вы возвращаетесь из функции, все локальные переменные в стеке выходят за рамки. Вы делаете это, устанавливая указатель стека обратно на базовый указатель (который был "предыдущим" сверху перед вызовом функции).
Выполнение выделения памяти таким образом очень, очень быстро и эффективно.
Ответ 4
РЕДАКТИРОВАТЬ: Более подробное описание см. В разделе Разборка/Функции 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 в конфигурации выпуска, поэтому вам будет сложно выполнять обход стека без dbghlp.dll.
Ответ 5
Прежде всего, указатель стека указывает на нижнюю часть стека, так как стеки x86 строятся от высоких значений адреса до более низких значений адреса. Указатель стека - это точка, в которой следующий вызов для push (или вызова) поместит следующее значение. Эта операция эквивалентна оператору C/С++:
// push eax
--*esp = eax
// pop eax
eax = *esp++;
// a function call, in this case, the caller must clean up the function parameters
move eax,some value
push eax
call some address // this pushes the next value of the instruction pointer onto the
// stack and changes the instruction pointer to "some address"
add esp,4 // remove eax from the stack
// a function
push ebp // save the old stack frame
move ebp, esp
... // do stuff
pop ebp // restore the old stack frame
ret
Базовый указатель находится сверху текущего кадра. ebp обычно указывает на ваш адрес возврата. ebp + 4 указывает на первый параметр вашей функции (или это значение метода класса). ebp-4 указывает на первую локальную переменную вашей функции, обычно старое значение ebp, чтобы вы могли восстановить указатель предыдущего кадра.
Ответ 6
Долгое время с тех пор, как я сделал программирование сборки, но эта ссылка может быть полезна...
Процессор имеет набор регистров, которые используются для хранения данных. Некоторые из них являются прямыми значениями, а другие - областью внутри ОЗУ. Регистры обычно используются для определенных конкретных действий, и каждый операнд в сборке потребует определенного количества данных в конкретных регистрах.
Указатель стека в основном используется, когда вы вызываете другие процедуры. С современными компиляторами куча данных сначала будет сброшена в стек, а затем обратный адрес, чтобы система узнала, куда вернуться, как только она вернется. Указатель стека будет указывать на следующее место, где новые данные могут быть перенесены в стек, где он будет оставаться до тех пор, пока он не вернется назад.
Базовые регистры или регистры сегментов просто указывают на адресное пространство большого объема данных. В сочетании со вторым регистром базовый указатель будет делить память на огромные блоки, а второй регистр будет указывать на элемент внутри этого блока. Базовые указатели для этого указывают на базу блоков данных.
Помните, что сборка очень специфична для процессора. Страница, с которой я связан, предоставляет информацию о различных типах процессоров.
Ответ 7
Править Да, это в основном неправильно. Он описывает что-то совершенно другое, если кому-то интересно:)
Да, указатель стека указывает на вершину стека (будь то первое пустое место стека или последнее полное, которое я не уверен). Базовый указатель указывает на местоположение памяти исполняемой команды. Это на уровне кодов операций - самой базовой инструкции, которую вы можете получить на компьютере. Каждый код операции и его параметры сохраняются в памяти. Одна линия C или С++ или С# может быть переведена на один код операции или на последовательность из двух или более в зависимости от того, насколько она сложна. Они записываются в программную память последовательно и выполняются. При нормальных обстоятельствах базовый указатель увеличивается на одну команду. Для управления программой (GOTO, IF и т.д.) Его можно увеличить несколько раз или просто заменить на следующий адрес памяти.
В этом контексте функции хранятся в памяти программы по определенному адресу. Когда функция вызывается, в стек попадает определенная информация, которая позволяет программе найти ее обратно туда, откуда была вызвана функция, а также параметры для функции, тогда адрес функции в программной памяти вставляется в базовый указатель. На следующем такте компьютер начинает выполнять инструкции с этого адреса памяти. Затем в какой-то момент он ВОЗВРАЩАЕТСЯ в ячейку памяти ПОСЛЕ инструкции, вызывающей эту функцию, и продолжения оттуда.
Ответ 8
esp означает "Extended Stack Pointer"..... ebp для "Что-то базовый указатель".... и eip для "Что-то указатель инструкций"......
Указатель стека указывает на адрес смещения сегмента стека.
Базовый указатель указывает на адрес смещения дополнительного сегмента.
Указатель инструкций указывает на адрес смещения сегмента кода.
Теперь о сегментах... это небольшие 64 КБ деления области памяти процессоров..... Этот процесс называется сегментированием памяти.
Я надеюсь, что этот пост был полезен.