2009-07-18 16 views
15

Hola He desmontado algunos programas (Linux) que escribí para entender mejor cómo funciona, y me di cuenta que la función principal siempre comienza con:tratar de entender las principales primeras instrucciones de desmontaje

lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ? 
and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ??? 
push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ? 
push ebp     
mov ebp,esp 
push ecx ;why is ecx pushed too ?? 

así que mi pregunta es: ¿por qué se hace todo este trabajo? que sólo entienden el uso de:

push ebp     
mov ebp,esp 

el resto parece inútil para mí ...

+1

¿Qué compilador está utilizando y puede proporcionar la subrutina principal desensamblada completa? – Inshallah

Respuesta

25

He tenido un ir en él:

;# As you have already noticed, the compiler wants to align the stack 
;# pointer on a 16 byte boundary before it pushes anything. That's 
;# because certain instructions' memory access needs to be aligned 
;# that way. 
;# So in order to first save the original offset of esp (+4), it 
;# executes the first instruction: 
lea ecx,[esp+0x4] 

;# Now alignment can happen. Without the previous insn the next one 
;# would have made the original esp unrecoverable: 
and esp,0xfffffff0 

;# Next it pushes the return addresss and creates a stack frame. I 
;# assume it now wants to make the stack look like a normal 
;# subroutine call: 
push DWORD PTR [ecx-0x4] 
push ebp 
mov ebp,esp 

;# Remember that ecx is still the only value that can restore the 
;# original esp. Since ecx may be garbled by any subroutine calls, 
;# it has to save it somewhere: 
push ecx 
+1

+1, esto es lo que está pasando + una buena explicación. Los límites de 16 bytes son ideales porque se requieren para el uso de instrucciones SIMD (MMX/SSE/SSE2, etc.). Si intenta utilizar una instrucción SIMD alineada en un valor alineado no de 16 bytes en la pila, usted segmentaría. – Falaina

+0

@Falaina: ¡Gracias! Editado para reflejar su entrada. – Inshallah

5

Esto se hace para mantener la pila alineada a un límite de 16 bytes. Algunas instrucciones requieren que ciertos tipos de datos se alineen en un límite de 16 bytes. Para cumplir este requisito, GCC se asegura de que la pila esté inicialmente alineada con 16 bytes, y asigna el espacio de la pila en múltiplos de 16 bytes. Esto se puede controlar usando la opción -mpreferred-stack-boundary=num. Si usa -mpreferred-stack-boundary = 2 (para un 2 = alineación de 4 bytes), este código de alineación no se generará porque la pila siempre está alineada con al menos 4 bytes. Sin embargo, podría tener problemas si su programa utiliza algún tipo de datos que requiera una alineación más fuerte.

De acuerdo con el manual de gcc:

En Pentium y PentiumPro, los valores dobles dobles y largo plazo deben estar alineados a un límite de 8 bytes (ver -malign doble) o sufren importantes reducciones del rendimiento en tiempo de ejecución. En Pentium III, el tipo de datos Streaming SIMD Extension (SSE) __m128 puede no funcionar correctamente si no está alineado 16 bytes.

Para asegurar la alineación correcta de estos valores en la pila, el límite de la pila debe estar tan alineado como el requerido por cualquier valor almacenado en la pila. Además, cada función debe generarse de manera que mantenga la pila alineada. Por lo tanto, llamar a una función compilada con un límite de pila preferido superior a partir de una función compilada con un límite de pila preferido inferior probablemente desalineará la pila. Se recomienda que las bibliotecas que usan devoluciones de llamada usen siempre la configuración predeterminada.

Esta alineación extra consume espacio adicional en la pila y generalmente aumenta el tamaño del código. El código que es sensible al uso del espacio de la pila, como los sistemas integrados y los kernels del sistema operativo, puede querer reducir la alineación preferida a -mpreferred-stack-boundary = 2.

Los lea cargas el puntero de pila original (desde antes de la llamada a main) en ecx, ya que el puntero de pila está a punto de modificado. Esto se utiliza para dos fines:

  1. para acceder a los argumentos de la función main, ya que son en relación con el puntero de pila original,
  2. para restaurar el puntero de pila a su valor original al volver de main
4
lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of  the main...why ? 
and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ??? 
push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ? 
push ebp     
mov ebp,esp 
push ecx ;why is ecx pushed too ?? 

Incluso si cada instrucción funcionó perfectamente sin penalización de velocidad a pesar de los operandos alineados arbitrariamente, la alineación aún aumentaría el rendimiento. Imagine un bucle que hace referencia a una cantidad de 16 bytes que simplemente se superpone a dos líneas de caché.Ahora, para cargar ese pequeño wchar en el caché, dos líneas de caché completas tienen que ser desalojadas, ¿y qué pasa si las necesitas en el mismo ciclo? La memoria caché es tremendamente más rápida que la RAM, por lo que el rendimiento de la memoria caché siempre es crítico.

Además, generalmente hay una penalización de velocidad para desviar los operandos desalineados a los registros. Dado que la pila se está realineando, naturalmente tenemos que guardar la alineación anterior para atravesar los marcos de pila para los parámetros y regresar.

ecx es un registro temporal por lo que debe guardarse. Además, según el nivel de optimización, algunas de las operaciones de vinculación de trama que no parecen estrictamente necesarias para ejecutar el programa podrían ser importantes para configurar una cadena de tramas lista para trazar.

Cuestiones relacionadas