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

Используя этот пример, исходящий из 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 КБ деления области памяти процессоров..... Этот процесс называется сегментированием памяти. Я надеюсь, что этот пост был полезен.