web-dev-qa-db-fra.com

Quel est le but du registre RBP dans l'assembleur x86_64?

J'essaie donc d'apprendre un peu l'assemblage, car j'en ai besoin pour le cours d'architecture informatique. J'ai écrit quelques programmes, comme l'impression de la séquence de Fibonacci.

J'ai reconnu que chaque fois que j'écris une fonction, j'utilise ces 3 lignes (comme je l'ai appris en comparant le code Assembly généré à partir de gcc à son équivalent C:)

pushq   %rbp
movq    %rsp, %rbp
subq    $16, %rsp

J'ai 2 questions à ce sujet:

  1. Tout d’abord, pourquoi dois-je utiliser %rbp? N'est-il pas plus simple d'utiliser %rsp, lorsque son contenu est déplacé vers %rbp sur la 2e ligne?
  2. Pourquoi dois-je soustraire quoi que ce soit de %rsp? Je veux dire que ce n'est pas toujours 16, quand j’étais printf comme 7 ou 8 variables, je soustrais donc 24 ou 28.

J'utilise Manjaro 64 bits sur une machine virtuelle (4 Go de RAM), processeur Intel 64 bits

42
user6827707

rbp est le pointeur du cadre sur x86_64. Dans votre code généré, il obtient un instantané du pointeur de la pile (rsp). Ainsi, lorsque des ajustements sont apportés à rsp (c’est-à-dire réservant de l’espace pour des variables locales ou des valeurs Pushing sur à la pile), les variables locales et les paramètres de fonction sont toujours accessibles à partir d’un décalage constant de rbp.

De nombreux compilateurs proposent l’omission du pointeur de trame comme option d’optimisation; cela créera les variables d'accès au code d'assemblage générées relatives à rsp à la place et libérera rbp en tant qu'un autre registre à usage général à utiliser dans les fonctions.

Dans le cas de GCC, que je suppose que vous utilisez à partir de la syntaxe de l'assembleur AT & T, ce commutateur est -fomit-frame-pointer. Essayez de compiler votre code avec ce commutateur et voyez quel code d'assemblage vous obtenez. Vous remarquerez probablement que lors de l'accès à des valeurs relatives à rsp au lieu de rbp, le décalage par rapport au pointeur varie dans l'ensemble de la fonction.

37
Govind Parmar

Linux utilise l'architecture System V ABI pour x86-64 (AMD64); voir System V ABI sur OSDev Wiki pour plus de détails.

Cela signifie que la pile se développe ; les petites adresses sont "plus haut" dans la pile. Les fonctions C typiques sont compilées pour

        pushq   %rbp        ; Save address of previous stack frame
        movq    %rsp, %rbp  ; Address of current stack frame
        subq    $16, %rsp   ; Reserve 16 bytes for local variables

        ; ... function ...

        movq    %rbp, %rsp  ; \ equivalent to the
        popq    %rbp        ; / 'leave' instruction
        ret

La quantité de mémoire réservée aux variables locales est toujours un multiple de 16 octets, afin que la pile reste alignée sur 16 octets. Si aucun espace de pile n'est requis pour les variables locales, il n'y a pas de subq $16, %rsp ou instruction similaire.

(Notez que l'adresse de retour et le précédent %rbp poussés vers la pile ont une taille de 8 octets, soit 16 octets au total.)

Tandis que %rbp pointe sur le cadre de pile actuel, %rsp pointe vers le haut de la pile. Parce que le compilateur connaît la différence entre %rbp et %rsp _ à n'importe quel moment de la fonction, il est libre d'utiliser l'une ou l'autre comme base pour les variables locales.

Un cadre de pile est simplement le terrain de jeu de la fonction locale: la région de pile utilisée par la fonction actuelle.

Les versions actuelles de GCC désactivent le cadre de la pile chaque fois que des optimisations sont utilisées. Cela a du sens, car pour les programmes écrits en C, les cadres de pile sont les plus utiles pour le débogage, mais pas grand-chose. (Vous pouvez utiliser, par exemple, -O2 -fno-omit-frame-pointer pour conserver les images de la pile tout en permettant les optimisations sinon.)

Bien que la même ABI s'applique à tous les fichiers binaires, quelle que soit la langue dans laquelle ils sont écrits, certaines autres langues ont besoin de cadres de pile pour être "déroulés" (par exemple, pour "lever des exceptions" à un appelant ancêtre de la fonction actuelle); c.-à-d. pour "dérouler" des trames de pile qu'une ou plusieurs fonctions peuvent être abandonnées et contrôlées par une fonction ancêtre, sans laisser de trucs inutiles sur la pile.

Lorsque les cadres de pile sont omis - -fomit-frame-pointer pour GCC -, l’implémentation de la fonction change essentiellement en

        subq    $8, %rsp    ; Re-align stack frame, and
                            ; reserve memory for local variables

        ; ... function ...

        addq    $8, %rsp
        ret

Parce qu'il n'y a pas de frame de pile (%rbp est utilisé à d’autres fins, et sa valeur n’est jamais transmise à la pile), chaque appel de fonction pousse uniquement l’adresse de retour dans la pile, ce qui correspond à une quantité de 8 octets; nous devons donc soustraire 8 de %rsp pour le conserver comme multiple de 16. (En général, la valeur soustraite et ajoutée à %rsp est un multiple impair de 8.)

Les paramètres de fonction sont généralement passés dans des registres. Voir le lien ABI au début de cette réponse pour plus de détails, mais en bref, les types et pointeurs intégraux sont passés dans les registres %rdi, %rsi, %rdx, %rcx, %r8, et %r9, avec des arguments en virgule flottante dans le %xmm0 à %xmm7 registres.

Dans certains cas, vous verrez rep ret au lieu de rep. Ne soyez pas confus: le rep ret signifie exactement la même chose que ret; le préfixe rep, bien que normalement utilisé avec des instructions de chaîne (instructions répétées), ne fait rien lorsqu'il est appliqué à l'instruction ret. C'est simplement que certains prédicteurs de branche de processeurs AMD n'aiment pas sauter à une instruction ret, et la solution de contournement recommandée consiste à utiliser un rep ret il y a la place.

Enfin, j'ai omis le zone rouge au-dessus du sommet de la pile (les 128 octets aux adresses inférieures à %rsp). En effet, cela n’est pas vraiment utile pour les fonctions classiques: dans le cas normal d’une pile have-frame-frame, vous voulez que vos données locales se trouvent dans la pile afin de permettre le débogage. Dans le cas omit-stack-frame, les exigences d’alignement de la pile signifient déjà que nous devons soustraire 8 de %rsp, donc inclure la mémoire nécessaire aux variables locales dans cette soustraction ne coûte rien.

35
Nominal Animal