J'ai les doutes suivants :

Comme nous le savons, System V x86-64 ABI nous donne une zone de taille fixe (128 octets) dans le cadre de la pile, appelée zone rouge. Par conséquent, nous n'avons pas besoin d'utiliser, par exemple, sub rsp, 12. Faites simplement mov [rsp-12], X et c'est tout.

Mais je n'arrive pas à en saisir l'idée. Pourquoi est-ce important? Est-il nécessaire de sub rsp, 12 sans zone rouge ? Après tout, la taille de la pile est limitée au début, alors pourquoi sub rsp, 12 est-il important ? Je sais que cela nous permet de suivre le haut de la pile mais ignorons-le à ce moment-là.

Je sais ce que certaines instructions utilisent la valeur rsp (comme ret) mais je ne m'en soucie pas à ce moment-là.

Le nœud du problème est le suivant : nous n'avons pas de zone rouge et nous avons :

function:
    mov [rsp-16], rcx
    mov [rsp-32], rcx
    mov [rsp-128], rcx
    mov [rsp-1024], rcx
    ret

Est-ce différent avec ?

function:
    sub rsp, 1024
    mov [rsp-16], rcx
    mov [rsp-32], rcx
    mov [rsp-128], rcx
    mov [rsp-1024], rcx
    add rsp, 1024
    ret
7
Gilgamesz 21 juin 2016 à 13:20

2 réponses

Meilleure réponse

La "zone rouge" n'est pas strictement nécessaire. Dans vos termes, cela pourrait être considéré comme « inutile ». Tout ce que vous pouviez faire en utilisant la zone rouge, vous pouviez également le faire de la manière traditionnelle en ciblant l'ABI IA-32.

Voici ce que l'AMD64 ABI dit à propos de la "zone rouge" :

La zone de 128 octets au-delà de l'emplacement pointé par %rsp est considérée comme réservée et ne doit pas être modifiée par les gestionnaires de signaux ou d'interruption. Par conséquent, les fonctions peuvent utiliser cette zone pour les données temporaires qui ne sont pas nécessaires lors des appels de fonction. En particulier, les fonctions feuilles peuvent utiliser cette zone pour l'intégralité de leur cadre de pile, plutôt que d'ajuster le pointeur de pile dans le prologue et l'épilogue. Cette zone est connue sous le nom de zone rouge.

Le véritable objectif de la zone rouge est une optimisation. Son existence permet au code de supposer que les 128 octets en dessous de rsp ne seront pas écrasés de manière asynchrone par des signaux ou des gestionnaires d'interruption, ce qui permet de l'utiliser comme espace de travail. Cela rend inutile de créer explicitement un espace de travail sur la pile en déplaçant le pointeur de pile dans rsp. Il s'agit d'une optimisation car les instructions pour décrémenter et restaurer rsp peuvent désormais être élidés, économisant du temps et de l'espace.

Alors oui, alors que vous pouviez le faire avec AMD64 (et devriez le faire avec IA-32) :

function:
    push rbp                      ; standard "prologue" to save the
    mov  rbp, rsp                 ;   original value of rsp

    sub  rsp, 32                  ; reserve scratch area on stack
    mov  QWORD PTR [rsp],   rcx   ; copy rcx into our scratch area
    mov  QWORD PTR [rsp+8], rdx   ; copy rdx into our scratch area

    ; ...do something that clobbers rcx and rdx...

    mov  rcx, [rsp]               ; retrieve original value of rcx from our scratch area
    mov  rdx, [rsp+8]             ; retrieve original value of rdx from our scratch area
    add  rsp, 32                  ; give back the stack space we used as scratch area

    pop  rbp                      ; standard "epilogue" to restore rsp
    ret

Nous n'avons pas besoin de le faire dans les cas où nous n'avons besoin que d'une zone de travail de 128 octets (ou moins), car nous pouvons alors utiliser la zone rouge comme zone de travail.

De plus, comme nous n'avons plus à décrémenter le pointeur de pile, nous pouvons utiliser rsp comme pointeur de base (au lieu de rbp), ce qui rend inutile la sauvegarde et la restauration de rbp (dans le prologue et épilogue), et libérant également rbp pour une utilisation comme un autre registre à usage général !

(Techniquement, activer l'omission du pointeur de trame (-fomit-frame-pointer, activé par défaut avec -O1 puisque l'ABI le permet) permettrait également au compilateur d'éliminer les sections prologue et épilogue, avec le même Cependant, en l'absence d'une zone rouge, la nécessité d'ajuster le pointeur de pile pour réserver de l'espace ne changerait pas.)

Notez, cependant, que l'ABI garantit uniquement que les choses asynchrones comme les signaux et les gestionnaires d'interruption ne modifient pas la zone rouge. Les appels à d'autres fonctions peuvent écraser les valeurs dans la zone rouge, ce n'est donc pas particulièrement utile, sauf dans les fonctions feuilles (qui n'appellent aucune autre fonction, comme si elles étaient à la "feuille" d'un arbre d'appel de fonction) .


Un dernier point : l'Windows x64 ABI s'écarte légèrement de l'ABI AMD64 utilisé sur d'autres systèmes d'exploitation. En particulier, il n'a pas de notion de "zone rouge". La zone au-delà de rsp est considérée comme volatile et susceptible d'être écrasée à tout moment. Au lieu de cela, il exige que l'appelant alloue un espace d'adressage personnel sur la pile , qui est ensuite disponible pour l'usage de l'appelé au cas où il aurait besoin de renverser l'un des paramètres passés par le registre.

12
Community 20 juin 2020 à 12:12

Vous avez les décalages dans le mauvais sens dans votre exemple, c'est pourquoi cela n'a pas de sens. Le code ne doit pas accéder à la région sous le pointeur de pile - elle n'est pas définie. La zone rouge est là pour protéger les 128 premiers octets sous le pointeur de pile. Votre deuxième exemple devrait lire :

function:
    sub rsp, 1024
    mov [rsp+16], rcx
    mov [rsp+32], rcx
    mov [rsp+128], rcx
    mov [rsp+1016], rcx
    add rsp, 1024
    ret

Si la quantité d'espace de travail dont une fonction a besoin est jusqu'à 128 octets alors elle peut utiliser des adresses en dessous du pointeur de pile sans avoir besoin d'ajuster la pile : c'est l'optimisation. Comparer:

function:        // Not using red-zone.
    sub rsp, 128
    mov [rsp+120], rcx
    add rsp, 128
    ret

Avec le même code en utilisant la zone rouge :

function:        // Using the red-zone, no adjustment of stack
    mov [rsp-8], rcx
    ret

La confusion sur les décalages du pointeur de pile est normalement due au fait que les compilateurs génèrent des décalages négatifs à partir de la trame (RBP), et non des décalages positifs à partir de la pile (RSP).

3
Andrew 21 juin 2016 à 14:21