À quoi sert le registre de pointeur de trame EBP?

95

Je suis un débutant en langage d'assemblage et j'ai remarqué que le code x86 émis par les compilateurs garde généralement le pointeur de cadre, même en mode version / optimisé, lorsqu'il pourrait utiliser le EBPregistre pour autre chose.

Je comprends pourquoi le pointeur de trame peut faciliter le débogage du code et peut être nécessaire s'il alloca()est appelé dans une fonction. Cependant, x86 a très peu de registres et l'utilisation de deux d'entre eux pour contenir l'emplacement du cadre de la pile alors qu'il en suffirait un n'a tout simplement pas de sens pour moi. Pourquoi l'omission du pointeur d'image est-elle considérée comme une mauvaise idée, même dans les versions optimisées / de version?

dsimcha
la source
19
Si vous pensez que x86 a très peu de registres, vous devriez vérifier 6502 :)
Sedat Kapanoglu
1
C99 VLA peut également en bénéficier.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
2
stackoverflow.com/questions/1395591/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Le pointeur de cadre ne rend-il pas le pointeur de pile redondant? . TL; DR: 1. alignement de pile non trivial 2. allocation de pile ( alloca) 3. facilité de mise en œuvre à l'exécution: gestion des exceptions, bac à sable, GC
Alexander Malakhov

Réponses:

102

Le pointeur de trame est un pointeur de référence permettant à un débogueur de savoir où se trouve une variable locale ou un argument avec un seul décalage constant. Bien que la valeur d'ESP change au cours de l'exécution, EBP reste la même, ce qui permet d'atteindre la même variable au même décalage (par exemple, le premier paramètre sera toujours à EBP + 8 tandis que les décalages ESP peuvent changer de manière significative puisque vous pousserez / faire éclater des choses)

Pourquoi les compilateurs ne jettent-ils pas le pointeur de cadre? Parce qu'avec le pointeur de trame, le débogueur peut déterminer où les variables locales et les arguments utilisent la table de symboles car ils sont garantis à un décalage constant par rapport à EBP. Sinon, il n'y a pas de moyen facile de déterminer où se trouve une variable locale à un moment quelconque du code.

Comme Greg l'a mentionné, cela facilite également le déroulement de la pile pour un débogueur car EBP fournit une liste liée inversée de cadres de pile permettant ainsi au débogueur de déterminer la taille de la trame de pile (variables locales + arguments) de la fonction.

La plupart des compilateurs offrent une option pour omettre les pointeurs de trame bien que cela rende le débogage vraiment difficile. Cette option ne doit jamais être utilisée globalement, même dans le code de version. Vous ne savez pas quand vous devrez déboguer le plantage d'un utilisateur.

Sedat Kapanoglu
la source
10
Le compilateur sait probablement ce qu'il fait à ESP. Les autres points sont cependant valides, +1
erikkallen
8
Les débogueurs modernes peuvent effectuer des backtraces de pile même dans le code compilé avec -fomit-frame-pointer. Ce paramètre est le paramètre par défaut dans gcc récent.
Peter Cordes
2
@SedatKapanoglu: Une section de données enregistre les informations nécessaires: yosefk.com/blog
Peter Cordes
3
@SedatKapanoglu: la .eh_frame_hdrsection est également utilisée pour les exceptions d'exécution. Vous le trouverez (avec objdump -h) dans la plupart des binaires sur un système Linux, c'est environ 16k pour /bin/bash, contre 572B pour GNU /bin/true, 108k pour ffmpeg. Il existe une option gcc pour désactiver sa génération, mais c'est une section de données "normale", pas une section de débogage qui stripsupprime par défaut. Sinon, vous ne pouvez pas revenir en arrière via une fonction de bibliothèque qui n'a pas de symboles de débogage. Cette section peut être plus grande que les push/mov/popinstructions qu'elle remplace, mais son coût d'exécution est presque nul (par exemple, le cache uop).
Peter Cordes
3
Concernant "tel que le premier paramètre sera toujours à EBP-4": N'est-ce pas le premier paramètre sur EBP + 8 (sur x86)?
Aydin K.
31

Juste ajouter mes deux cents à des réponses déjà bonnes.

Cela fait partie d'une bonne architecture de langage d'avoir une chaîne de cadres de pile. Le BP pointe vers la trame actuelle, où les variables locales du sous-programme sont stockées. (Les sections locales sont à des décalages négatifs et les arguments à des décalages positifs.)

L'idée que cela empêche l'utilisation d'un registre parfaitement bon dans l'optimisation pose la question: quand et où l'optimisation vaut-elle réellement la peine?

L'optimisation n'est valable que dans les boucles serrées qui 1) n'appellent pas de fonctions, 2) où le compteur de programme passe une fraction significative de son temps, et 3) dans le code que le compilateur verra réellement (c'est-à-dire les fonctions non-bibliothèque). Il s'agit généralement d'une très petite fraction du code global, en particulier dans les grands systèmes.

Un autre code peut être tordu et pressé pour se débarrasser des cycles, et cela n'a tout simplement pas d'importance, car le compteur de programme n'est pratiquement jamais là.

Je sais que vous ne l'avez pas demandé, mais d'après mon expérience, 99% des problèmes de performances n'ont rien à voir avec l'optimisation du compilateur. Ils ont tout à voir avec la sur-conception.

Mike Dunlavey
la source
Merci @Mike, j'ai trouvé votre réponse très utile.
sixtyfootersdude
2
L'élimination du pointeur de cadre vous permet également d'économiser quelques instructions à chaque appel de fonction, ce qui est une petite optimisation en soi. BTW, votre utilisation de "pose la question" est incorrecte; vous voulez dire "soulève la question".
augurar
@augurar: Corrigé. Merci. Je suis un peu grognon de grammaire moi-même :)
Mike Dunlavey
3
@augurar Le langage évolue: "Demande la question" signifie maintenant simplement "soulève la question". Être un pinailleur prescriptiviste pour un usage dépassé n'ajoute rien.
user3364825
9

Cela dépend du compilateur, certainement. J'ai vu du code optimisé émis par des compilateurs x86 qui utilisent librement le registre EBP comme registre à usage général. (Je ne me souviens pas avec quel compilateur j'ai remarqué cela.)

Les compilateurs peuvent également choisir de maintenir le registre EBP pour aider au déroulement de la pile pendant la gestion des exceptions, mais là encore, cela dépend de l'implémentation précise du compilateur.

Greg Hewgill
la source
La plupart des compilateurs utilisent par défaut -fomit-frame-pointerlorsque l'optimisation est activée. (lorsque l'ABI le permet). GCC, clang, ICC et MSVC le font tous, IIRC, même en ciblant Windows 32 bits. Oui, ma réponse sur Pourquoi est-il préférable d'utiliser ebp que le registre esp pour localiser les paramètres sur la pile? montre que même Windows 32 bits peut omettre le pointeur de cadre. Linux x86 32 bits le peut et le fait. Et bien sûr, les ABI 64 bits ont permis l'omission du pointeur de trame dès le début.
Peter Cordes
4

Cependant, x86 a très peu de registres

Ceci n'est vrai que dans le sens où les opcodes ne peuvent adresser que 8 registres. Le processeur lui-même aura en fait beaucoup plus de registres que cela et utilisera le renommage de registre, le pipelining, l'exécution spéculative et d'autres mots à la mode du processeur pour contourner cette limite. Wikipedia a un bon paragraphe d'introduction sur ce qu'un processeur x86 peut faire pour surmonter la limite de registre: http://en.wikipedia.org/wiki/X86#Current_implementations .

MSN
la source
1
La question originale concerne le code généré, qui est strictement limité aux registres référençables par opcodes.
Darron
1
Oui, mais c'est pourquoi la suppression du pointeur de cadre dans les versions optimisées n'est pas aussi importante de nos jours.
Michael
1
Le changement de nom de registre n'est pas tout à fait la même chose que d'avoir en fait un plus grand nombre de registres disponibles. Il y a encore beaucoup de situations où le changement de nom de registre n'aidera pas, mais des registres plus «réguliers» le feraient.
jalf
1

L'utilisation de cadres de pile est devenue incroyablement bon marché dans n'importe quel matériel, même à distance moderne. Si vous avez des cadres de pile bon marché, la sauvegarde de quelques registres n'est pas aussi importante. Je suis sûr que les cadres de pile rapides par rapport à plus de registres étaient un compromis d'ingénierie, et les cadres de pile rapides ont gagné.

Combien économisez-vous sur le registre pur? Est-ce que ça vaut le coup?

dwc
la source
Un plus grand nombre de registres est limité par le codage des instructions. x86-64 utilise des bits dans l'octet de préfixe REX pour étendre la partie des instructions spécifiant le registre de 3 à 4 bits pour les registres src et dest. S'il y avait de la place, x86-64 serait probablement passé à 32 registres architecturaux, bien que sauvegarder / restaurer autant de commutateurs de contexte commence à s'additionner. 15 est un énorme pas en avant par rapport à 7, mais 31 est une amélioration beaucoup moins importante dans la plupart des cas. (sans compter le pointeur de pile comme un usage général.) Faire push / pop rapide est idéal pour plus que juste des cadres de pile. Ce n'est pas un compromis avec le nombre de règles, cependant.
Peter Cordes