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
2 réponses
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.
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).
Questions connexes
Questions liées
De nouvelles questions
assembly
Questions relatives au langage d'assemblage. Veuillez marquer le processeur et / ou le jeu d'instructions que vous utilisez, ainsi que l'assembleur, un jeu valide doit être comme ceci: (assembly, x86, gnu) Notez que vous devez utiliser la balise ".net-assembly" à la place pour Les langages d'assemblage .NET et pour le bytecode Java, utilisez plutôt la balise java-bytecode-asm.