J'ai essayé :
;# 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
Ceci est fait pour maintenir la pile alignée sur une limite de 16 octets. Certaines instructions exigent que certains types de données soient alignés sur une limite de 16 octets maximum. Afin de répondre à cette exigence, GCC s'assure que la pile est initialement alignée sur 16 octets et alloue l'espace de la pile par multiples de 16 octets. Cela peut être contrôlé à l'aide de l'option -mpreferred-stack-boundary=num . Si vous utilisez -mpreferred-stack-boundary=2 (pour un alignement 2=4 octets), ce code d'alignement ne sera pas généré car la pile est toujours alignée sur au moins 4 octets. Cependant, vous pourriez alors avoir des problèmes si votre programme utilise des types de données nécessitant un alignement plus fort.
D'après le manuel de gcc :
Sur Pentium et PentiumPro, les valeurs doubles et doubles longues doivent être alignées sur une limite de 8 octets (voir -malign-double) ou subir des pénalités significatives en termes de performances d'exécution. Sur Pentium III, le type de données Streaming SIMD Extension (SSE) __m128 peut ne pas fonctionner correctement s'il n'est pas aligné sur 16 octets.
Pour garantir un alignement correct de ces valeurs sur la pile, la limite de la pile doit être aussi alignée que celle requise par toute valeur stockée sur la pile. De plus, chaque fonction doit être générée de manière à maintenir la pile alignée. Ainsi, appeler une fonction compilée avec une limite de pile préférée plus élevée à partir d'une fonction compilée avec une limite de pile préférée inférieure désalignera très probablement la pile. Il est recommandé aux bibliothèques qui utilisent des rappels d'utiliser toujours le paramètre par défaut.
Cet alignement supplémentaire consomme de l'espace de pile supplémentaire et augmente généralement la taille du code. Le code sensible à l'utilisation de l'espace de pile, comme les systèmes embarqués et les noyaux de système d'exploitation, peut vouloir réduire l'alignement préféré à -mpreferred-stack-boundary=2.
Le lea
charge le pointeur de pile d'origine (avant l'appel à main
) en ecx
, puisque le pointeur de pile est sur le point d'être modifié. Ceci est utilisé à deux fins :
- pour accéder aux arguments du
main
fonction, car ils sont relatifs au pointeur de pile d'origine - pour restaurer le pointeur de pile à sa valeur d'origine lors du retour 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 ??
Même si chaque instruction fonctionnait parfaitement sans pénalité de vitesse malgré des opérandes arbitrairement alignés, l'alignement augmenterait encore les performances. Imaginez une boucle faisant référence à une quantité de 16 octets qui chevauche juste deux lignes de cache. Maintenant, pour charger ce petit wchar dans le cache, deux lignes de cache entières doivent être expulsées, et que se passe-t-il si vous en avez besoin dans la même boucle ? Le cache est tellement plus rapide que la RAM que les performances du cache sont toujours essentielles.
De plus, il y a généralement une pénalité de vitesse pour déplacer les opérandes mal alignés dans les registres. Étant donné que la pile est en cours de réalignement, nous devons naturellement enregistrer l'ancien alignement afin de parcourir les cadres de pile pour les paramètres et le retour.
ecx est un registre temporaire, il doit donc être enregistré. De plus, selon le niveau d'optimisation, certaines des opérations de liaison de trames qui ne semblent pas strictement nécessaires pour exécuter le programme pourraient bien être importantes pour mettre en place une chaîne de trames prête à tracer.