Lo he intentado:
;# 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
Esto se hace para mantener la pila alineada con un límite de 16 bytes. Algunas instrucciones requieren que ciertos tipos de datos estén alineados en un límite de hasta 16 bytes. Para cumplir con este requisito, GCC se asegura de que la pila esté inicialmente alineada en 16 bytes y asigna espacio de 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 una alineación de 2=4 bytes), este código de alineación no se generará porque la pila siempre tiene una alineación de al menos 4 bytes. Sin embargo, podría tener problemas si su programa usa cualquier tipo de datos que requiera una alineación más fuerte.
Según el manual de gcc:
En Pentium y PentiumPro, los valores double y long double deben alinearse con un límite de 8 bytes (ver -malign-double) o sufrir penalizaciones significativas en el rendimiento del tiempo de ejecución. En Pentium III, el tipo de datos __m128 de Streaming SIMD Extension (SSE) puede no funcionar correctamente si no está alineado en 16 bytes.
Para garantizar la alineación adecuada de estos valores en la pila, el límite de la pila debe estar tan alineado como lo requiere 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 más alto desde una función compilada con un límite de pila preferido más bajo probablemente desalineará la pila. Se recomienda que las bibliotecas que utilizan devoluciones de llamada utilicen siempre la configuración predeterminada.
Esta alineación adicional consume espacio de pila adicional 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 núcleos de los sistemas operativos, puede querer reducir la alineación preferida a -mpreferred-stack-boundary=2.
El lea
carga el puntero de pila original (desde antes de la llamada a main
) en ecx
, ya que el puntero de la pila está a punto de modificarse. Esto se utiliza para dos propósitos:
- para acceder a los argumentos del
main
función, ya que son relativas al puntero de pila original - para restaurar el puntero de la pila a su valor original al regresar de
main
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 funcionara 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 solo se superpone a dos líneas de caché. Ahora, para cargar ese pequeño wchar en el caché, se deben desalojar dos líneas de caché completas, ¿y si las necesita en el mismo ciclo? El caché es tan tremendamente más rápido que la RAM que el rendimiento del caché siempre es fundamental.
Además, generalmente hay una penalización de velocidad para cambiar 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 la pila en busca de parámetros y retorno.
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 fotogramas que no parecen estrictamente necesarias para ejecutar el programa pueden ser importantes para configurar una cadena de fotogramas lista para rastrear.