Gestion des exceptions ARM
Windows sur ARM utilise le même mécanisme de gestion des exceptions structurées pour les exceptions générées par le matériel asynchrone et les exceptions générées par le logiciel synchrone. Les gestionnaires d'exceptions propres aux langages s'appuient sur la gestion des exceptions structurées Windows en utilisant des fonctions d'assistance de langage. Ce document décrit la gestion des exceptions dans Windows sur ARM et les assistances linguistiques à la fois l’assembleur Microsoft ARM et le compilateur MSVC générés.
Gestion des exceptions ARM
Windows sur ARM utilise des codes de déroulement pour contrôler le déroulement de la pile pendant la gestion structurée des exceptions (SEH). Les codes de déroulement sont une séquence d’octets stockés dans la .xdata
section de l’image exécutable. Ces codes décrivent l’opération du prologue de fonction et du code épilogue de manière abstraite. Le gestionnaire les utilise pour annuler les effets du prologue de fonction lorsqu’il se déroule vers le cadre de pile de l’appelant.
L’EABI ARM (interface binaire d’application incorporée) spécifie un modèle de déroulement d’exception qui utilise des codes de déroulement. Le modèle n’est pas suffisant pour le déroulement de SEH dans Windows. Il doit gérer les cas asynchrones où le processeur se trouve au milieu du prologue ou de l’épilogue d’une fonction. De même, Windows sépare le contrôle du déroulement en déroulement au niveau de la fonction et en déroulement de portée propre au langage, qui est unifié dans l'interface EABI ARM. Pour ces raisons, Windows on ARM spécifie plus de détails pour les données et la procédure de déroulement.
Hypothèses
Les images exécutables pour Windows on ARM utilisent le format PE (Portable Executable). Pour plus d’informations, consultez PE Format. Les informations de gestion des exceptions sont stockées dans les .pdata
sections de .xdata
l’image.
Le mécanisme de gestion des exceptions établit certaines hypothèses concernant le code qui suit l'interface ABI pour Windows on ARM :
Lorsqu’une exception se produit dans le corps d’une fonction, le gestionnaire peut annuler les opérations du prologue ou effectuer les opérations de l’épilogue de manière avancée. Les deux doivent produire des résultats identiques.
Les prologues et les épilogues ont tendance à se ressembler. Cette fonctionnalité peut être utilisée pour réduire la taille des métadonnées nécessaires pour décrire le déroulement.
Les fonctions ont tendance à être relativement petites. Plusieurs optimisations s’appuient sur cette observation pour l’empaquetage efficace des données.
Si une condition est définie dans un épilogue, elle s'applique également à chaque instruction de l'épilogue.
Si le prologue enregistre le pointeur de pile (SP) dans un autre registre, ce registre doit rester inchangé dans toute la fonction, de sorte que le sp d’origine peut être récupéré à tout moment.
À moins que le SP soit enregistré dans un autre registre, toutes les manipulations dont il fait l'objet doivent se produire exclusivement dans le prologue et l'épilogue.
Pour dérouler un frame de pile, les opérations suivantes sont nécessaires :
ajuster le registre r13 (SP) par incréments de 4 octets ;
exécuter un pop sur un ou plusieurs registres d'entiers ;
exécuter un pop sur un ou plusieurs registres VFP (virgule flottante vectorielle) ;
copier une valeur de registre arbitraire dans le registre r13 (SP) ;
charger le pointeur de pile (SP) à partir de la pile à l'aide d'une opération de légère post-décrémentation ;
analyser l'un des quelques types de frame bien définis.
.pdata
Archives
Les .pdata
enregistrements d’une image au format PE sont un tableau ordonné d’éléments de longueur fixe qui décrivent chaque fonction de manipulation de pile. Les fonctions feuilles (fonctions qui n’appellent pas d’autres fonctions) ne nécessitent .pdata
pas d’enregistrements lorsqu’elles ne manipulent pas la pile. (Autrement dit, elles n'ont besoin ni de stockage local ni d'enregistrer ou restaurer des registres non volatils.) Les enregistrements de ces fonctions peuvent être omis dans la section pour économiser de l’espace .pdata
. Une opération de déroulement de l'une de ces fonctions peut simplement copier l'adresse de retour du registre de liaison (ou LR, Link Register) dans le compteur de programme (ou PC, Program Counter) à déplacer vers l'appelant.
Chaque .pdata
enregistrement pour ARM est de 8 octets de long. Le format général d’un enregistrement place l’adresse virtuelle relative (RVA) de la fonction commence dans le premier mot 32 bits, suivi d’un deuxième mot qui contient un pointeur vers un bloc de longueur .xdata
variable ou un mot pack qui décrit une séquence de déroulement de fonction canonique, comme illustré dans ce tableau :
Décalage de mot | Bits | Objectif |
---|---|---|
0 | 0-31 | Function Start RVA est la RVA 32 bits du début de la fonction. Si la fonction contient du code thumb, le bit inférieur de cette adresse doit être défini. |
1 | 0-1 | Flag est un champ 2 bits qui indique comment interpréter les 30 bits restants du deuxième .pdata mot. Si Flag la valeur est 0, les bits restants forment une RVA d’informations d’exception (avec les deux bits faibles implicitement 0). Si Flag ce n’est pas zéro, les bits restants forment une structure Dewind Data Packed. |
1 | 2-31 | Informations d’exception RVA ou Données de déroulement packées. Exception Information RVA est l’adresse de la structure d’informations d’exception de longueur variable, stockée dans la .xdata section. Ces données doivent être alignées sur 4 octets.Les données de déroulement empaquetées sont une description compressée des opérations nécessaires au déroulement d’une fonction, en supposant une forme canonique. Dans ce cas, aucun enregistrement n’est .xdata requis. |
Données de déroulement compressées
Pour les fonctions dont les prologues et les épilogues suivent la forme canonique décrite ci-dessous, il est possible d'utiliser des données de déroulement compressées. Il élimine la nécessité d’un .xdata
enregistrement et réduit considérablement l’espace nécessaire pour fournir les données de déroulement. Les prologues canoniques et les épilogues sont conçus pour répondre aux exigences courantes d’une fonction simple qui ne nécessite pas de gestionnaire d’exceptions et effectue ses opérations de configuration et de déchirure dans un ordre standard.
Ce tableau montre le format d’un .pdata
enregistrement qui a empaqueté les données de déroulement :
Décalage de mot | Bits | Objectif |
---|---|---|
0 | 0-31 | Function Start RVA est la RVA 32 bits du début de la fonction. Si la fonction contient du code thumb, le bit inférieur de cette adresse doit être défini. |
1 | 0-1 | Flag est un champ 2 bits qui a ces significations :- 00 = données de déroulement empaquetées non utilisées ; les bits restants pointent à .xdata enregistrer.- 01 = données de déroulement compressées. - 10 = données de déroulement empaquetées où la fonction est supposée ne pas avoir de prologue. Ceci est utile pour décrire les fragments de fonction discontinus par rapport au début de la fonction. - 11 = réservé. |
1 | 2-12 | Function Length est un champ 11 bits qui fournit la longueur de la fonction entière en octets divisés par 2. Si la fonction est supérieure à 4 000 octets, un enregistrement complet .xdata doit être utilisé à la place. |
1 | 13-14 | Ret est un champ 2 bits qui indique la façon dont la fonction retourne :- 00 = retour via pop {pc} (le L bit d’indicateur doit être défini sur 1 dans ce cas).- 01 = retour à l’aide d’une branche 16 bits. - 10 = retour à l’aide d’une branche 32 bits. - 11 = aucun épilogue du tout. Ceci est utile pour décrire un fragment de fonction discontinu qui peut ne contenir qu'un prologue, mais dont l'épilogue se trouve ailleurs. |
1 | 15 | H est un indicateur 1 bits qui indique si la fonction « homes » enregistre le paramètre entier (r0-r3) en les pushant au début de la fonction et désalloue les 16 octets de la pile avant de retourner. (0 = ne s’inscrit pas à domicile, 1 = registres d’habitation.) |
1 | 16-18 | Reg est un champ 3 bits qui indique l’index du dernier registre non volatile enregistré. Si le R bit est 0, seuls les registres entiers sont enregistrés et sont supposés être dans la plage de r4-rN, où N est égal à 4 + Reg . Si le R bit est 1, seuls les registres à virgule flottante sont enregistrés et sont supposés être dans la plage de d8-dN, où N est égal à 8 + Reg . La combinaison spéciale de R = 1 et Reg = 7 indique qu’aucun registre n’est enregistré. |
1 | 19 | R est un indicateur 1 bits qui indique si les registres non volatiles enregistrés sont des registres entiers (0) ou des registres à virgule flottante (1). Si R la valeur est définie sur 1 et que le Reg champ est défini sur 7, aucun registre non volatile n’a été envoyé( push). |
1 | 20 | L est un indicateur 1 bits qui indique si la fonction enregistre/restaure LR, ainsi que d’autres registres indiqués par le Reg champ. (0 = n’enregistre pas/restaure, 1 = enregistre/restaure.) |
1 | 21 | C est un indicateur 1 bits qui indique si la fonction inclut des instructions supplémentaires pour configurer une chaîne d’images pour la marche rapide (1) ou non (0). Si ce bit est défini, le registre r11 est ajouté implicitement à la liste des registres non volatils d'entiers enregistrés. (Voir les restrictions ci-dessous si l’indicateur C est utilisé.) |
1 | 22-31 | Stack Adjust est un champ 10 bits qui indique le nombre d’octets de pile alloués pour cette fonction, divisé par 4. Cependant, seules les valeurs comprises entre 0x000 et 0x3F3 peuvent être directement encodées. Les fonctions qui allouent plus de 4 044 octets de pile doivent utiliser un enregistrement complet .xdata . Si le Stack Adjust champ est 0x3F4 ou supérieur, les 4 bits bas ont une signification spéciale :- Les bits 0-1 indiquent le nombre de mots d’ajustement de pile (1-4) moins 1. - Le bit 2 est défini sur 1 si le prologue a combiné cet ajustement dans son opération push. - Le bit 3 est défini sur 1 si l’épilogue a combiné cet ajustement dans son opération pop. |
Du fait des redondances possibles dans les encodages précédents, les restrictions suivantes s'appliquent :
Si l’indicateur
C
est défini sur 1 :L’indicateur
L
doit également être défini sur 1, car le chaînage d’images nécessite à la fois r11 et LR.r11 ne doit pas être inclus dans l’ensemble des registres décrits par
Reg
. Autrement dit, si r4-r11 sont poussés,Reg
ne doit décrire que r4-r10, car l’indicateurC
implique r11.
Si le
Ret
champ est défini sur 0, l’indicateurL
doit être défini sur 1.
La violation de ces restrictions donne lieu à une séquence non prise en charge.
Pour les besoins de la discussion ci-dessous, deux pseudo-indicateurs sont dérivés de Stack Adjust
:
PF
ou « pliage du prologue » indique qu’ilStack Adjust
est 0x3F4 ou plus grand et que le bit 2 est défini.EF
ou « pliage d’épilogue » indique qu’ilStack Adjust
s’agit 0x3F4 ou plus grand et que le bit 3 est défini.
Les prologues des fonctions canoniques peuvent avoir jusqu'à 5 instructions (à noter que les instructions 3a et 3b s'excluent mutuellement) :
Instruction | Un opcode est considéré être présent si : | Taille | Opcode | Codes de déroulement |
---|---|---|---|---|
1 | H ==1 |
16 | push {r0-r3} |
04 |
2 | C ==1 ou L ==1 ou R ==0 ou PF ==1 |
16/32 | push {registers} |
80-BF/D0-DF/EC-ED |
3a | C ==1 et (R ==1 et PF ==0) |
16 | mov r11,sp |
FB |
3b | C ==1 et (R ==0 ou PF ==1) |
32 | add r11,sp,#xx |
FC |
4 | R ==1 et Reg != 7 |
32 | vpush {d8-dE} |
E0-E7 |
5 | Stack Adjust != 0 et PF ==0 |
16/32 | sub sp,sp,#xx |
00-7F/E8-EB |
L’instruction 1 est toujours présente si le H
bit est défini sur 1.
Pour configurer le chaînage d’images, l’instruction 3a ou 3b est présente si le C
bit est défini. Il s'agit d'un mov
de 16 bits si aucun autre registre que r11 et LR ne fait l'objet d'un push ; sinon, il s'agit d'un add
de 32 bits.
Si un ajustement non plié est spécifié, l'instruction 5 est l'ajustement de pile explicite.
Les instructions 2 et 4 sont définies selon qu'un push est nécessaire ou pas. Ce tableau récapitule les registres enregistrés en fonction des champs, et PF
L
R
des C
champs. Dans tous les cas, N
est égal à Reg
+ 4, E
est égal à Reg
+ 8, et S
est égal à (~Stack Adjust
) & 3.
C | L | R | PF | Registres d'entiers faisant l'objet d'un push | Registres VFP faisant l'objet d'un push |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | r4 - r*N * |
Aucune |
0 | 0 | 0 | 1 | r*S * - r*N * |
Aucune |
0 | 0 | 1 | 0 | Aucune | d8 - d*E * |
0 | 0 | 1 | 1 | r*S * - r3 |
d8 - d*E * |
0 | 1 | 0 | 0 | r4 - r*N *, LR |
Aucune |
0 | 1 | 0 | 1 | r** - r*S N *, LR |
Aucune |
0 | 1 | 1 | 0 | LR | d8 - d*E * |
0 | 1 | 1 | 1 | r*S * - r3, LR |
d8 - d*E * |
1 | 0 | 0 | 0 | (encodage non valide) | (encodage non valide) |
1 | 0 | 0 | 1 | (encodage non valide) | (encodage non valide) |
1 | 0 | 1 | 0 | (encodage non valide) | (encodage non valide) |
1 | 0 | 1 | 1 | (encodage non valide) | (encodage non valide) |
1 | 1 | 0 | 0 | r4 - r**N , r11, LR |
Aucune |
1 | 1 | 0 | 1 | r** - r*S N *, r11, LR |
Aucune |
1 | 1 | 1 | 0 | r11, LR | d8 - d*E * |
1 | 1 | 1 | 1 | r*S * - r3, r11, LR |
d8 - d*E * |
Les épilogues des fonctions canoniques suivent une forme analogue, mais en sens inverse et avec quelques options supplémentaires. L'épilogue peut compter jusqu'à 5 instructions et sa forme est strictement dictée par celle du prologue.
Instruction | Un opcode est considéré être présent si : | Taille | Opcode |
---|---|---|---|
6 | Stack Adjust !=0 et EF ==0 |
16/32 | add sp,sp,#xx |
7 | R ==1 et Reg !=7 |
32 | vpop {d8-dE} |
8 | C ==1 ou (L ==1 et (H ==0 ou Ret !=0)) ou R ==0 ou EF ==1 |
16/32 | pop {registers} |
9a | H ==1 et (L ==0 ou Ret !=0) |
16 | add sp,sp,#0x10 |
9b | H ==1 et L ==1 et Ret ==0 |
32 | ldr pc,[sp],#0x14 |
10a | Ret ==1 |
16 | bx reg |
10b | Ret ==2 |
32 | b address |
L’instruction 6 est l’ajustement de pile explicite si un ajustement non plié est spécifié. Étant donné qu’il PF
est indépendant de , il est possible d’avoir EF
l’instruction 5 présente sans instruction 6, ou inversement.
Les instructions 7 et 8 utilisent la même logique que le prologue pour déterminer quels registres sont restaurés à partir de la pile, mais avec ces trois modifications : d’abord, EF
est utilisé à la place de PF
; deuxième, si Ret
= 0 et H
= 0, alors LR est remplacé par PC dans la liste des registres et l’épilogue se termine immédiatement ; troisième, si Ret
= 0 et H
= 1, ensuite LR est omis dans la liste d’inscriptions et dépilé par l’instruction 9b.
Si H
elle est définie, l’instruction 9a ou 9b est présente. L’instruction 9a est utilisée lorsqu’elle Ret
n’est pas égale à zéro, ce qui implique également la présence de 10a ou 10b. Si L=1, le LR a été dépilé dans le cadre de l’instruction 8. L’instruction 9b est utilisée lorsqu’elle L
est égale à 1 et Ret
est égale à zéro, pour indiquer une fin anticipée à l’épilogue, et pour retourner et ajuster la pile en même temps.
Si l’épilogue n’a pas encore terminé, l’instruction 10a ou 10b est présente, pour indiquer une branche 16 bits ou 32 bits, en fonction de la valeur de Ret
.
.xdata
Archives
Lorsque le format de déroulement compressé est insuffisant pour décrire le déroulement d’une fonction, un enregistrement de longueur .xdata
variable doit être créé. L’adresse de cet enregistrement est stockée dans le deuxième mot de l’enregistrement .pdata
. Le format du fichier .xdata
est un ensemble de mots de longueur variable empaqueté qui comporte quatre sections :
En-tête 1 ou 2 mots qui décrit la taille globale de la
.xdata
structure et fournit des données de fonction clés. Le deuxième mot n’est présent que si les champs Nombre d’épilogues et mots de code sont tous deux définis sur 0. Les champs sont décrits en détail dans ce tableau :Word Bits Objectif 0 0-17 Function Length
est un champ 18 bits qui indique la longueur totale de la fonction en octets, divisé par 2. Si une fonction est supérieure à 512 Ko, plusieurs.pdata
enregistrements doivent.xdata
être utilisés pour décrire la fonction. Pour plus de détails, consultez la section Grandes fonctions dans ce document.0 18-19 Vers est un champ 2 bits qui décrit la version du reste .xdata
. Seule la version 0 est actuellement définie ; les valeurs 1 à 3 sont réservées.0 20 X est un champ 1 bits qui indique la présence (1) ou l’absence (0) des données d’exception. 0 21 E
est un champ 1 bits qui indique que les informations qui décrivent un épilogue unique sont empaquetées dans l’en-tête (1) plutôt que d’exiger des mots d’étendue supplémentaires plus tard (0).0 22 F est un champ 1 bits qui indique que cet enregistrement décrit un fragment de fonction (1) ou une fonction complète (0). Un fragment implique qu’il n’y a pas de prologue et que tous les traitements de prologue doivent être ignorés. 0 23-27 Epilogue Count est un champ 5 bits qui a deux significations, en fonction de l’état E
du bit :
- SiE
la valeur est 0, ce champ est un nombre total d’étendues d’épilogue décrites dans la section 2. Si plus de 31 étendues existent dans la fonction, ce champ et le champ Mots du code doivent tous deux être définis sur 0 pour indiquer qu’un mot d’extension est requis.
- SiE
la valeur est 1, ce champ spécifie l’index du premier code de déroulement qui décrit le seul épilogue.0 28-31 Les mots de code sont un champ 4 bits qui spécifie le nombre de mots 32 bits requis pour contenir tous les codes de déroulement de la section 4. Si plus de 15 mots sont requis pour plus de 63 octets de code de déroulement, ce champ et le champ Nombre d’épilogues doivent tous deux être définis sur 0 pour indiquer qu’un mot d’extension est requis. 1 0-15 Le nombre d’épilogues étendus est un champ 16 bits qui offre plus d’espace pour l’encodage d’un nombre inhabituel d’épilogues. Le mot d’extension qui contient ce champ n’est présent que si les champs Nombre d’épilogues et mots de code dans le premier mot d’en-tête sont tous deux définis sur 0. 1 16-23 Les mots de code étendus sont un champ 8 bits qui offre plus d’espace pour l’encodage d’un nombre inhabituel de mots de code de déroulement. Le mot d’extension qui contient ce champ n’est présent que si les champs Nombre d’épilogues et mots de code dans le premier mot d’en-tête sont tous deux définis sur 0. 1 24-31 Reserved Après les données d’exception (si le
E
bit dans l’en-tête a été défini sur 0) est une liste d’informations sur les étendues d’épilogue, qui sont empaquetées un dans un mot et stockées dans l’ordre d’augmentation du décalage de démarrage. Chaque portée contient ces champs :Bits Objectif 0-17 Epilogue Start Offset est un champ 18 bits qui décrit le décalage de l’épilogue, en octets divisés par 2, par rapport au début de la fonction. 18-19 Res est un champ 2 bits réservé à l’expansion future. Il doit avoir la valeur 0. 20-23 La condition est un champ 4 bits qui donne la condition sous laquelle l’épilogue est exécuté. Pour les épilogues inconditionnels, il doit avoir la valeur 0xE, ce qui indique « toujours ». (Un épilogue doit être entièrement conditionnel ou entièrement inconditionnel, et en mode Thumb-2, l'épilogue commence par la première instruction située après l'opcode IT.) 24-31 Epilogue Start Index est un champ 8 bits qui indique l’index d’octet du premier code de déroulement qui décrit cet épilogue. Après la liste des portées d'épilogue figure un tableau d'octets qui contient les codes de déroulement, qui sont décrits en détail dans la section Code de déroulement de cet article. Ce tableau est rempli à la fin jusqu'à la limite du mot complet le plus proche. Les octets sont stockés dans un ordre Little-Endian, ce qui permet de les récupérer directement en mode Little-Endian.
Si le champ X de l’en-tête est 1, les octets de code de déroulement sont suivis des informations du gestionnaire d’exceptions. Il s’agit d’une RVA de gestionnaire d’exceptions qui contient l’adresse du gestionnaire d’exceptions, suivie immédiatement de la quantité de données (de longueur variable) requise par le gestionnaire d’exceptions.
L’enregistrement .xdata
est conçu afin qu’il soit possible d’extraire les 8 premiers octets et de calculer la taille complète de l’enregistrement, sans inclure la longueur des données d’exception de taille variable qui suivent. Cet extrait de code permet de calculer la taille de l'enregistrement :
ULONG ComputeXdataSize(PULONG Xdata)
{
ULONG Size;
ULONG EpilogueScopes;
ULONG UnwindWords;
if ((Xdata[0] >> 23) != 0) {
Size = 4;
EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
UnwindWords = (Xdata[0] >> 28) & 0x0f;
} else {
Size = 8;
EpilogueScopes = Xdata[1] & 0xffff;
UnwindWords = (Xdata[1] >> 16) & 0xff;
}
if (!(Xdata[0] & (1 << 21))) {
Size += 4 * EpilogueScopes;
}
Size += 4 * UnwindWords;
if (Xdata[0] & (1 << 20)) {
Size += 4; // Exception handler RVA
}
return Size;
}
Bien que le prologue et chaque épilogue aient un index dans les codes de déroulement, la table est partagée entre elles. Il n’est pas rare qu’ils puissent tous partager les mêmes codes de déroulement. Nous recommandons aux rédacteurs de compilateur de prévoir une optimisation pour ce cas de figure, car la taille maximale d'index est de 255, ce qui limite le nombre total de codes de déroulement possibles pour une fonction déterminée.
Codes de déroulement
Le tableau de codes de déroulement est un pool de séquences d'instructions qui indiquent exactement comment annuler les effets du prologue, dans l'ordre où les opérations doivent être annulées. Les codes de déroulement correspondent à un mini-ensemble d'instructions, encodé sous la forme d'une chaîne d'octets. Une fois l'exécution terminée, l'adresse de retour vers la fonction appelante se trouve dans le registre LR, et tous les registres non volatils sont restaurés avec les valeurs qui étaient les leurs au moment où la fonction a été appelée.
S'il était garanti que les exceptions ne se produisaient que dans le corps d'une fonction et jamais dans un prologue ou un épilogue, une seule séquence de déroulement serait nécessaire. Cependant, le modèle de déroulement Windows impose de pouvoir effectuer un déroulement à partir d'un prologue ou d'un épilogue partiellement exécuté. Pour satisfaire cette exigence, les codes de déroulement ont été soigneusement conçus pour bénéficier d'un mappage un-à-un non équivoque à chaque opcode approprié dans le prologue et l'épilogue. Cela a plusieurs conséquences :
Il est possible de calculer la longueur du prologue et de l'épilogue en comptant le nombre de codes de déroulement. Cela est possible même avec des instructions Thumb-2 de longueur variable, car il existe des mappages distincts pour les opcodes de 16 et 32 bits.
En comptant le nombre d'instructions après le début d'une portée d'épilogue, il est possible d'ignorer le nombre équivalent de codes de déroulement et d'exécuter le reste d'une séquence pour terminer le déroulement partiellement exécuté par l'épilogue.
En comptant le nombre d'instructions avant la fin du prologue, il est possible d'ignorer le nombre équivalent de codes de déroulement et d'exécuter le reste d'une séquence pour annuler uniquement les parties du prologue qui ont mené à bien l'exécution.
Le tableau suivant présente le mappage entre les codes de déroulement et les opcodes. Les codes les plus courants ne font qu'un octet, alors que les moins courants nécessitent deux, trois, voire quatre octets. Chaque code est stocké de l'octet le plus significatif à l'octet le moins significatif. La structure des codes de déroulement est différente de l'encodage décrit dans l'interface EABI ARM, car ces codes de déroulement sont conçus pour bénéficier d'un mappage un-à-un aux opcodes dans le prologue et l'épilogue afin de permettre le déroulement des prologues et épilogues partiellement exécutés.
Octet 1 | Octet 2 | Octet 3 | Octet 4 | Taille d'opcode | Explication |
---|---|---|---|---|---|
00-7F | 16 | add sp,sp,#X où X est (Code &0x7F) * 4 |
|||
80-BF | 00-FF | 32 | pop {r0-r12, lr} où LR est dépilé si Code &0x2000 et r0-r12 sont dépilés si le bit correspondant est défini dans Code &0x1FFF |
||
C0-CF | 16 | mov sp,rX où X est Code &0x0F |
|||
D0-D7 | 16 | pop {r4-rX,lr} où X est (Code &0x03) + 4 et LR est dépilé si Code &0x04 |
|||
D8-DF | 32 | pop {r4-rX,lr} où X est (Code &0x03) + 8 et LR est dépilé si Code &0x04 |
|||
E0-E7 | 32 | vpop {d8-dX} où X est (Code &0x07) + 8 |
|||
E8-EB | 00-FF | 32 | addw sp,sp,#X où X est (Code &0x03FF) * 4 |
||
EC-ED | 00-FF | 16 | pop {r0-r7,lr} où LR est dépilé si Code &0x0100 et r0-r7 sont dépilés si le bit correspondant est défini dans Code &0x00FF |
||
EE | 00-0F | 16 | Spécifique à Microsoft | ||
EE | 10-FF | 16 | Disponible | ||
EF | 00-0F | 32 | ldr lr,[sp],#X où X est (Code &0x000F) * 4 |
||
EF | 10-FF | 32 | Disponible | ||
F0-F4 | - | Disponible | |||
F5 | 00-FF | 32 | vpop {dS-dE} où S est (Code &0x00F0) >> 4 et E est Code &0x000F |
||
F6 | 00-FF | 32 | vpop {dS-dE} où S est (Code &0x00F0) >> 4) + 16 et E est (Code &0x000F) + 16 |
||
F7 | 00-FF | 00-FF | 16 | add sp,sp,#X où X est (Code &0x00FFFF) * 4 |
|
F8 | 00-FF | 00-FF | 00-FF | 16 | add sp,sp,#X où X est (Code &0x00FFFFFF) * 4 |
F9 | 00-FF | 00-FF | 32 | add sp,sp,#X où X est (Code &0x00FFFF) * 4 |
|
FA | 00-FF | 00-FF | 00-FF | 32 | add sp,sp,#X où X est (Code &0x00FFFFFF) * 4 |
FB | 16 | nop (16 bits) | |||
FC | 32 | nop (32 bits) | |||
FD | 16 | fin + nop de 16 bits dans l'épilogue | |||
FE | 32 | fin + nop de 32 bits dans l'épilogue | |||
FF | - | end |
Cela montre la plage de valeurs hexadécimales pour chaque octet dans un code de déroulement, ainsi que la taille opcode Opsize et l’interprétation d’instruction d’origine correspondante. Les cellules vides indiquent des codes de déroulement plus courts. Dans les instructions qui contiennent des valeurs élevées couvrant plusieurs octets, les bits les plus significatifs sont stockés en premier. Le champ Opsize affiche la taille implicite d’opcode associée à chaque opération Thumb-2. Les entrées en double apparentes figurant dans le tableau avec des encodages différents servent à faire la distinction entre les différentes tailles d’opcode.
Les codes de déroulement sont conçus de telle sorte que le premier octet du code indique à la fois la taille totale en octets du code et la taille de l'opcode correspondant dans le flux d'instructions. Pour calculer la taille du prologue ou de l’épilogue, parcourez les codes de déroulement du début jusqu’à la fin de la séquence, puis utilisez une table de correspondance ou une méthode similaire pour déterminer la longueur de l’opcode correspondant.
Les codes de déroulement 0xFD et 0xFE sont équivalents au code de fin normal 0xFF, mais prennent en compte un opcode nop supplémentaire dans le cas de l'épilogue, de 16 ou 32 bits. Pour les prologues, les codes 0xFD, 0xFE et 0xFF sont tout à fait équivalents. Cela tient compte des terminaisons bx lr
d’épilogue courants ou b <tailcall-target>
, qui n’ont pas d’instruction de prologue équivalente. Cela augmente les probabilités de partage des séquences de déroulement entre le prologue et les épilogues.
Dans bien des cas, il devrait être possible d'utiliser le même ensemble de codes de déroulement pour le prologue et tous les épilogues. Or, pour gérer le déroulement des prologues et des épilogues partiellement exécutés, il serait nécessaire d'avoir plusieurs séquences de code de déroulement avec un ordre ou un comportement différents. C'est pourquoi chaque épilogue a son propre index dans le tableau de déroulement pour indiquer où commencer l'exécution.
Déroulement des prologues et des épilogues partiels
Le cas de déroulement le plus courant est celui où l'exception se produit dans le corps de la fonction, en dehors du prologue et de tous les épilogues. Dans ce cas, le déchargeur exécute les codes du tableau de déchargement à partir de l'index 0 et continue jusqu'à ce qu'un opcode de fin soit détecté.
Quand une exception se produit pendant l'exécution d'un prologue ou d'un épilogue, le frame de pile n'est que partiellement construit et le déchargeur doit déterminer exactement ce qui a été fait pour l'annuler correctement.
Penchons-nous à titre d'exemple sur cette séquence de prologue et d'épilogue :
0000: push {r0-r3} ; 0x04
0002: push {r4-r9, lr} ; 0xdd
0006: mov r7, sp ; 0xc7
...
0140: mov sp, r7 ; 0xc7
0142: pop {r4-r9, lr} ; 0xdd
0146: add sp, sp, #16 ; 0x04
0148: bx lr
En regard de chaque opcode figure le code déroulement approprié qui décrit l’opération. La séquence de codes de déroulement du prologue est une image miroir des codes de déroulement de l'épilogue, l'instruction finale en moins. Ce cas est courant et est la raison pour laquelle les codes de déroulement du prologue sont toujours supposés être stockés dans l’ordre inverse de l’ordre d’exécution du prologue. Cela nous donne un ensemble commun de codes de déroulement :
0xc7, 0xdd, 0x04, 0xfd
Le code 0xFD est un code spécial pour la fin de la séquence qui signifie que l'épilogue est plus long que le prologue d'une instruction de 16 bits. Cela permet un plus grand partage de codes de déroulement.
Dans l'exemple, si une exception se produit pendant l'exécution du corps de la fonction entre le prologue et l'épilogue, le déroulement commence par le cas de l'épilogue au décalage 0 dans le code de l'épilogue. Cela correspond au décalage 0x140 dans l'exemple. Le dérouleur exécute la séquence de déroulement complète, car aucun nettoyage n'a été fait. En revanche, si l'exception se produit au niveau de la première instruction suivant le début du code de l'épilogue, le dérouleur peut procéder au déroulement en ignorant le premier code de déroulement. Étant donné un mappage un-à-un entre les codes opcodes et les codes de déroulement, si le déroulement de l’instruction n dans l’épilogue, le déroulement doit ignorer les premiers codes de déroulement.
La logique qui prévaut dans le cas du prologue est identique mais inversée. Si le déroulement se produit à partir du décalage 0 dans le prologue, il n'y a rien à exécuter. Si le déroulement démarre à la première instruction, la séquence de déroulement doit commencer au premier code de déroulement en partant de la fin, car les codes de déroulement du prologue sont stockés dans l'ordre inverse. Dans le cas général, en cas de déroulement de l’instruction n dans le prologue, le déroulement doit commencer à s’exécuter au niveau des codes de déroulement à partir de la fin de la liste des codes.
Les codes de déroulement de prologue et d’épilogue ne correspondent pas toujours exactement. Dans ce cas, il se peut que le tableau des codes de déroulement doive contenir plusieurs séquences de codes. Pour déterminer à quel décalage commencer le traitement des codes, suivez cette logique :
Si le déroulement démarre dans le corps de la fonction, commencez à exécuter les codes de déroulement à l’index 0 et continuez jusqu’à ce qu’un opcode de fin soit atteint.
Si le déroulement démarre dans un épilogue, utilisez l'index de démarrage propre à l'épilogue fourni par la portée de l'épilogue. Calculez le nombre d'octets qui séparent le compteur de programme (PC) du début de l'épilogue. Parcourez les codes de déroulement jusqu'à ce que toutes les instructions déjà exécutées soient prises en compte. Exécutez la séquence de déroulement à partir de ce point.
Si le déroulement démarre dans le prologue, partez de l'index 0 dans les codes de déroulement. Calculez la longueur du code de prologue à partir de la séquence, puis calculez le nombre d'octets qui séparent le compteur de programme (PC) de la fin du prologue. Parcourez les codes de déroulement jusqu'à ce que toutes les instructions non exécutées soient prises en compte. Exécutez la séquence de déroulement à partir de ce point.
Les codes de déroulement du prologue doivent toujours être les premiers dans le tableau. ils sont également les codes utilisés pour décompresser dans le cas général du déroulement à partir du corps. Les séquences de codes propres à l'épilogue doivent suivre immédiatement la séquence de codes du prologue.
Fragments de fonction
Pour l'optimisation de code, il peut être utile de scinder une fonction en plusieurs parties discontinues. Lorsque cela est fait, chaque fragment de fonction nécessite son propre enregistrement distinct .pdata
( et éventuellement .xdata
).
En supposant que le prologue de la fonction se trouve au début de la fonction et qu'il ne peut pas être scindé, il existe quatre cas de fragments de fonction :
un prologue uniquement ; tous les épilogues se trouvent dans d'autres fragments ;
Prologue et un ou plusieurs épilogues ; plus d’épilogues dans d’autres fragments.
pas de prologue ni d'épilogues ; un prologue et un ou plusieurs épilogues dans d'autres fragments ;
Épilogues uniquement ; prologue et éventuellement plus d’épilogues dans d’autres fragments.
Dans le premier cas, seul le prologue doit être décrit. Cela peut être fait sous forme compacte .pdata
en décrivant normalement le prologue et en spécifiant une Ret
valeur de 3 pour indiquer aucun épilogue. Dans la forme complète .xdata
, cela peut être fait en fournissant les codes de déroulement du prologue à l’index 0 comme d’habitude, et en spécifiant un nombre d’épilogues de 0.
Le deuxième cas s'apparente tout simplement à une fonction normale. S’il n’y a qu’un seul épilogue dans le fragment et qu’il se trouve à la fin du fragment, un enregistrement compact .pdata
peut être utilisé. Sinon, un enregistrement complet .xdata
doit être utilisé. Gardez à l'esprit que les décalages spécifiés pour le début de l'épilogue sont fonction du début du fragment, et non du début initial de la fonction.
Les troisième et quatrième cas sont des variantes des premier et deuxième cas, respectivement, sauf qu’ils ne contiennent pas de prologue. Dans ces situations, il est supposé qu’il y a du code avant le début de l’épilogue et qu’il est considéré comme faisant partie du corps de la fonction, qui serait normalement déwound en annulant les effets du prologue. Ces cas doivent ainsi être encodés avec un pseudo-prologue, qui décrit la façon dont le déroulement s'opère à partir du corps, mais qui est considéré comme étant de longueur nulle au moment de déterminer si un déroulement partiel doit être effectué au début du fragment. Ce pseudo-prologue peut aussi être décrit en utilisant les mêmes codes de déroulement que l'épilogue, car on peut supposer qu'ils effectuent des opérations équivalentes.
Dans les troisième et quatrième cas, la présence d’un pseudo-prologue est spécifiée soit en définissant le Flag
champ de l’enregistrement compact .pdata
sur 2, soit en définissant l’indicateur F dans l’en-tête .xdata
sur 1. Dans les deux cas, la recherche d'un déroulement de prologue partiel est ignorée et tous les déroulements non liés aux épilogues sont considérés comme complets.
Grandes fonctions
Les fragments peuvent être utilisés pour décrire les fonctions supérieures à la limite de 512 Ko imposée par les champs de bits dans l’en-tête .xdata
. Pour décrire une fonction plus grande, il suffit de la décomposer en fragments inférieurs à 512 Ko. Chaque fragment doit être ajusté afin qu’il ne divise pas un épilogue en plusieurs morceaux.
Seul le premier fragment de la fonction contient un prologue. Tous les autres fragments sont marqués comme n’ayant pas de prologue. Selon le nombre d'épilogues, chaque fragment peut contenir aucun ou plusieurs épilogues. Ne perdez pas de vue que chaque portée d'épilogue d'un fragment spécifie son décalage de départ par rapport au début du fragment, et non au début de la fonction.
Si un fragment n’a pas de prologue et pas d’épilogue, il nécessite toujours son propre .pdata
enregistrement (et éventuellement .xdata
) pour décrire comment se dérouler à partir du corps de la fonction.
Emballage par rétraction
Un cas spécial plus complexe de fragments de fonction est appelé habillage de réduction. Il s’agit d’une technique permettant de différer les enregistrements d’enregistrement à partir du début de la fonction vers une version ultérieure de la fonction. Il optimise les cas simples qui ne nécessitent pas d’enregistrement. Ce cas comporte deux parties : il existe une région externe qui alloue l’espace de pile, mais enregistre un ensemble minimal de registres et une région interne qui enregistre et restaure d’autres registres.
ShrinkWrappedFunction
push {r4, lr} ; A: save minimal non-volatiles
sub sp, sp, #0x100 ; A: allocate all stack space up front
... ; A:
add r0, sp, #0xE4 ; A: prepare to do the inner save
stm r0, {r5-r11} ; A: save remaining non-volatiles
... ; B:
add r0, sp, #0xE4 ; B: prepare to do the inner restore
ldm r0, {r5-r11} ; B: restore remaining non-volatiles
... ; C:
pop {r4, pc} ; C:
Les fonctions encapsulées de réduction sont généralement censées préalloué l’espace pour les registres supplémentaires enregistrés dans le prologue normal, puis enregistrer les registres à l’aide str
ou stm
au lieu de push
. Cette action conserve toutes les manipulations de pointeur de pile dans le prologue d’origine de la fonction.
L’exemple de fonction de réduction encapsulée doit être divisé en trois régions, marquées comme A
, B
et C
dans les commentaires. La première A
région couvre le début de la fonction jusqu’à la fin des enregistrements non volatiles supplémentaires. Un .pdata
ou .xdata
un enregistrement doit être construit pour décrire ce fragment comme ayant un prologue et aucun épilogue.
La région centrale B
obtient son propre .pdata
ou .xdata
enregistrement qui décrit un fragment qui n’a pas de prologue et aucun épilogue. Cependant, des codes de déroulement doivent toujours être présents pour cette région, car elle est considérée comme un corps de fonction. Les codes doivent décrire un prologue composite qui représente les registres d’origine enregistrés dans le prologue de région A
et les registres supplémentaires enregistrés avant d’entrer dans la région B
, comme s’ils étaient produits par une séquence d’opérations.
Les registres enregistrés pour la région B
ne peuvent pas être considérés comme un « prologue interne », car le prologue composite décrit pour la région B
doit décrire à la fois le prologue de la région A
et les registres supplémentaires enregistrés. Si le fragment B
avait un prologue, les codes de déroulement impliquent également la taille de ce prologue, et il n’existe aucun moyen de décrire le prologue composite d’une manière qui mappe un-à-un avec les opcodes qui enregistrent uniquement les registres supplémentaires.
Les enregistrement d’enregistrement supplémentaires doivent être considérés comme faisant partie de la région A
, car tant qu’ils ne sont pas terminés, le prologue composite ne décrit pas avec précision l’état de la pile.
La dernière C
région obtient son propre .pdata
ou .xdata
enregistrement, décrivant un fragment qui n’a pas de prologue, mais qui a un épilogue.
Une autre approche peut également fonctionner si la manipulation de la pile effectuée avant d’entrer dans la région B
peut être réduite à une seule instruction :
ShrinkWrappedFunction
push {r4, lr} ; A: save minimal non-volatile registers
sub sp, sp, #0xE0 ; A: allocate minimal stack space up front
... ; A:
push {r4-r9} ; A: save remaining non-volatiles
... ; B:
pop {r4-r9} ; B: restore remaining non-volatiles
... ; C:
pop {r4, pc} ; C: restore non-volatile registers
L’insight clé est que sur chaque limite d’instruction, la pile est entièrement cohérente avec les codes de déroulement de la région. Si un déroulement se produit avant l’envoi interne dans cet exemple, il est considéré comme faisant partie de la région A
. Seul le prologue de la région A
est déwound. Si le déroulement se produit après l’envoi interne, il est considéré comme faisant partie de la région B
, qui n’a pas de prologue. Toutefois, il comporte des codes de déroulement qui décrivent à la fois l’push interne et le prologue d’origine de la région A
. Une logique similaire est en attente pour la fenêtre contextuelle interne.
Encodage d'optimisations
La richesse des codes de déroulement, et la possibilité d’utiliser des formes de données compactes et étendues, offrent de nombreuses opportunités d’optimiser l’encodage pour réduire davantage l’espace. Avec l’utilisation agressive de ces techniques, la surcharge nette de la description des fonctions et des fragments à l’aide de codes de déroulement peut être réduite.
L’idée d’optimisation la plus importante : ne confondez pas les limites du prologue et de l’épilogue à des fins de déroulement avec des limites logiques de prologue et d’épilogue du point de vue du compilateur. Les limites de déroulement peuvent être réduites et resserrées pour améliorer l'efficacité. Par exemple, un prologue peut contenir du code après la configuration de la pile pour effectuer des vérifications. Mais une fois que toutes les manipulations de pile sont terminées, il n’est pas nécessaire d’encoder d’autres opérations, et tout ce qui peut être supprimé du prologue de déroulement.
La même règle s'applique à la longueur des fonctions. S’il existe des données (par exemple, un pool littéral) qui suivent un épilogue dans une fonction, elles ne doivent pas être incluses dans le cadre de la longueur de la fonction. En réduisant la fonction uniquement au code qui fait partie de la fonction, les chances sont beaucoup plus importantes que l’épilogue est à la fin et qu’un enregistrement compact .pdata
peut être utilisé.
Dans un prologue, une fois le pointeur de pile enregistré dans un autre registre, il n’est généralement pas nécessaire d’enregistrer d’autres opcodes. Pour décompresser la fonction, la première chose effectuée consiste à récupérer le fournisseur de services à partir du registre enregistré. D’autres opérations n’ont aucun effet sur le déroulement.
Les épilogues à instruction unique n’ont pas besoin d’être encodés du tout, en tant qu’étendues ou en tant que codes de déroulement. Si un déroulement se produit avant l’exécution de cette instruction, il est sûr de supposer qu’elle se trouve dans le corps de la fonction. L’exécution des codes de déroulement du prologue suffit. Lorsque le déroulement a lieu après l’exécution de l’instruction unique, par définition, il a lieu dans une autre région.
Les épilogues multi-instructions n’ont pas besoin d’encoder la première instruction de l’épilogue, pour la même raison que le point précédent : si le déroulement a lieu avant l’exécution de cette instruction, un déroulement complet du prologue est suffisant. Si le déroulement a lieu après cette instruction, seules les opérations ultérieures doivent être prises en compte.
La réutilisation du code de déroulement doit être agressive. L’index de chaque étendue d’épilogue spécifie un point de départ arbitraire dans le tableau de codes de déroulement. Il n’est pas nécessaire de pointer vers le début d’une séquence précédente ; il peut pointer au milieu. La meilleure approche consiste à générer la séquence de code de déroulement. Ensuite, recherchez une correspondance d’octet exacte dans le pool déjà encodé de séquences. Utilisez une correspondance parfaite comme point de départ pour la réutilisation.
Une fois que les épilogues à instruction unique sont ignorés, s’il n’y a pas d’épilogues restants, envisagez d’utiliser une forme compacte .pdata
; il devient beaucoup plus probable en l’absence d’un épilogue.
Exemples
Dans ces exemples, la base d'image se trouve au niveau de 0x00400000.
Exemple 1 : fonction terminale, pas de variables locales
Prologue:
004535F8: B430 push {r4-r5}
Epilogue:
00453656: BC30 pop {r4-r5}
00453658: 4770 bx lr
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x000535F8 (= 0x004535F8-0x00400000)
Mot 1
Flag
= 1, indiquant les formats de prologue canonique et d’épilogueFunction Length
= 0x31 (= 0x62/2)Ret
= 1, indiquant un retour de branche 16 bitsH
= 0, indiquant que les paramètres n’ont pas été hébergésR
= 0 etReg
= 1, indiquant push/pop de r4-r5L
= 0, indiquant qu’aucun enregistrement/restauration LR n’est indiquéC
= 0, indiquant qu’aucun chaînage de trames n’est indiquéStack Adjust
= 0, indiquant qu’aucun ajustement de pile n’est appliqué
Exemple 2 : fonction imbriquée avec allocation locale
Prologue:
004533AC: B5F0 push {r4-r7, lr}
004533AE: B083 sub sp, sp, #0xC
Epilogue:
00453412: B003 add sp, sp, #0xC
00453414: BDF0 pop {r4-r7, pc}
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x000533AC (= 0x004533AC -0x00400000)
Mot 1
Flag
= 1, indiquant les formats de prologue canonique et d’épilogueFunction Length
= 0x35 (= 0x6A/2)Ret
= 0, indiquant un retour pop {pc}H
= 0, indiquant que les paramètres n’ont pas été hébergésR
= 0 etReg
= 3, indiquant push/pop de r4-r7L
= 1, indiquant que LR a été enregistré/restauréC
= 0, indiquant qu’aucun chaînage de trames n’est indiquéStack Adjust
= 3 (= 0x0C/4)
Exemple 3 : fonction variadique imbriquée
Prologue:
00453988: B40F push {r0-r3}
0045398A: B570 push {r4-r6, lr}
Epilogue:
004539D4: E8BD 4070 pop {r4-r6}
004539D8: F85D FB14 ldr pc, [sp], #0x14
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x00053988 (= 0x00453988-0x00400000)
Mot 1
Flag
= 1, indiquant les formats de prologue canonique et d’épilogueFunction Length
= 0x2A (= 0x54/2)Ret
= 0, indiquant un retour pop {pc}-style (dans ce cas unldr pc,[sp],#0x14
retour)H
= 1, indiquant que les paramètres ont été hébergésR
= 0 etReg
= 2, indiquant push/pop de r4-r6L
= 1, indiquant que LR a été enregistré/restauréC
= 0, indiquant qu’aucun chaînage de trames n’est indiquéStack Adjust
= 0, indiquant qu’aucun ajustement de pile n’est appliqué
Exemple 4 : fonction avec plusieurs épilogues
Prologue:
004592F4: E92D 47F0 stmdb sp!, {r4-r10, lr}
004592F8: B086 sub sp, sp, #0x18
Epilogues:
00459316: B006 add sp, sp, #0x18
00459318: E8BD 87F0 ldm sp!, {r4-r10, pc}
...
0045943E: B006 add sp, sp, #0x18
00459440: E8BD 87F0 ldm sp!, {r4-r10, pc}
...
004595D4: B006 add sp, sp, #0x18
004595D6: E8BD 87F0 ldm sp!, {r4-r10, pc}
...
00459606: B006 add sp, sp, #0x18
00459608: E8BD 87F0 ldm sp!, {r4-r10, pc}
...
00459636: F028 FF0F bl KeBugCheckEx ; end of function
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x000592F4 (= 0x004592F4-0x00400000)
Mot 1
Flag
= 0, indiquant l’enregistrement.xdata
présent (requis pour plusieurs épilogues).xdata
adresse - 0x00400000
.xdata
(variable, 6 mots) :
Mot 0
Function Length
= 0x0001A3 (= 0x000346/2)Vers
= 0, indiquant la première version de.xdata
X
= 0, indiquant aucune donnée d’exceptionE
= 0, indiquant une liste d’étendues d’épilogueF
= 0, indiquant une description complète de la fonction, y compris le prologueEpilogue Count
= 0x04, indiquant les 4 étendues d’épilogue totalesCode Words
= 0x01, indiquant un mot 32 bits de codes de déroulement
Mots 1 à 4, décrivant 4 portées d'épilogue à 4 emplacements. À chaque portée correspond un ensemble commun de codes de déroulement, partagé avec le prologue, au niveau du décalage 0x00, et inconditionnel, spécifiant la condition 0x0E (toujours).
Codes de déroulement, commençant au Mot 5 : (partagé entre le prologue et l'épilogue)
Code de déroulement 0 = 0x06 : sp += (6 << 2)
Code de déroulement 1 = 0xDE : pop {r4-r10, lr}
Code de déroulement 2 = 0xFF : fin
Exemple 5 : fonction avec pile dynamique et épilogue interne
Prologue:
00485A20: B40F push {r0-r3}
00485A22: E92D 41F0 stmdb sp!, {r4-r8, lr}
00485A26: 466E mov r6, sp
00485A28: 0934 lsrs r4, r6, #4
00485A2A: 0124 lsls r4, r4, #4
00485A2C: 46A5 mov sp, r4
00485A2E: F2AD 2D90 subw sp, sp, #0x290
Epilogue:
00485BAC: 46B5 mov sp, r6
00485BAE: E8BD 41F0 ldm sp!, {r4-r8, lr}
00485BB2: B004 add sp, sp, #0x10
00485BB4: 4770 bx lr
...
00485E2A: F7FF BE7D b #0x485B28 ; end of function
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x00085A20 (= 0x00485A20-0x00400000)
Mot 1
Flag
= 0, indiquant l’enregistrement.xdata
présent (nécessaire pour plusieurs épilogues).xdata
adresse - 0x00400000
.xdata
(variable, 3 mots) :
Mot 0
Function Length
= 0x0001A3 (= 0x000346/2)Vers
= 0, indiquant la première version de.xdata
X
= 0, indiquant aucune donnée d’exceptionE
= 0, indiquant une liste d’étendues d’épilogueF
= 0, indiquant une description complète de la fonction, y compris le prologueEpilogue Count
= 0x001, indiquant l’étendue d’épilogue totale 1Code Words
= 0x01, indiquant un mot 32 bits de codes de déroulement
Mot 1 : portée d'épilogue au niveau du décalage 0xC6 (= 0x18C/2), index du code de déroulement de départ à 0x00, avec une condition de 0x0E (toujours)
Codes de déroulement, commençant au Mot 2 : (partagé entre le prologue et l'épilogue)
Code de déroulement 0 = 0xC6 : sp = r6
Code de déroulement 1 = 0xDC : pop {r4-r8, lr}
Code de déroulement 2 = 0x04 : sp += (4 << 2)
Code de déroulement 3 = 0xFD : fin, compte comme une instruction de 16 bits pour l'épilogue
Exemple 6 : fonction avec un gestionnaire d'exceptions
Prologue:
00488C1C: 0059 A7ED dc.w 0x0059A7ED
00488C20: 005A 8ED0 dc.w 0x005A8ED0
FunctionStart:
00488C24: B590 push {r4, r7, lr}
00488C26: B085 sub sp, sp, #0x14
00488C28: 466F mov r7, sp
Epilogue:
00488C6C: 46BD mov sp, r7
00488C6E: B005 add sp, sp, #0x14
00488C70: BD90 pop {r4, r7, pc}
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x00088C24 (= 0x00488C24-0x00400000)
Mot 1
Flag
= 0, indiquant l’enregistrement.xdata
présent (nécessaire pour plusieurs épilogues).xdata
adresse - 0x00400000
.xdata
(variable, 5 mots) :
Mot 0
Function Length
=0x000027 (= 0x00004E/2)Vers
= 0, indiquant la première version de.xdata
X
= 1, indiquant les données d’exception présentesE
= 1, indiquant un épilogue uniqueF
= 0, indiquant une description complète de la fonction, y compris le prologueEpilogue Count
= 0x00, indiquant que les codes de déroulement de l’épilogue commencent au décalage 0x00Code Words
= 0x02, indiquant deux mots 32 bits de codes de déroulement
Codes de déroulement, commençant au Mot 1 :
Code de déroulement 0 = 0xC7 : sp = r7
Code de déroulement 1 = 0x05 : sp += (5 << 2)
Code de déroulement 2 = 0xED/0x90 : pop {r4, r7, lr}
Code de déroulement 4 = 0xFF : fin
Word 3 spécifie un gestionnaire d’exceptions = 0x0019A7ED (= 0x0059A7ED - 0x00400000)
Les mots 4 et suivants sont des données d'exception inline
Exemple 7 : Funclet
Function:
00488C72: B500 push {lr}
00488C74: B081 sub sp, sp, #4
00488C76: 3F20 subs r7, #0x20
00488C78: F117 0308 adds r3, r7, #8
00488C7C: 1D3A adds r2, r7, #4
00488C7E: 1C39 adds r1, r7, #0
00488C80: F7FF FFAC bl target
00488C84: B001 add sp, sp, #4
00488C86: BD00 pop {pc}
.pdata
(fixe, 2 mots) :
Mot 0
Function Start RVA
= 0x00088C72 (= 0x00488C72-0x00400000)
Mot 1
Flag
= 1, indiquant les formats de prologue canonique et d’épilogueFunction Length
= 0x0B (= 0x16/2)Ret
= 0, indiquant un retour pop {pc}H
= 0, indiquant que les paramètres n’ont pas été hébergésR
= 0 etReg
= 7, indiquant qu’aucun registre n’a été enregistré/restauréL
= 1, indiquant que LR a été enregistré/restauréC
= 0, indiquant qu’aucun chaînage de trames n’est indiquéStack Adjust
= 1, indiquant un ajustement de pile de 1 × 4 octets
Voir aussi
Vue d’ensemble des conventions ABI ARM
Problèmes courants de migration ARM Visual C++