language - assembly pelicula
¿Cuál es el propósito de EBP en el siguiente código? (3)
EBP
forma un punto fijo de referencia para las variables en la pila: principalmente todos los parámetros de una función, todos los parámetros locales de la función y finalmente la dirección de retorno. Con este punto fijo, una función puede crecer / alterar su stack casi al azar, saltar al epílogo de función desde cualquier punto y restaurar el puntero de la pila a la posición original.
El concepto era casi obligatorio, ya que el código 8086 original no permitía usar el puntero de pila con el desplazamiento como en mov ax, [sp + 10]
, pero solo con push
y pop
. Referencia a cualquier otra cosa, pero el elemento superior debe hacerse con mov xx, [bp + 10]
.
Tengo dos preguntas sobre el registro EBP.
Entiendo ESP y EIP. Sin embargo, realmente no entiendo por qué uno usaría EBP.
En el siguiente código, presiono el registro EBP (que en realidad es 0000000) a la pila. Luego muevo la dirección de memoria de la pila a EBP para que ESP y EBP tengan los mismos datos. Este es el prólogo. Hay un código que termina con syscall. Luego hago lo inverso (el epílogo) ya que ''leave'' indica que muevo EBP a ESP (estos valores son los mismos gracias al prólogo) y luego mosto el último valor de la pila (que es EBP, que es 00000000) a EBP. Esto le da a EBP el mismo valor que pasó antes del prólogo.
¿Por qué alguien haría esto? ¿Cual es el punto? Por favor responde de una manera simple! Recuerde que no capto lo que realmente hace EBP (el puntero del cuadro).
EDITAR: ¿o es que esta es una forma de hacer una copia de seguridad efectiva de la pila (ESP) cuando está en una función? En otras palabras: el programa puede hacer lo que hace con la pila y la ''pila original'' siempre estará ahí en EBP. Luego, cuando el programa finaliza, EBP vuelve a ser como era antes. ¿Es esto correcto? Si es así, ¿el epílogo es solo una rutina de limpieza?
Además, AIUI, puedo usar ''enter'' para reemplazar ''push ebp / mov ebp, esp''. Sin embargo, cuando intento compilar en nasm, obtengo ''error: combinación inválida de código de operación y operandos'' ''leave'' funciona bien; ''enter'' no. ¿Cual es la sintaxis correcta?
¡Gracias!
Example:
push ebp
mov, ebp, esp
[some code here]
int 0x80
leave
ret
enter
también necesita un número que es la cantidad de espacio para asignar, que es la clave de su pregunta: estas instrucciones se realizan para configurar el espacio para las variables locales de su función.
Las variables locales se refieren a través del registro EBP. Dejame mostrarte un ejemplo:
import core.stdc.stdio;
void main() {
int a = 8;
a += 8;
printf("%d/n", 8);
}
(Este es el código D, pero eso no es realmente relevante)
Disassembly of section .text._Dmain:
00000000 <_Dmain>:
0: 55 push ebp
1: 8b ec mov ebp,esp
3: 83 ec 04 sub esp,0x4
6: b8 08 00 00 00 mov eax,0x8
b: 89 45 fc mov DWORD PTR [ebp-0x4],eax
e: 01 45 fc add DWORD PTR [ebp-0x4],eax
11: 50 push eax
12: b9 00 00 00 00 mov ecx,"%d/n"
17: 51 push ecx
18: e8 fc ff ff ff call printf
1d: 31 c0 xor eax,eax
1f: 83 c4 08 add esp,0x8
22: c9 leave
23: c3 ret
Vamos a dividir esto en cada parte:
0: 55 push ebp
1: 8b ec mov ebp,esp
3: 83 ec 04 sub esp,0x4
Este es el prólogo de función, configurando ebp. El sub esp, 0x4
empujó la pila a 4 bytes de distancia, esto deja espacio para nuestra variable int a
local, que tiene 4 bytes de longitud.
La instrucción enter
raramente se usa, pero creo que enter 4,0
hace lo mismo: ingrese una función con 4 bytes de espacio variable local. editar: El otro 0 es el nivel de anidación, nunca lo he visto usar ... enter es generalmente más lento que solo hacer los pasos usted mismo como lo hace el compilador aquí. /editar
6: b8 08 00 00 00 mov eax,0x8
b: 89 45 fc mov DWORD PTR [ebp-0x4],eax
Esta es la línea a a=8
- la segunda línea almacena el valor en la memoria de la variable local.
e: 01 45 fc add DWORD PTR [ebp-0x4],eax
Luego, le agregamos a+=8
(el compilador reutilizó eax aquí ya que reconoció que el número es el mismo ...)
Después de eso, llama a printf empujando sus argumentos a la pila, luego pone a cero el registro eax ( xor eax, eax
), que es como D devuelve 0 de una función.
11: 50 push eax
12: b9 00 00 00 00 mov ecx,"%d/n"
17: 51 push ecx
18: e8 fc ff ff ff call printf
1d: 31 c0 xor eax,eax
1f: 83 c4 08 add esp,0x8
Tenga en cuenta que add esp, 0x8
aquí es parte de la llamada a printf: quien llama es responsable de limpiar los argumentos después de llamar a la función. Esto es necesario porque solo quien llama sabe cuántos argumentos envió en realidad, esto es lo que habilita los argumentos variables de printf.
De todos modos, finalmente, limpiamos nuestras variables locales y regresamos de la función:
22: c9 leave
23: c3 ret
editar: leave
cierto se expande a mov esp, ebp; pop ebp;
mov esp, ebp; pop ebp;
- es exactamente lo contrario de las instrucciones de configuración, y como dijo Aki Suihkonen en la otra respuesta, una cosa buena aquí es que la pila se restablece a cómo estaba en la entrada de función, sin importar lo que sucedió dentro de la función (bueno, a menos que la función destruyó totalmente el contenido de la pila, en cuyo caso lo más probable es que su programa se bloquee pronto). /editar
Entonces, en resumidas cuentas, las cosas de ebp tienen que ver con sus variables locales. Utiliza esp para empezar, por lo que tiene un buen espacio de memoria que no interfiere con otras funciones (está en la pila de llamadas), sino que lo mueve a ebp para que los locales permanezcan en una compensación constante en toda la función: la variable a
SIEMPRE [EBP-4] está en esta función, incluso cuando se manipula la pila.
Es más fácil ver en acción desensamblando una función que escribes en C o algo así, como hicimos aquí. El comando linux objdump -d -M intel somefile.o
es lo que usé (luego, manualmente arreglé algunas cosas menores para hacerlo más legible. Si desensamblas un archivo .o, no todas las llamadas a la biblioteca se resuelven todavía, así que puede parecer un poco raro, pero eso no afecta las variables locales!)
La idea de EBP es efectivamente formar un punto fijo de referencia. A menudo puede juguetear con el puntero de la pila (por ejemplo, al presionar los parámetros en la pila para una llamada) y encontrar un verdadero dolor para descubrir dónde está una pieza de datos relativa al puntero de la pila. Pero en relación con el puntero de base, siempre es el mismo. Los compiladores modernos no tienen dificultad para resolver esto, pero si quisieras escribir una gran pieza de código de ensamblador (a mano) que use la pila para empujar y estallar, te resultaría más fácil hacer referencia a tus variables locales en relación con un registro (EBP ) eso no cambia