GNU/Linux >> Tutoriales Linux >  >> Linux

Tratando de entender la complicada alineación de pila de gcc en la parte superior de main que copia la dirección de retorno

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:

  1. para acceder a los argumentos del main función, ya que son relativas al puntero de pila original
  2. 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.


Linux
  1. El kernel de Linux:las 5 principales innovaciones

  2. Encuentre la geolocalización de una dirección IP desde la línea de comandos

  3. ¿Encuentra la computadora en una red Lan?

  4. ¿Cómo encuentro el tamaño máximo de pila?

  5. ¿Qué es el usuario debian-+?

VA Linux:la compañía de Linux que una vez gobernó NASDAQ

Cómo encontrar la dirección IP de una máquina virtual KVM

Cómo personalizar el comando superior de Linux

Una forma sencilla de entender el comando IOStat

Cómo encontrar la dirección IP principal compartida de su servidor en cPanel

Los 20 juegos de Steam mejor valorados para Linux que no podrás resistirte a jugar