web-dev-qa-db-fra.com

Essayer de comprendre l'option gcc -fomit-frame-pointer

J'ai demandé à Google de me donner la signification de l'option gcc-fomit-frame-pointer, Qui me redirige vers l'instruction ci-dessous.

- fomit-frame-pointer

Ne gardez pas le pointeur d'image dans un registre pour les fonctions qui n'en ont pas besoin. Cela évite les instructions pour enregistrer, configurer et restaurer les pointeurs de trame; il rend également un registre supplémentaire disponible dans de nombreuses fonctions. Cela rend également le débogage impossible sur certaines machines.

Selon mes connaissances sur chaque fonction, un enregistrement d'activation sera créé dans la pile de la mémoire de processus pour conserver toutes les variables locales et certaines informations supplémentaires. J'espère que ce pointeur de cadre signifie l'adresse de l'enregistrement d'activation d'une fonction.

Dans ce cas, quels sont les types de fonctions pour lesquelles il n’est pas nécessaire de garder le pointeur de cadre dans un registre? Si j'obtiens cette information, je vais essayer de concevoir la nouvelle fonction en fonction de celle-ci (si possible), car si le pointeur de la trame n'est pas conservé dans les registres, certaines instructions seront omises en binaire. Cela améliorera considérablement les performances dans une application comportant de nombreuses fonctions.

73
rashok

La plupart des petites fonctions n’ont pas besoin de pointeur d’image - les grandes fonctions PEUVENT en avoir besoin.

Il s’agit vraiment de la manière dont le compilateur parvient à suivre l’utilisation de la pile et de l’emplacement de la pile (variables locales, arguments transmis à la fonction actuelle et arguments en cours de préparation pour une fonction sur le point d’être appelée). Je ne pense pas qu'il soit facile de caractériser les fonctions qui ont besoin ou non d'un pointeur d'image (techniquement, AUCUNE fonction NE DOIT avoir un pointeur d'image - c'est plutôt un cas de "si le compilateur juge nécessaire de réduire la complexité de autre code ").

Je ne pense pas que vous devriez "essayer de faire en sorte que les fonctions ne comportent pas de pointeur de cadre" dans le cadre de votre stratégie de codage - comme je l'ai dit, les fonctions simples n'en ont pas besoin, utilisez donc -fomit-frame-pointer, et vous obtiendrez un registre supplémentaire disponible pour l’allocateur de registres et sauvegarderez 1 à 3 instructions d’entrée/de sortie pour les fonctions. Si votre fonction a besoin d'un pointeur d'image, c'est parce que le compilateur décide que c'est une meilleure option que de ne pas utiliser de pointeur d'image. Ce n'est pas un objectif d'avoir des fonctions sans pointeur d'image, c'est un objectif d'avoir un code qui fonctionne correctement et rapidement.

Notez que "ne pas avoir de pointeur de trame" devrait donner de meilleures performances, mais ce n’est pas une solution miracle qui donne d’énormes améliorations - en particulier pas sur x86-64, qui a déjà 16 registres pour commencer. Sur x86 32 bits, puisqu'il ne possède que 8 registres, dont l'un est le pointeur de pile, et en prenant un autre comme pointeur d'image, cela signifie que 25% de l'espace de registre est pris. Changer cela à 12,5% est une amélioration. Bien sûr, compiler pour 64 bits aidera beaucoup aussi.

52
Mats Petersson

Il s’agit du registre BP/EBP/RBP sur les plates-formes Intel. Ce registre utilise par défaut le segment de pile (un préfixe spécial n’est pas nécessaire pour accéder au segment de pile).

L'EBP est le meilleur choix de registre pour accéder aux structures de données, aux variables et à l'espace de travail alloué de manière dynamique dans la pile. EBP est souvent utilisé pour accéder aux éléments de la pile par rapport à un point fixe de la pile plutôt que par rapport au TOS actuel. Il identifie généralement l'adresse de base du cadre de pile actuel établi pour la procédure en cours. Lorsque EBP est utilisé comme registre de base dans un calcul de décalage, le décalage est calculé automatiquement dans le segment de pile actuel (c'est-à-dire, le segment actuellement sélectionné par SS). Comme il n'est pas nécessaire de spécifier explicitement SS, le codage des instructions est alors plus efficace. EBP peut également être utilisé pour indexer en segments adressables via d’autres registres de segments.

(source - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )

Étant donné que sur la plupart des plates-formes 32 bits, le segment de données et le segment de pile sont identiques, cette association d'EBP/RBP avec la pile n'est plus un problème. Il en va de même pour les plates-formes 64 bits: l'architecture x86-64, introduite par AMD en 2003, a largement abandonné la prise en charge de la segmentation en mode 64 bits: quatre des registres de segments: CS, SS, DS et ES sont forcés à 0 Ces circonstances des plates-formes x86 32 bits et 64 bits signifient essentiellement que le registre EBP/RBP peut être utilisé, sans préfixe, dans les instructions du processeur qui accèdent à la mémoire.

L’option de compilateur que vous avez décrite permet donc à BP/EBP/RBP d’être utilisé à d’autres fins, par exemple. tenir une variable locale.

Par "Cela évite les instructions pour enregistrer, configurer et restaurer les pointeurs de trame", on entend éviter le code suivant lors de la saisie de chaque fonction:

Push ebp
mov ebp, esp

ou l'instruction enter qui était très utile sur les processeurs Intel 80286 et 80386.

De plus, avant le retour de la fonction, le code suivant est utilisé:

mov esp, ebp
pop ebp 

ou l'instruction leave.

Les outils de débogage peuvent analyser les données de la pile et utiliser ces données de registre EBP poussées lors de la localisation de call sites, c’est-à-dire d’afficher les noms de la fonction et les arguments dans l’ordre dans lequel ils ont été appelés hiérarchiquement.

Les programmeurs peuvent se poser des questions sur les cadres de pile non pas au sens large (qu’il s’agit d’une seule entité dans la pile servant uniquement un appel de fonction et conservant une adresse de retour, des arguments et des variables locales), mais dans un sens étroit - lorsque le terme stack frames est mentionné dans le contexte des options du compilateur. Du point de vue du compilateur, un cadre de pile est simplement l’entrée et le code de sortie de la routine , qui place une ancre dans la pile - qui peut également être utilisé pour le débogage et la gestion des exceptions. Les outils de débogage peuvent analyser les données de la pile et utiliser ces ancres pour effectuer un retour arrière, tout en localisant call sites dans la pile, c’est-à-dire pour afficher les noms de la fonction dans l’ordre dans lequel ils ont été appelés hiérarchiquement.

C’est pourquoi il est très important que le programmeur comprenne ce qu’est un cadre de pile en termes d’options du compilateur - car le compilateur peut décider de générer ou non ce code.

Dans certains cas, le compilateur peut omettre le cadre de pile (code d’entrée et de sortie de la routine) et accéder directement aux variables via le pointeur de pile (SP/ESP/RSP) plutôt que le pointeur de base pratique (BP/ESP/RSP). Les conditions pour qu'un compilateur omette les cadres de pile pour certaines fonctions peuvent être différentes, par exemple: (1) la fonction est une fonction feuille (c'est-à-dire une entité finale qui n'appelle pas d'autres fonctions); (2) aucune exception n'est utilisée; (3) aucune routine n'est appelée avec des paramètres sortants sur la pile; (4) la fonction n'a pas de paramètre.

L'omission des cadres de pile (code d'entrée et de sortie de la routine) peut rendre le code plus petit et plus rapide, mais peut également affecter négativement la capacité des débogueurs à retracer les données dans la pile et à les afficher au programmeur. Ce sont les options du compilateur qui déterminent les conditions auxquelles une fonction doit satisfaire pour que le compilateur lui attribue l'entrée et le code de sortie du cadre de pile. Par exemple, un compilateur peut disposer d'options permettant d'ajouter ce code d'entrée et de sortie aux fonctions dans les cas suivants: (a) toujours, (b) jamais, (c) si nécessaire (en spécifiant les conditions).

Revenir des généralités aux particularités: si vous utiliserez le -fomit-frame-pointer Option du compilateur GCC, vous pouvez gagner à la fois sur le code d’entrée et de sortie de la routine et sur un registre supplémentaire (à moins qu’il soit déjà activé par défaut, lui-même ou implicitement par d’autres options, dans ce cas, vous bénéficiez déjà du gain de l’utilisation du registre EBP/RBP et aucun gain supplémentaire ne sera obtenu en spécifiant explicitement cette option si elle est déjà implicitement). Notez cependant que, dans les modes 16 bits et 32 ​​bits, le registre du partenaire principal n’a pas la possibilité d’accéder à ses parties 8 bits comme le fait AX (AL et AH).

Depuis cette option, en plus de permettre au compilateur d'utiliser EBP comme registre général dans les optimisations, empêche également la génération de code de sortie et d'entrée pour le cadre de pile, ce qui complique le débogage - c'est pourquoi la documentation GCC explicitement indique (en insistant de manière inhabituelle avec un style gras) que cette option est activée. rend le débogage impossible sur certaines machines

Sachez également que d’autres options du compilateur, liées au débogage ou à l’optimisation, peuvent implicitement transformer le -fomit-frame-pointer option ON ou OFF.

Je n'ai trouvé aucune information officielle sur gcc.gnu.org sur la manière dont les autres options affectent -fomit-frame-pointer _ sur les plates-formes x86 , le https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize -Options.html n'indique que ce qui suit:

-O active également le pointeur -fomit-frame-sur les machines où cela n’interfère pas avec le débogage.

Il n’est donc pas clair dans la documentation en soi si -fomit-frame-pointer sera activé si vous compilez avec un seul -O option sur la plate-forme x86. Cela peut être testé de manière empirique, mais dans ce cas, les développeurs de GCC ne s'engagent pas à ne pas modifier le comportement de cette option à l'avenir sans préavis.

Cependant, Peter Cordes a souligné dans les commentaires qu'il existe une différence entre les paramètres par défaut de la -fomit-frame-pointer entre les plates-formes x86-16 et les plates-formes x86-32/64.

Cette option -- -fomit-frame-pointer - est aussi concerne le compilateur Intel C++ 15. , pas seulement le GCC:

Pour le compilateur Intel, cette option a un alias /Oy.

Voici ce que Intel a écrit à ce sujet:

Ces options déterminent si EBP est utilisé comme registre à usage général dans les optimisations. Les options -fomit-frame-pointer et/Oy permettent cette utilisation. Les options -fno-omit-frame-pointer et/Oy- l'interdisent.

Certains débogueurs s'attendent à ce que EBP soit utilisé en tant que pointeur d'image de pile et ne peuvent pas générer de trace de pile à moins que ce ne soit le cas. Les options -fno-omit-frame-pointer et/Oy- indiquent au compilateur de générer du code qui maintient et utilise EBP en tant que pointeur de frame de pile pour toutes les fonctions, de sorte qu'un débogueur puisse toujours produire une trace arrière de pile sans effectuer les opérations suivantes:

Pour -fno-omit-frame-pointer: désactiver les optimisations avec -O0 pour/Oy-: désactiver les optimisations/O1,/O2 ou/O3 L'option -fno-omit-frame-pointer est définie lorsque vous spécifiez l'option - O0 ou l'option -g. L'option -fomit-frame-pointer est définie lorsque vous spécifiez l'option -O1, -O2 ou -O3.

L'option/Oy est définie lorsque vous spécifiez l'option/O1,/O2 ou/O3. Option/Oy - est défini lorsque vous spécifiez l'option/Od.

L'utilisation de l'option -fno-omit-frame-pointer ou/Oy- réduit de 1 le nombre de registres à usage général disponibles et peut entraîner un code légèrement moins efficace.

REMARQUE Pour les systèmes Linux *: Il existe actuellement un problème avec la gestion des exceptions GCC 3.2. Par conséquent, le compilateur Intel ignore cette option lorsque GCC 3.2 est installé pour C++ et que la gestion des exceptions est activée (par défaut).

Veuillez noter que la citation ci-dessus ne concerne que le compilateur Intel C++ 15, pas GCC.

10
Maxim Masiutin