GNU/Linux >> Tutoriels Linux >  >> Linux

Essayer de comprendre l'alignement de pile compliqué de gcc en haut de main qui copie l'adresse de retour

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 :

  1. pour accéder aux arguments du main fonction, car ils sont relatifs au pointeur de pile d'origine
  2. 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.


Linux
  1. Le noyau Linux :Top 5 des innovations

  2. Les 20 meilleurs guides et tutoriels d'administration système

  3. Trouver l'ordinateur sur un réseau LAN ?

  4. Qu'est-ce que l'utilisateur debian-+ ?

  5. Essayer de comprendre la bonne façon de créer une route statique dans CentOS, veuillez aider

VA Linux :la société Linux qui dirigeait autrefois le NASDAQ

Comment trouver l'adresse IP d'une machine virtuelle KVM

Comment personnaliser la commande Linux top

Une manière simple de comprendre la commande IOStat

Comment trouver l'adresse IP partagée principale de votre serveur dans cPanel

Les 20 meilleurs jeux Steam pour Linux auxquels vous ne pouvez pas résister