Что такое базовый указатель и указатель стека? На что они указывают?

используя из Википедии, в которой DrawSquare () вызывает DrawLine (),

alt text

(обратите внимание, что эта диаграмма имеет высокие адреса внизу и низкие адреса вверху.)

может кто-нибудь объяснить мне, что ebp и esp в этом контексте?

из того, что я вижу, я бы сказал, что указатель стека всегда указывает на верхнюю часть стека, а базовый указатель на начало текущей функции? Или что?


edit: я имею в виду это в контексте программ 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

все они кажутся dwords, таким образом, принимая 4 байта каждый. Поэтому я вижу, что есть разрыв от hInstance до var_4 4 байтов. Каковы они? Я предполагаю, что это обратный адрес, как видно из Википедии фото?


(Примечание редактора: удалена длинная цитата из ответа Майкла, которая не относится к вопросу, но следующий вопрос был отредактирован):

это потому, что поток вызова функции:

* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals

мой вопрос (последний, я надеюсь!) теперь, что именно происходит с момента появления аргументов функции, которую я хочу вызвать, до конца пролога? Я хочу знать, как ebp, esp развиваются во время этих моменты (я уже понял, как работает пролог, я просто хочу знать, что происходит после того, как я нажал аргументы в стеке и до пролога).

8 ответов


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

это потому, что поток вызова функции:

  • Push параметры (hInstance, etc.)
  • функция вызова, которая толкает обратный адрес
  • Push ebp
  • выделить место для местных

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

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

при входе в функцию старое значение EBP помещается в стек, и EBP устанавливается в значение ESP. Затем ESP уменьшается (потому что стек растет вниз в памяти), чтобы выделить пространство для локальных переменных и временных функций. С этого момента во время выполнения функции аргументы функции располагаются в стеке с положительным значением смещения от EBP (потому что они были нажаты до вызова функции), а локальные переменные расположены в отрицательных смещениях от EBP (потому что они были выделены в стеке после записи функции). Вот почему EBP называется указатель фрейма, потому что он указывает к центру функция кадр вызова.

при выходе все, что должна сделать функция, это установить ESP на значение EBP (которое освобождает локальные переменные от стек, и предоставляет запись EBP в верхней части стека), затем всплывающее старое значение EBP из стека, а затем функция возвращает (выталкивая обратный адрес в EIP).


вы правы. Указатель стека указывает на верхний элемент стека, а базовый указатель указывает на " предыдущая " верхняя часть стека перед вызовом функции.

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

выполнение выделения памяти таким образом очень, очень быстро и эффективно.


изменить: для лучшего описания см. x86 разборка / функции и кадры стека в WikiBook о сборке x86. Я пытаюсь добавить информацию, которая может вас заинтересовать в использовании Visual Studio.

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

говоря о программах Windows, вы, вероятно, можете использовать Visual Studio для компиляции кода C++. Имейте в виду, что Microsoft использует оптимизацию, называемую пропуском указателя кадра, что делает почти невозможным ходить по стеку без использования библиотеки dbghlp и файла PDB для исполняемого файла.

Это опущение указателя кадра означает, что компилятор не хранит старый EBP на стандартное место и использует регистр EBP для чего-то еще, поэтому вам трудно найти вызывающий EIP, не зная, сколько места нужно локальным переменным для данной функции. Конечно, Microsoft предоставляет API, который позволяет выполнять Stack-walks даже в этом случае, но поиск базы данных таблицы символов в файлах PDB занимает слишком много времени для некоторых случаев использования.

чтобы избежать FPO в ваших единицах компиляции, вам нужно избегать использования /O2 или явно добавлять /Oy - в C++ флаги компиляции в ваших проектах. Вероятно, вы ссылаетесь на среду выполнения C или C++, которая использует FPO в конфигурации выпуска, поэтому вам будет трудно выполнять прогулки по стеку без dbghlp.файл DLL.


прежде всего, указатель стека указывает на нижнюю часть стека, так как стеки x86 строятся от значений высокого адреса до более низких значений адреса. Указатель стека-это точка, в которой следующий вызов для push (или вызова) разместит следующее значение. Это операция эквивалентна оператору C/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, поэтому вы можете восстановить предыдущий указатель кадра.


долгое время, так как я сделал Программирование сборки, но этой ссылке может быть полезно...

процессор имеет набор регистров, которые используются для хранения данных. Некоторые из них являются прямыми значениями, в то время как другие указывают на область в ОЗУ. Регистры, как правило, используются для определенных конкретных действий, и каждый операнд в сборке потребует определенного объема данных в конкретных регистрах.

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

базовые регистры или сегментные регистры просто указывают на адресное пространство большого объема данных. В сочетании со вторым regiser, базовый указатель разделите память на огромные блоки, в то время как второй регистр будет указывать на элемент внутри этого блока. Базовые указатели для этого указывают на базу блоков данных.

имейте в виду, что сборка очень специфична для процессора. Страница, на которую я ссылаюсь, содержит информацию о различных типах процессоров.


редактировать Да, это в основном неправильно. Он описывает что-то совершенно другое, если кто-то заинтересован :)

да, указатель стека указывает на верхнюю часть стека (будь то первое пустое место стека или последнее полное, в котором я не уверен). Базовый указатель указывает на расположение в памяти выполняемой инструкции. Это на уровне опкодов - самая основная инструкция, которую можно получить на компьютере. Каждый опкод и его параметры хранятся в памяти. Одна строка C, C++ или C# может быть переведена в один код операции или последовательность из двух или более в зависимости от сложности. Они записываются в память программы последовательно и выполняются. При нормальных обстоятельствах, базовый указатель увеличивается на одну инструкцию. Для управления программой (GOTO, IF и т. д.) Ее можно увеличить несколько раз или просто заменить следующим адресом памяти.

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


esp означает "расширенный указатель стека".....ebp для"чего-то базового указателя"....и eip Для "что-то указателя инструкции"...... Указатель стека указывает на адрес смещения сегмента стека. Базовый указатель указывает на адрес смещения дополнительного сегмента. Указатель инструкции указывает на адрес смещения сегмента кода. Теперь о сегментах...это небольшие 64KB подразделения области памяти процессоров.....Этот процесс известен как сегментация памяти. Я надеюсь, что это сообщение было полезно.