Usando este ejemplo proveniente de wikipedia, en el que DrawSquare () llama a DrawLine (),
(Tenga en cuenta que este diagrama tiene direcciones altas en la parte inferior y direcciones bajas en la parte superior).
¿Alguien podría explicarme qué ebp
y esp
en este contexto?
Por lo que veo, diría que el puntero de la pila apunta siempre a la parte superior de la pila, y el puntero de base al comienzo de la función actual. ¿O que?
editar: me refiero a esto en el contexto de los programas de Windows
edit2: ¿Y cómo eip
funciona también?
edit3: Tengo el siguiente código de 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
Todos ellos parecen ser dwords, por lo tanto toman 4 bytes cada uno. Entonces puedo ver que hay una brecha entre hInstance y var_4 de 4 bytes. ¿Qué son? Supongo que es la dirección de retorno, como se puede ver en la imagen de Wikipedia.
(Nota del editor: eliminó una larga cita de la respuesta de Michael, que no pertenece a la pregunta, pero se editó una pregunta de seguimiento):
Esto se debe a que el flujo de la llamada a la función es:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Mi pregunta (por último, ¡espero!) Ahora es, ¿qué es exactamente lo que sucede desde el instante en que hago estallar los argumentos de la función que quiero invocar hasta el final del prólogo? Quiero saber cómo evoluciona el ebp, especialmente durante esos momentos (ya entendí cómo funciona el prólogo, solo quiero saber qué está sucediendo después de haber introducido los argumentos en la pila y antes del prólogo).
Respuestas:
esp
es como tú dices, la parte superior de la pila.ebp
generalmente se establece alesp
inicio de la función. Se accede a los parámetros de función y a las variables locales sumando y restando, respectivamente, un desplazamiento constante deebp
. Todas las convenciones de llamadas x86 se definenebp
como preservadas en las llamadas a funciones.ebp
en sí mismo apunta al puntero base del cuadro anterior, lo que permite que la pila recorra un depurador y vea otras variables locales de cuadros para que funcionen.La mayoría de los prólogos de funciones se parecen a:
Luego, más adelante en la función, puede tener un código similar (suponiendo que ambas variables locales sean de 4 bytes)
La optimización de omisión de puntero de cuadro o FPO que puede habilitar en realidad eliminará esto y lo usará
ebp
como otro registro y accederá a los locales directamente desde fueraesp
, pero esto hace que la depuración sea un poco más difícil ya que el depurador ya no puede acceder directamente a los cuadros de la pila de llamadas a funciones anteriores.EDITAR:
Para su pregunta actualizada, las dos entradas que faltan en la pila son:
Esto se debe a que el flujo de la llamada a la función es:
hInstance
, etc.)ebp
fuente
ESP es el puntero actual de la pila, que cambiará cada vez que una palabra o dirección se empuja o se saca de la pila. EBP es una forma más conveniente para que el compilador realice un seguimiento de los parámetros y las variables locales de una función que usar el ESP directamente.
Generalmente (y esto puede variar de un compilador a otro), todos los argumentos de una función que se llama son empujados a la pila por la función de llamada (generalmente en el orden inverso al declarado en el prototipo de la función, pero esto varía) . Luego se llama a la función, que empuja la dirección de retorno (EIP) a la pila.
Al ingresar a la función, el antiguo valor de EBP se inserta en la pila y EBP se establece en el valor de ESP. Luego, el ESP disminuye (porque la pila crece hacia abajo en la memoria) para asignar espacio para las variables locales y temporales de la función. A partir de ese momento, durante la ejecución de la función, los argumentos de la función se ubican en la pila en compensaciones positivas de EBP (porque fueron empujados antes de la llamada a la función), y las variables locales se ubican en compensaciones negativas de EBP (porque se asignaron en la pila después de la entrada de la función). Es por eso que el EBP se llama puntero de cuadro , porque apunta al centro del cuadro de llamada de función .
Al salir, todo lo que la función tiene que hacer es establecer ESP en el valor de EBP (que desasigna las variables locales de la pila y expone la entrada de EBP en la parte superior de la pila), luego extrae el antiguo valor de EBP de la pila, y luego la función regresa (ingresando la dirección de retorno en EIP).
Al regresar a la función de llamada, puede incrementar ESP para eliminar los argumentos de la función que introdujo en la pila justo antes de llamar a la otra función. En este punto, la pila vuelve al mismo estado que tenía antes de invocar la función llamada.
fuente
Lo tienes bien El puntero de la pila apunta al elemento superior de la pila y el puntero base apunta a la parte superior "anterior" de la pila antes de que se llamara a la función.
Cuando llama a una función, cualquier variable local se almacenará en la pila y el puntero de la pila se incrementará. Cuando regresa de la función, todas las variables locales en la pila quedan fuera de alcance. Para ello, vuelva a configurar el puntero de la pila en el puntero base (que era la parte superior "anterior" antes de la llamada a la función).
Hacer la asignación de memoria de esta manera es muy , muy rápido y eficiente.
fuente
EDITAR: Para una mejor descripción, vea Desmontaje / Funciones x86 y Marcos de pila en un WikiBook sobre ensamblaje x86. Intento agregar información que pueda interesarle usar Visual Studio.
El almacenamiento del EBP de la persona que llama como la primera variable local se denomina marco de pila estándar, y esto se puede usar para casi todas las convenciones de llamadas en Windows. Existen diferencias si la persona que llama o la persona que llama desasigna los parámetros pasados y qué parámetros se pasan en los registros, pero estos son ortogonales al problema estándar del marco de la pila.
Hablando de programas de Windows, es probable que use Visual Studio para compilar su código C ++. Tenga en cuenta que Microsoft usa una optimización llamada Frame Pointer Omission, que hace que sea casi imposible recorrer la pila sin usar la biblioteca dbghlp y el archivo PDB para el ejecutable.
Esta omisión de puntero de trama significa que el compilador no almacena el antiguo EBP en un lugar estándar y utiliza el registro EBP para otra cosa, por lo tanto, le resulta difícil encontrar el EIP del llamador sin saber cuánto espacio necesitan las variables locales para una función determinada. Por supuesto, Microsoft proporciona una API que le permite realizar recorridos de pila incluso en este caso, pero buscar la base de datos de la tabla de símbolos en archivos PDB lleva demasiado tiempo para algunos casos de uso.
Para evitar FPO en sus unidades de compilación, debe evitar usar / O2 o agregar explícitamente / Oy- a los indicadores de compilación de C ++ en sus proyectos. Probablemente se vincule con el tiempo de ejecución de C o C ++, que usa FPO en la configuración de lanzamiento, por lo que tendrá dificultades para realizar recorridos de pila sin el dbghlp.dll.
fuente
En primer lugar, el puntero de la pila apunta a la parte inferior de la pila, ya que las pilas x86 se acumulan desde valores de dirección altos a valores de dirección más bajos. El puntero de la pila es el punto donde la próxima llamada para empujar (o llamar) colocará el siguiente valor. Su operación es equivalente a la declaración C / C ++:
El puntero base está en la parte superior del marco actual. ebp generalmente apunta a su dirección de devolución. ebp + 4 señala el primer parámetro de su función (o el valor de este método de clase). ebp-4 apunta a la primera variable local de su función, generalmente el valor anterior de ebp para que pueda restaurar el puntero de fotograma anterior.
fuente
Hace mucho tiempo que no hago programación de ensamblaje, pero este enlace puede ser útil ...
El procesador tiene una colección de registros que se utilizan para almacenar datos. Algunos de estos son valores directos, mientras que otros apuntan a un área dentro de la RAM. Los registros tienden a usarse para ciertas acciones específicas y cada operando en el ensamblaje requerirá una cierta cantidad de datos en registros específicos.
El puntero de la pila se usa principalmente cuando llama a otros procedimientos. Con los compiladores modernos, un montón de datos se volcará primero en la pila, seguido de la dirección de retorno para que el sistema sepa dónde regresar una vez que se le indique que regrese. El puntero de la pila apuntará a la siguiente ubicación donde se pueden enviar nuevos datos a la pila, donde permanecerán hasta que vuelvan a aparecer.
Los registros base o los registros de segmento solo apuntan al espacio de direcciones de una gran cantidad de datos. Combinado con un segundo registrador, el puntero Base dividirá la memoria en grandes bloques, mientras que el segundo registro apuntará a un elemento dentro de este bloque. Los punteros de base apuntan a la base de bloques de datos.
Tenga en cuenta que el ensamblaje es muy específico de la CPU. La página a la que he vinculado proporciona información sobre los diferentes tipos de CPU.
fuente
Editar Sí, esto está muy mal. Describe algo completamente diferente en caso de que alguien esté interesado :)
Sí, el puntero de la pila apunta a la parte superior de la pila (ya sea que sea la primera ubicación de pila vacía o la última llena, no estoy seguro). El puntero base apunta a la ubicación de la memoria de la instrucción que se está ejecutando. Esto está en el nivel de los códigos de operación: la instrucción más básica que puede obtener en una computadora. Cada código de operación y sus parámetros se almacenan en una ubicación de memoria. Una línea C o C ++ o C # podría traducirse a un código de operación, o una secuencia de dos o más, dependiendo de lo complejo que sea. Estos se escriben en la memoria del programa secuencialmente y se ejecutan. En circunstancias normales, el puntero base se incrementa una instrucción. Para el control del programa (GOTO, IF, etc.) puede incrementarse varias veces o simplemente reemplazarse con la siguiente dirección de memoria.
En este contexto, las funciones se almacenan en la memoria del programa en una determinada dirección. Cuando se llama a la función, cierta información se inserta en la pila que permite que el programa encuentre su lugar donde se llamó la función, así como los parámetros de la función, luego la dirección de la función en la memoria del programa se inserta en el puntero base. En el siguiente ciclo de reloj, la computadora comienza a ejecutar instrucciones desde esa dirección de memoria. Luego, en algún momento, VOLVERÁ a la ubicación de la memoria DESPUÉS de la instrucción que llamó a la función y continuará desde allí.
fuente
esp significa "puntero de pila extendido" ..... ebp para "puntero de base de algo" ... y eip para "puntero de instrucción de algo" ...... El puntero de pila apunta a la dirección de desplazamiento del segmento de pila . El puntero base apunta a la dirección de desplazamiento del segmento adicional. El puntero de instrucción apunta a la dirección de desplazamiento del segmento de código. Ahora, sobre los segmentos ... son pequeñas divisiones de 64 KB del área de memoria de los procesadores ... Este proceso se conoce como Segmentación de memoria. Espero que esta publicación haya sido útil.
fuente