Delen via


Afhandeling van ARM-uitzonderingen

Windows op ARM maakt gebruik van hetzelfde gestructureerde mechanisme voor het verwerken van uitzonderingen voor asynchrone hardware gegenereerde uitzonderingen en synchrone software gegenereerde uitzonderingen. Taalspecifieke uitzonderingshandlers zijn gebaseerd op de verwerking van gestructureerde Windows-uitzonderingen met behulp van taalhulpfuncties. In dit document wordt de verwerking van uitzonderingen in Windows op ARM en worden de taalhulpprogramma's die zowel de Microsoft ARM-assembler als de MSVC-compiler genereren, beschreven.

Afhandeling van ARM-uitzonderingen

Windows op ARM maakt gebruik van ontkoppelingcodes om het afwikkelen van de stack tijdens het verwerken van gestructureerde uitzonderingen (SEH) te regelen. Afwikkelcodes zijn een reeks bytes die zijn opgeslagen in de .xdata sectie van het uitvoerbare beeld. Deze codes beschrijven de werking van functieproloog en epiloogcode op een abstracte manier. De handler gebruikt ze om de effecten van de functieproloog ongedaan te maken wanneer deze wordt afgewikkeld naar het stackframe van de aanroeper.

De binaire interface van ARM EABI (embedded application binary interface) specificeert een model voor exception handling dat gebruikmaakt van unwind-codes. Het model is niet voldoende voor SEH-afwikkeling in Windows. Het moet asynchrone gevallen verwerken waarbij de processor zich midden in de proloog of epiloog van een functie bevindt. Windows scheidt ook afwikkelingscontrole op functieniveau en taalspecifieke scope-afwikkeling, die verenigd zijn in de ARM EABI. Om deze redenen geeft Windows in ARM meer details op voor de afwikkeling van gegevens en procedures.

Aannamen

Uitvoerbare bestanden voor Windows op ARM gebruiken het Portable Executable (PE)-formaat. Zie PE Format voor meer informatie. Informatie over het verwerken van uitzonderingen wordt opgeslagen in de .pdata secties .xdata van de afbeelding.

Het mechanisme voor het afhandelen van uitzonderingen maakt bepaalde veronderstellingen over code die volgt op de ABI voor Windows in ARM:

  • Wanneer er een uitzondering optreedt in de hoofdtekst van een functie, kan de handler de bewerkingen van de proloog ongedaan maken of de bewerkingen van de epiloog op een voorwaartse manier uitvoeren. Beide moeten identieke resultaten opleveren.

  • Proloog en epiloog spiegelen elkaar meestal. Deze functie kan worden gebruikt om de grootte van de metagegevens te verkleinen die nodig zijn om te beschrijven hoe u zich kunt ontspannen.

  • Functies zijn meestal relatief klein. Verschillende optimalisaties zijn afhankelijk van deze observatie voor een efficiënte verpakking van gegevens.

  • Als een voorwaarde op een epiloog wordt geplaatst, wordt deze evenzeer toegepast op elke instructie in de epiloog.

  • Als de proloog de stackpointer (SP) in een ander register opslaat, moet dat register ongewijzigd blijven gedurende de functie, zodat de oorspronkelijke SP op elk gewenst moment kan worden hersteld.

  • Tenzij de SP wordt opgeslagen in een ander register, moet iedere manipulatie ervan strikt binnen de proloog en epiloog plaatsvinden.

  • Als u een stackframe wilt afwikkelen, zijn deze bewerkingen vereist:

    • Pas r13 (SP) in stappen van vier bytes aan.

    • Pop een of meer integerregisters.

    • Haal een of meer VFP-registers (virtuele floating-point) op.

    • Kopieer een willekeurige registerwaarde naar r13 (SP).

    • Laad SP uit de stack met behulp van een kleine bewerking na het verlagen.

    • Analyseert een van de goed gedefinieerde frametypes.

.pdata Bestanden

De .pdata records in een PE-indelingsafbeelding zijn een geordende matrix met items met vaste lengte die elke stack-manipulatiefunctie beschrijven. Bladfuncties (functies die geen andere functies aanroepen) vereisen geen .pdata records wanneer ze de stack niet bewerken. (Dit betekent dat ze geen lokale opslag nodig hebben en geen niet-vluchtige registers hoeven op te slaan of te herstellen.) Records voor deze functies kunnen worden weggelaten uit de .pdata sectie om ruimte te besparen. Een afwikkelbewerking van een van deze functies kan gewoon het retouradres van het koppelingsregister (LR) naar de programmateller (PC) kopiëren om naar de beller te gaan.

Elke .pdata record voor ARM is 8 bytes lang. De algemene indeling van een record plaatst het relatieve virtuele adres (RVA) van het begin van de functie in het eerste 32-bits woord, gevolgd door een tweede woord dat een aanwijzer bevat naar een blok met variabele lengte .xdata, of een verpakt woord dat een canonieke ontrafeling van de functie beschrijft, zoals weergegeven in deze tabel:

Woordverschuiving Bits Doel
0 0-31 Function Start RVA is de 32-bits RVA van het begin van de functie. Als de functie duimcode bevat, moet de lage bit van dit adres worden ingesteld.
1 0-1 Flag is een 2-bits veld dat aangeeft hoe de resterende 30 bits van het tweede .pdata woord moeten worden geïnterpreteerd. Als Flag 0 is, vormen de resterende bits een Exception Information RVA (met de lage twee bits impliciet 0). Als Flag niet nul is, vormen de resterende bits een Verpakt Unwind-gegevens structuur.
1 2-31 Uitzonderingsinformatie RVA of Ingepakte Afwikkelgegevens.

Uitzonderingsinformatie RVA is het adres van de informatiestructuur voor uitzonderingen met variabele lengte, opgeslagen in de .xdata sectie. Deze gegevens moeten 4 byte zijn uitgelijnd.

Packed Unwind Data is een gecomprimeerde beschrijving van de bewerkingen die nodig zijn om te ontspannen van een functie, uitgaande van een canonieke vorm. In dit geval is er geen .xdata record vereist.

Verpakte ontpakgegevens

Voor functies waarvan de proloog en epiloog de canonieke vorm volgen die hieronder wordt beschreven, kunnen verpakte unwind-gegevens worden gebruikt. Het elimineert de noodzaak van een .xdata record en vermindert aanzienlijk de ruimte die nodig is om de gegevens tot rust te brengen. De canonieke prolog en epiloog zijn ontworpen om te voldoen aan de algemene vereisten van een eenvoudige functie waarvoor geen uitzonderingshandler is vereist, en die de installatie- en afbouwbewerkingen in een standaardvolgorde uitvoert.

In deze tabel ziet u de indeling van een .pdata-record met verpakte unwind-gegevens:

Woordverschuiving Bits Doel
0 0-31 Function Start RVA is de 32-bits RVA van het begin van de functie. Als de functie duimcode bevat, moet de lage bit van dit adres worden ingesteld.
1 0-1 Flag is een 2-bits veld met deze betekenissen:

- 00 = verpakte afwikkelgegevens worden niet gebruikt; resterende bits verwijzen naar .xdata record.
- 01 = uitgepakte afwikkelgegevens.
- 10 = verpakte afwikkelgegevens waarbij men ervan uitgaat dat de functie geen proloog heeft. Dit is handig voor het beschrijven van functiefragmenten die niet overeenkomen met het begin van de functie.
- 11 = gereserveerd.
1 2-12 Function Length is een 11-bits veld dat de lengte van de hele functie in bytes gedeeld door 2 biedt. Als de functie groter is dan 4K bytes, moet in plaats daarvan een volledige .xdata record worden gebruikt.
1 13-14 Ret is een 2-bits veld dat aangeeft hoe de functie retourneert:

- 00 = retourneren via pop {pc} (de L vlagbit moet in dit geval op 1 worden ingesteld).
- 01 = retourneren met behulp van een 16-bits vertakking.
- 10 = terugkeren met behulp van een 32-bits vertakking.
- 11 = helemaal geen epiloog. Dit is handig voor het beschrijven van een niet aaneengesloten functiefragment dat mogelijk alleen een proloog bevat, maar waarvan de epiloog zich elders bevindt.
1 15 H is een 1-bits vlag die aangeeft of de functie de integer parameterregisters (r0-r3) opvraagt door ze aan het begin van de functie te pushen, en de 16 bytes van stack vrijgeeft voordat de functie wordt geretourneerd. (0 = geen huisregisters, 1 = huizenregisters.)
1 16-18 Reg is een 3-bits veld dat de index van het laatst opgeslagen niet-vluchtige register aangeeft. Als de R bit 0 is, worden alleen gehele getallen opgeslagen en wordt ervan uitgegaan dat deze zich in het bereik van r4-rN bevinden, waarbij N gelijk is aan 4 + Reg. Als de R bit 1 is, worden alleen drijvendekommaregisters opgeslagen en wordt ervan uitgegaan dat ze zich in het bereik van d8-dN bevinden, waarbij N gelijk is aan 8 + Reg. De speciale combinatie van R = 1 en Reg = 7 geeft aan dat er geen registers worden opgeslagen.
1 19 R is een 1-bits vlag die aangeeft of de opgeslagen niet-vluchtige registers integere registers (0) of drijvendekommaregisters (1) zijn. Als R is ingesteld op 1 en het Reg-veld is ingesteld op 7, wordt er geen niet-vluchtige registers gepusht.
1 20 L is een 1-bits vlag die aangeeft of de functie LR opslaat/herstelt, samen met andere registers die door het Reg veld worden aangegeven. (0 = slaat/herstel niet op, 1 = slaat/herstelt.)
1 21 C is een 1-bits vlag die aangeeft of de functie extra instructies bevat voor het instellen van een frameketen voor snelle stapelloop (1) of niet (0). Als dit bit is ingesteld, wordt r11 impliciet toegevoegd aan de lijst met niet-vluchtige registers die worden opgeslagen. (Zie de onderstaande beperkingen als de C vlag wordt gebruikt.)
1 22-31 Stack Adjust is een 10-bits veld dat het aantal bytes aan stack aangeeft dat is toegewezen voor deze functie, gedeeld door 4. Alleen waarden tussen 0x000-0x3F3 kunnen echter rechtstreeks worden gecodeerd. Functies die meer dan 4044 bytes aan stack toewijzen, moeten een volledige .xdata record gebruiken. Als het Stack Adjust veld 0x3F4 of groter is, hebben de lage 4 bits een speciale betekenis:

- Bits 0-1 geven het aantal woorden van stapelaanpassing (1-4) min 1 aan.
- Bit 2 is ingesteld op 1 als de proloog deze aanpassing in zijn push-bewerking heeft gecombineerd.
- Bit 3 is ingesteld op 1 als de epiloog deze aanpassing in zijn popbewerking heeft gecombineerd.

Vanwege mogelijke redundantie in de bovenstaande coderingen zijn deze beperkingen van toepassing:

  • Als de C vlag is ingesteld op 1:

    • De L vlag moet ook worden ingesteld op 1, omdat framekoppeling zowel r11 als LR vereist.

    • r11 mag niet worden opgenomen in de reeks registers die worden beschreven door Reg. Dat wil zeggen, als r4-r11 wordt gepusht, Reg alleen r4-r10 mag beschrijven, omdat de C vlag r11 impliceert.

  • Als het Ret veld is ingesteld op 0, moet de L vlag worden ingesteld op 1.

Als u deze beperkingen schendt, wordt een niet-ondersteunde reeks veroorzaakt.

In het kader van de onderstaande discussie worden twee pseudovlagmen afgeleid van Stack Adjust:

  • PF ofwel "proloog folding" geeft aan dat Stack Adjust 0x3F4 of groter is en bit 2 is gezet.

  • EF of "epiloog folding" geeft aan dat Stack Adjust 0x3F4 of groter is en dat bit 3 is ingesteld.

Proloog voor canonieke functies kan maximaal 5 instructies hebben (let op: 3a en 3b sluiten elkaar wederzijds uit):

Instructie Er wordt van uitgegaan dat de opcode aanwezig is als: Grootte Opcode Afwikkelcodes
1 H==1 16 push {r0-r3} 04
2 C==1 of L==1 of R==0 of PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3a C==1 en (R=1 en PF==0) 16 mov r11,sp Facebook
3b C==1 en (R==0 of PF==1) 32 add r11,sp,#xx FC
4 R==1 en Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 en PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

Instructie 1 is altijd aanwezig als de H bit is ingesteld op 1.

Voor het instellen van de framekoppeling is instructie 3a of 3b aanwezig als de C-bit is gezet. Het is een 16-bits mov als er geen andere registers dan r11 en LR worden gepusht; anders is het een 32-bits add.

Als een niet-gevouwen aanpassing is opgegeven, is instructie 5 de expliciete stapelaanpassing.

Instructies 2 en 4 worden ingesteld op basis van of een push vereist is. In deze tabel wordt samengevat welke registers worden opgeslagen op basis van de Cvelden , Len RPF velden. Is in alle gevallen N gelijk aan Reg + 4, E is gelijk aan Reg + 8 en S is gelijk aan (~Stack Adjust) & 3.

C L R PF Gehele getalregisters gepusht VFP-registers opgestapeld
0 0 0 0 r4 - r*N* Geen
0 0 0 1 r*S* - r*N* Geen
0 0 1 0 Geen d8 - d*E*
0 0 1 1 r*S* - r3 d8 - d*E*
0 1 0 0 r4 - r*N*, LR Geen
0 1 0 1 r*S* - r*N*, LR Geen
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (ongeldige codering) (ongeldige codering)
1 0 0 1 (ongeldige codering) (ongeldige codering)
1 0 1 0 (ongeldige codering) (ongeldige codering)
1 0 1 1 (ongeldige codering) (ongeldige codering)
1 1 0 0 r4 - r*N*, r11, LR Geen
1 1 0 1 r*S* - r*N*, r11, LR Geen
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

De epiloog voor canonieke functies volgt een vergelijkbare vorm, maar omgekeerd en met een aantal extra opties. De epiloog mag maximaal vijf instructies lang zijn en de vorm ervan wordt strikt bepaald door de vorm van de proloog.

Instructie Er wordt van uitgegaan dat de opcode aanwezig is als: Grootte Opcode
6 Stack Adjust!=0 en EF==0 16/32 add sp,sp,#xx
7 R==1 en Reg!=7 32 vpop {d8-dE}
8 C==1 of (L==1 en (H==0 of Ret !=0)) of R==0 of EF==1 16/32 pop {registers}
9a H==1 en (L==0 of Ret!=0) 16 add sp,sp,#0x10
9b H==1 en L==1 en Ret==0 32 ldr pc,[sp],#0x14
10a Ret==1 16 bx reg
10b Ret==2 32 b address

Instructie 6 is de expliciete stapelafstelling als een niet-samengevouwen afstelling is opgegeven. Omdat PF is onafhankelijk van EF, het is mogelijk om instructie 5 aanwezig te hebben zonder instructie 6, of omgekeerd.

Instructies 7 en 8 gebruiken dezelfde logica als de proloog om te bepalen welke registers van de stapel worden hersteld, maar met deze drie wijzigingen: ten eerste wordt EF gebruikt in plaats van PF; ten tweede, als Ret = 0 en H = 0, wordt LR vervangen door PC in de registerlijst en eindigt de epiloog onmiddellijk; ten derde, als Ret = 0 en H = 1, wordt EF weggelaten uit de registerlijst en uitgelezen door instructie 9b.

Als H is ingesteld, dan is instructie 9a of 9b aanwezig. Instructie 9a wordt gebruikt wanneer Ret niet-nul is, wat ook de aanwezigheid van 10a of 10b impliceert. Als L=1, werd LR gepopt als onderdeel van instructie 8. Instructie 9b wordt gebruikt wanneer L 1 en Ret nul is, om een vroeg einde aan de epiloog aan te geven en de stapel tegelijkertijd te retourneren en aan te passen.

Als de epiloog nog niet is beëindigd, is instructie 10a of 10b aanwezig om een 16-bits of 32-bits vertakking aan te geven op basis van de waarde van Ret.

.xdata Bestanden

Wanneer de gecomprimeerde terugdraaiindeling onvoldoende is om de terugdraaiing van een functie te beschrijven, moet er een record met variabele lengte .xdata worden gemaakt. Het adres van deze record wordt opgeslagen in het tweede woord van de .pdata record. De indeling van de .xdata is een ingepakte woordenreeks met variabele lengte met vier secties:

  1. Een koptekst van 1 of 2 woorden die de totale grootte van de .xdata structuur beschrijft en belangrijke functiegegevens biedt. Het tweede woord is alleen aanwezig als de velden Epiloogaantal en Codewoorden beide zijn ingesteld op 0. De velden zijn onderverdeeld in deze tabel:

    Woord Bits Doel
    0 0-17 Function Length is een 18-bits veld dat de totale lengte van de functie in bytes aangeeft, gedeeld door 2. Als een functie groter is dan 512 kB, moeten meerdere .pdata records .xdata worden gebruikt om de functie te beschrijven. Zie de sectie Grote functies in dit document voor meer informatie.
    0 18-19 Vers is een 2-bits veld dat de versie van de resterende.xdata versie beschrijft. Alleen versie 0 is momenteel gedefinieerd; waarden van 1-3 zijn gereserveerd.
    0 20 X is een 1-bits veld dat de aanwezigheid (1) of afwezigheid (0) van uitzonderingsgegevens aangeeft.
    0 21 E is een 1-bits veld dat aangeeft dat informatie die één epiloog beschrijft, in de koptekst (1) is verpakt in plaats van dat er later extra bereikwoorden nodig zijn (0).
    0 22 F is een 1-bits veld dat aangeeft dat deze record een functiefragment (1) of een volledige functie (0) beschrijft. Een fragment impliceert dat er geen proloog is en dat alle proloogverwerking moet worden genegeerd.
    0 23-27 Epiloogaantal is een 5-bits veld met twee betekenissen, afhankelijk van de status van de E bit:

    - Als E 0 is, geeft dit veld het totale aantal epiloogbereiken aan dat in sectie 2 wordt beschreven. Als er meer dan 31 scopes in de functie bestaan, moeten dit veld en het veld Codewoorden beide op 0 worden gezet om aan te geven dat een extensiewoord vereist is.
    - Als E 1 is, specificeert dit veld de index van de eerste afwikkelcode die de enige epiloog beschrijft.
    0 28-31 Codewoorden zijn een 4-bits veld dat het aantal 32-bits woorden aangeeft dat nodig is om alle afwikkelcodes in sectie 4 te bevatten. Als er meer dan 15 woorden nodig zijn voor meer dan 63 codebytes, moeten dit veld en het veld Epiloogaantal beide worden ingesteld op 0 om aan te geven dat een extensiewoord vereist is.
    1 0-15 Extended Epiloog Count is een 16-bits veld dat meer ruimte biedt voor het coderen van een ongebruikelijk groot aantal epiloogen. Het extensiewoord dat dit veld bevat, is alleen aanwezig als de velden Aantal epiloog en codewoorden in het eerste kopwoord beide zijn ingesteld op 0.
    1 16-23 Extended Code Words is een 8-bits veld dat meer ruimte biedt voor het encoderen van een ongebruikelijk groot aantal unwind codewoorden. Het extensiewoord dat dit veld bevat, is alleen aanwezig als de velden Aantal epiloog en codewoorden in het eerste kopwoord beide zijn ingesteld op 0.
    1 24-31 Gereserveerd
  2. Na de uitzonderingsgegevens (als de E-bit in de header is ingesteld op 0) volgt een lijst met informatie over epiloogbereiken, die per stuk in een woord worden verpakt en worden opgeslagen in volgorde van toenemende startoffset. Elk bereik bevat deze velden:

    Bits Doel
    0-17 Epiloog start offset is een 18-bits veld dat de verschuiving van de epiloog beschrijft, in bytes gedeeld door 2, ten opzichte van het begin van de functie.
    18-19 Res is een 2-bits veld dat is gereserveerd voor toekomstige uitbreiding. De waarde moet 0 zijn.
    20-23 Voorwaarde is een 4-bits veld dat de voorwaarde geeft waaronder de epiloog wordt uitgevoerd. Voor onvoorwaardelijke epiloog moet deze worden ingesteld op 0xE, wat 'altijd' aangeeft. (Een epiloog moet volledig voorwaardelijk of volledig onvoorwaardelijk zijn, en in de Thumb-2-modus begint de epiloog met de eerste instructie na de IT-opcode.)
    24-31 Epiloogstartindex is een 8-bits veld dat de byteindex aangeeft van de eerste afwikkelcode die deze epiloog beschrijft.
  3. Na de lijst met epiloogbereiken volgt een array van bytes die afwikkelcodes bevatten, welke gedetailleerd worden beschreven in de sectie Afwikkelcodes in dit artikel. Deze matrix wordt aan het einde van de dichtstbijzijnde volledige woordgrens opgevuld. De bytes worden opgeslagen in de little-endian-volgorde, zodat ze rechtstreeks kunnen worden opgehaald in de little-endian-modus.

  4. Als het X-veld in de koptekst gelijk is aan 1, worden de unwind codebytes gevolgd door de exception handler informatie. Dit bestaat uit één Uitzonderingshandler RVA die het adres van de uitzonderingshandler bevat, gevolgd door de hoeveelheid gegevens (variabele lengte) die door de uitzonderingshandler is vereist.

De .xdata record is zo ontworpen dat het mogelijk is om de eerste 8 bytes op te halen en de volledige grootte van de record te berekenen, niet inclusief de lengte van de uitzonderingsgegevens van variabele grootte die volgt. Met dit codefragment wordt de recordgrootte berekend:

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;
}

Hoewel de proloog en elke epiloog een index in de afwikkelcodes hebben, wordt de tabel ertussen gedeeld. Het is niet ongebruikelijk dat ze allemaal dezelfde afwikkelcodes kunnen delen. Het is raadzaam om compilerschrijvers te optimaliseren voor dit geval, omdat de grootste index die kan worden opgegeven 255 is en dat het totale aantal afwikkelcodes voor een bepaalde functie beperkt.

Afwikkelcodes

De matrix van afwikkelcodes is een groep instructiereeksen die precies beschrijven hoe de effecten van de proloog ongedaan moeten worden gemaakt, in de volgorde waarin de bewerkingen ongedaan moeten worden gemaakt. De afwikkelcodes zijn een mini-instructieset, gecodeerd als een reeks bytes. Wanneer de uitvoering is voltooid, bevindt het retouradres van de aanroepende functie zich in het LR-register en worden alle niet-vluchtige registers teruggezet naar hun waarden op het moment dat de functie werd aangeroepen.

Als uitzonderingen gegarandeerd alleen ooit binnen een functielichaam zouden plaatsvinden en nooit binnen een proloog of epiloog, dan zou slechts één afwikkelvolgorde nodig zijn. Het Windows-afwikkelmodel moet echter de mogelijkheid bieden om af te wikkelen vanuit een gedeeltelijk uitgevoerde proloog of epiloog. Om aan deze eis te voldoen, zijn de unwind-codes zorgvuldig ontworpen om een ondubbelzinnige een-op-een mapping te hebben voor elke relevante opcode in de proloog en epiloog. Dit heeft verschillende gevolgen:

  • Het is mogelijk om de lengte van de proloog en epiloog te berekenen door het aantal afwikkelcodes te tellen. Dit is mogelijk, zelfs met variabele-lengte Thumb-2 instructies, omdat er verschillende toewijzingen zijn voor 16-bits en 32-bits opcodes.

  • Door het aantal instructies na het begin van een epiloogbereik te tellen, is het mogelijk om het equivalente aantal afwikkelcodes over te slaan en de rest van een reeks uit te voeren om de gedeeltelijk uitgevoerde afwikkeling die de epiloog uitvoerde te voltooien.

  • Door het aantal instructies vóór het einde van de proloog te tellen, is het mogelijk om het equivalente aantal afwikkelcodes over te slaan en de rest van de reeks uit te voeren om alleen die delen van de proloog ongedaan te maken die de uitvoering hebben voltooid.

In de volgende tabel ziet u de toewijzing van unwind-codes naar opcodes. De meest voorkomende codes zijn slechts één byte, terwijl minder gangbare codes twee, drie of zelfs vier bytes vereisen. Elke code wordt opgeslagen van de belangrijkste byte naar de minst significante byte. De structuur van de afwikkelcode verschilt van de codering zoals beschreven in de ARM EABI, omdat deze afwikkelcodes zijn ontworpen om een een-op-een-toewijzing te hebben naar de opcodes in de proloog en epiloog. Dit maakt het mogelijk om gedeeltelijk uitgevoerde prologen en epilogen af te wikkelen.

Byte 1 Byte 2 Byte 3 Byte 4 Opsize Uitleg
00-7F 16 add sp,sp,#X

waarbij X is (Code & 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

waarbij LR wordt geladen bij Code & 0x2000 en r0-r12 worden geladen als de bijbehorende bit is ingesteld in Code & 0x1FFF
C0-CF 16 mov sp,rX

waarbij X Code &0x0F
D0-D7 16 pop {r4-rX,lr}

waarbij X is (Code & 0x03) + 4 en LR wordt weergegeven als Code & 0x04
D8-DF 32 pop {r4-rX,lr}

waar X (Code & 0x03) + 8 is en LR wordt gepopt als Code & 0x04
E0-E7 32 vpop {d8-dX}

waarbij X is (Code & 0x07) + 8
E8-EB 00-FF 32 addw sp,sp,#X

waarin X is (Code & 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

waarbij LR wordt gepopt als Code & 0x0100 en r0-r7 worden gepopt als het bijbehorende bit is gezet in Code & 0x00FF
EE 00-0F 16 Microsoft-specifiek
EE 10-FF 16 Beschikbaar
EF 00-0F 32 ldr lr,[sp],#X

waarbij X gelijk is aan (Code & 0x000F) * 4
EF 10-FF 32 Beschikbaar
F0-F4 - Beschikbaar
F5 00-FF 32 vpop {dS-dE}

waarbij S (Code & 0x00F0) >> 4 is en E Code & 0x000F
F6 00-FF 32 vpop {dS-dE}

waarbij S (Code & 0x00F0) 4) >> + 16 en E is (Code & 0x000F) + 16
F7 00-FF 00-FF 16 add sp,sp,#X

waarbij X is (Code & 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

waarbij X is (Code & 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

waarbij X is (Code & 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

waarbij X is (Code & 0x00FFFFFF) * 4
FB 16 nop (16-bits)
FC 32 nop (32-bits)
FD 16 end + 16-bit nop in epiloog
FE 32 end + 32-bit nop in epiloog
FF - einde

Dit toont het bereik van hexadecimale waarden voor elke byte in een afwikkelcodecode, samen met de opcodegrootte Opsize en de bijbehorende oorspronkelijke instructie-interpretatie. Lege cellen geven kortere afwikkelcodes aan. In instructies met grote waarden die betrekking hebben op meerdere bytes, worden de belangrijkste bits eerst opgeslagen. In het veld Opsize wordt de impliciete opcodegrootte weergegeven die is gekoppeld aan elke thumb-2-bewerking. De schijnbare dubbele vermeldingen in de tabel met verschillende coderingen worden gebruikt om onderscheid te maken tussen verschillende opcodegrootten.

De afwikkelcodes zijn zo ontworpen dat de eerste byte van de code zowel de totale grootte in bytes van de code als de grootte van de bijbehorende opcode in de instructiestroom aangeeft. Als u de grootte van de proloog of epiloog wilt berekenen, loopt u de unwind codes van het begin tot het einde van de reeks en gebruikt u een opzoektabel of een vergelijkbare methode om te bepalen hoe lang de bijbehorende opcode duurt.

Afwikkelcodes 0xFD en 0xFE zijn gelijk aan de reguliere eindcode 0xFF, maar houden rekening met één extra nop-opcode in het geval van de epiloog, van 16 of 32 bits. Voor proloog zijn codes 0xFD, 0xFE en 0xFF exact gelijk. Dit geldt voor de algemene epiloogbeëindigingen bx lr of b <tailcall-target>, die geen equivalente prolooginstructie hebben. Dit verhoogt de kans dat sequenties voor het ontrafelen kunnen worden gedeeld tussen de proloog en de epilogen.

In veel gevallen moet het mogelijk zijn om dezelfde set afwikkelcodes voor de proloog en alle epiloogcodes te gebruiken. Om echter om te gaan met het afwikkelen van gedeeltelijk uitgevoerde prologen en epilogen, moet u mogelijk meerdere afwikkelcodereeksen hebben die variëren in volgorde of gedrag. Daarom heeft elke epiloog een eigen index in de unwind-array om aan te duiden waar begonnen moet worden met uitvoeren.

Gedeeltelijke proloog en epiloog afwikkelen

De meest voorkomende afwikkelingssituatie is wanneer de exceptie optreedt in het hoofdgedeelte van de functie, buiten de proloog en alle epilogen. In dit geval voert de afwikkelfunctie de codes uit in de afwikkelmatrix vanaf index 0 en gaat verder totdat een end-opcode wordt gedetecteerd.

Wanneer er een uitzondering optreedt terwijl een proloog of epiloog wordt uitgevoerd, is het stackframe slechts gedeeltelijk opgebouwd en moet de afwikkelaar precies bepalen wat er is gedaan om het correct ongedaan te maken.

Denk bijvoorbeeld aan deze proloog- en epiloogvolgorde:

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

Naast elke opcode is de juiste afwikkelcode om deze bewerking te beschrijven. De reeks afwikkelcodes voor de proloog is een spiegelbeeld van de afwikkelcodes voor de epiloog, en telt niet de laatste instructie. Dit geval is gebruikelijk en het is de reden dat de unwind-codes voor de proloog altijd in omgekeerde volgorde van de uitvoeringsvolgorde van de proloog worden opgeslagen. Dit geeft ons een gemeenschappelijke set afwikkelcodes:

0xc7, 0xdd, 0x04, 0xfd

De 0xFD code is een speciale code voor het einde van de reeks, wat betekent dat de epiloog een 16-bits instructie langer is dan de proloog. Dit maakt een betere verdeling van afwikkelcodes mogelijk.

Als in het voorbeeld een uitzondering optreedt terwijl de functiebody tussen de proloog en epiloog wordt uitgevoerd, begint de afwikkeling met het epilooggeval, bij offset 0 binnen de epiloogcode. Dit komt overeen met offset 0x140 in het voorbeeld. De afwikkelaar voert de volledige afwikkelreeks uit, omdat er geen opschoonactie is uitgevoerd. Als in plaats daarvan de uitzondering één instructie na het begin van de epiloogcode optreedt, kan de unwinder met succes afwikkelen door de eerste afwikkelcode over te slaan. Gezien een een-op-een-toewijzing tussen opcodes en afwikkelcodes, dient, wanneer er vanaf instructie n in de epiloog wordt afgewikkeld, de eerste n afwikkelcodes te worden overgeslagen door de verwerker.

Vergelijkbare logica werkt omgekeerd voor de proloog. Als men vanuit offset 0 in de proloog afwikkelt, dan hoeft er niets te worden uitgevoerd. Als u van één instructie afwikkelt, moet de afwikkelvolgorde bij één afwikkelcode vanaf het einde beginnen, omdat proloog-afwikkelcodes in omgekeerde volgorde worden opgeslagen. In het algemene geval, als het afwikkelen van instructie n in de proloog plaatsvindt, moet het afwikkelen beginnen met het uitvoeren van n unwind-codes vanaf het einde van de lijst met codes.

Proloog- en epiloogontwindingscodes komen niet altijd exact overeen. In dat geval moet de ontkoppelcode-array mogelijk verschillende reeksen codes bevatten. Gebruik deze logica om de offset te bepalen om te beginnen met het verwerken van codes:

  1. Begin met het uitvoeren van afwikkelcodes vanuit de functie zelf, bij index 0, en ga door totdat een eind-opcode is bereikt.

  2. Als u vanuit een epiloog ontrafelt, gebruik dan de beginindex die specifiek voor epilogen door de epiloogsfeer wordt verstrekt. Bereken hoeveel bytes de pc is vanaf het begin van de epiloog. Sla door de afwikkelcodes door totdat alle reeds uitgevoerde instructies worden verwerkt. Voer de afwikkelvolgorde uit vanaf dat moment.

  3. Als u zich ontspant vanuit de proloog, begint u vanaf index 0 in de afwikkelcodes. Bereken de lengte van de proloogcode uit de reeks en bereken vervolgens hoeveel bytes de pc is vanaf het einde van de proloog. Doorloop de afwikkelcodes totdat alle niet-uitgevoerde instructies in aanmerking zijn genomen. Voer de afwikkelvolgorde uit vanaf dat moment.

De afwikkelcodes voor de proloog moeten altijd vooraan in de array staan. ze zijn ook de codes die worden gebruikt om te ontspannen in het algemeen geval van ontspannen vanuit het lichaam. Alle epiloogspecifieke codereeksen moeten direct na de proloogcodereeks worden gevolgd.

Functiefragmenten

Voor codeoptimalisatie kan het handig zijn om een functie te splitsen in niet-aaneengesloten onderdelen. Wanneer dit gebeurt, vereist elk functiefragment een eigen afzonderlijke .pdata, en mogelijk .xdata- record.

Ervan uitgaande dat de functieproloog zich aan het begin van de functie bevindt en niet kan worden gesplitst, zijn er vier functiefragmentgevallen:

  • Alleen proloog; alle epilogen voorkomen in andere fragmenten.

  • Proloog en een of meer epiloog; meer epiloog in andere fragmenten.

  • Geen proloog of epiloog; proloog en een of meer epiloog in andere fragmenten.

  • Alleen epiloog; proloog en mogelijk meer epiloog in andere fragmenten.

In het eerste geval moet alleen de proloog worden beschreven. Dit kan in compacte .pdata vorm worden gedaan door de proloog normaal te beschrijven en een Ret waarde van 3 op te geven om geen epiloog aan te geven. In de volledige .xdata-vorm kan dit worden gedaan door zoals gebruikelijk de proloog-afwikkelcodes op index 0 op te geven en een epiloogaantal van 0 te specificeren.

Het tweede geval is net als een normale functie. Als er slechts één epiloog in het fragment staat en het aan het einde van het fragment staat, kan een compacte .pdata record worden gebruikt. Anders moet een volledige .xdata record worden gebruikt. Houd er rekening mee dat de verschuivingen die zijn opgegeven voor het begin van de epiloog relatief zijn ten opzichte van het begin van het fragment, niet ten opzichte van het oorspronkelijke begin van de functie.

De derde en vierde gevallen zijn varianten van respectievelijk de eerste en tweede gevallen, behalve dat ze geen proloog bevatten. In deze situaties wordt ervan uitgegaan dat er code is vóór het begin van de epiloog en dat deze wordt beschouwd als onderdeel van het lichaam van de functie, wat normaal gesproken ongedaan zou worden gemaakt door het effect van de proloog te herstellen. Deze gevallen moeten daarom worden gecodeerd met een pseudoproloog, waarin wordt beschreven hoe je vanuit de body kunt afwikkelen, maar die als 0-lengte wordt beschouwd bij het bepalen of een gedeeltelijke afwikkeling aan het begin van het fragment moet worden uitgevoerd. Deze pseudoproloog kan ook worden beschreven met behulp van dezelfde afwikkelcodes als de epiloog, omdat ze vermoedelijk gelijkwaardige bewerkingen uitvoeren.

In de derde en vierde gevallen wordt de aanwezigheid van een pseudoproloog opgegeven door het Flag veld van de compacte .pdata record in te stellen op 2 of door de F-vlag in de .xdata koptekst in te stellen op 1. In beide gevallen wordt de controle op een gedeeltelijke proloog-unwind genegeerd, en alle niet-epiloog-unwinds worden als volledig beschouwd.

Grote functies

Fragmenten kunnen worden gebruikt om functies te beschrijven die groter zijn dan de limiet van 512 kB die wordt opgelegd door de bitvelden in de .xdata header. Als u een grotere functie wilt beschrijven, kunt u deze opsplitsen in fragmenten die kleiner zijn dan 512 kB. Elk fragment moet worden aangepast, zodat er geen epiloog in meerdere stukken wordt gesplitst.

Alleen het eerste fragment van de functie bevat een proloog. Alle andere fragmenten worden gemarkeerd als geen proloog. Afhankelijk van het aantal epiloogen kan elk fragment nul of meer epiloog bevatten. Houd er rekening mee dat elk epiloogbereik in een fragment de beginafstand aangeeft ten opzichte van het begin van het fragment, niet het begin van de functie.

Als een fragment geen proloog en geen epiloog heeft, heeft het nog steeds zijn eigen .pdata- en mogelijk .xdata-record nodig om te beschrijven hoe uit het lichaam van de functie los te maken.

Krimpverpakken

Een complexer speciaal geval van functiefragmenten wordt shrink-wrapping genoemd. Het is een techniek voor het uitstellen van het opslaan van de registerstatus vanaf het begin van de functie tot later in de functie. Het optimaliseert voor eenvoudige gevallen waarvoor het opslaan van het register niet nodig is. Dit geval heeft twee delen: er is een buitenste gebied dat de stackruimte toewijst en een minimale set registers opslaat, en een binnenste gebied dat andere registers opslaat en herstelt.

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:

In shrink-wrap verpakte functies wordt doorgaans verwacht dat zij de ruimte voor de extra registers vooraf toewijzen in de reguliere proloog en de registers vervolgens opslaan met str of stm in plaats van push. Met deze actie blijven alle stack-pointermanipulaties behouden in de oorspronkelijke proloog van de functie.

De verpakte voorbeeldfunctie moet worden onderverdeeld in drie regio's, die zijn gemarkeerd als A, B en C zoals opgemerkt in de opmerkingen. Het eerste A gebied omvat het begin van de functie tot het einde van de extra niet-vluchtige opslagacties. Een .pdata of .xdata record moet worden samengesteld om dit fragment te beschrijven als een proloog en geen epiloog.

De middelste B regio krijgt een eigen .pdata of .xdata record die een fragment beschrijft dat geen proloog en geen epiloog heeft. De afwikkelcodes voor dit gebied moeten echter nog steeds aanwezig zijn omdat het wordt beschouwd als een functieblok. De codes moeten een samengestelde proloog beschrijven die zowel de oorspronkelijke registers vertegenwoordigt die zijn opgeslagen in de regioproloog A als de extra registers die zijn opgeslagen vóór het invoeren van de regio B, alsof ze door één reeks bewerkingen zijn geproduceerd.

Het register dat voor regio B wordt opgeslagen kan niet worden beschouwd als een "interne proloog" omdat de samengestelde proloog, die voor regio B wordt beschreven, zowel de proloog van regio A als de aanvullende opgeslagen registers moet beschrijven. Als fragment B een proloog had, zou de afwikkelcode ook de implicatie van de grootte van die proloog omvatten, en er is geen manier om de samengestelde proloog te beschrijven die direct overeenkomt met de opcodes die alleen de extra registers opslaan.

De extra registeropslagingen moeten als een deel van regio A worden beschouwd, omdat totdat ze compleet zijn, de samengestelde proloog niet nauwkeurig de staat van de stapel beschrijft.

De laatste C regio krijgt een eigen .pdata- of .xdata-record, waarin een fragment wordt beschreven dat geen proloog, maar wel een epiloog heeft.

Een alternatieve benadering kan ook werken als het terugbrengen van de stackbewerking tot één instructie voordat de regio B wordt ingevoerd mogelijk is.

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

Het belangrijkste inzicht is dat de stack op elke instructiegrens volledig overeenkomt met de afwikkelcodes voor de regio. Als een ontwinding optreedt voordat de binnenste push in dit voorbeeld plaatsvindt, wordt het beschouwd als onderdeel van regio A. Alleen de regioproloog A is onwikkeld. Als de rust na de binnenste duw plaatsvindt, wordt het beschouwd als een deel van de regio B, die geen proloog heeft. Het heeft echter afwikkelcodes die zowel de innerlijke push als de oorspronkelijke proloog uit regio A beschrijven. Vergelijkbare logica geldt voor de binnenste pop.

Optimalisaties voor codering

De rijkdom van de unwind codes en de mogelijkheid om gebruik te maken van compacte en uitgebreide vormen van gegevens bieden veel kansen om de codering te optimaliseren om de ruimte nog verder te verminderen. Met intensief gebruik van deze technieken kan de nettooverhead van het beschrijven van functies en delen met behulp van afwikkelcodes worden geminimaliseerd.

Het belangrijkste optimalisatie-idee: verwar de grenzen van proloog en epiloog ten behoeve van het afwikkelen niet met de logische grenzen van proloog en epiloog vanuit het perspectief van de compiler. De afwikkelgrenzen kunnen worden verkleind en strakker gemaakt om de efficiëntie te verbeteren. Een proloog kan bijvoorbeeld code bevatten nadat de stack is ingesteld om verificatiecontroles uit te voeren. Maar zodra alle stackbewerkingen zijn voltooid, hoeft u geen verdere bewerkingen te coderen en alles wat verder kan worden verwijderd uit de afwikkelende proloog.

Dezelfde regel is van toepassing op de functielengte. Als er gegevens zijn (zoals een letterlijke gegevenspool) die een naspel in een functie volgen, mogen deze niet worden opgenomen als onderdeel van de lengte van de functie. Door de functie te verkleinen tot alleen de code die deel uitmaakt van de functie, is de kans veel groter dat de epiloog zich aan het eind bevindt en een compacte .pdata record kan worden gebruikt.

Zodra de stackpointer in een proloog is opgeslagen in een ander register, hoeft men doorgaans geen verdere opcodes bij te houden. Om de functie af te wikkelen, is het eerste wat gedaan wordt SP terug te halen uit het opgeslagen register. Verdere bewerkingen hebben geen effect op het ontrollen.

Epilogen met één instructie hoeven helemaal niet te worden gecodeerd, noch als scopes, noch als afwikkelcodes. Als een unwind plaatsvindt voordat die instructie wordt uitgevoerd, is het veilig aan te nemen dat het gebeurt binnen de functie. Alleen het uitvoeren van de proloog afwikkelcodes is voldoende. Wanneer de ontwinding plaatsvindt nadat de enkel instructie is uitgevoerd, vindt deze per definitie plaats in een andere regio.

Meerinstructie-epilogen hoeven de eerste instructie van de epiloog niet te coderen, om dezelfde reden als het vorige punt: als het ontwindproces plaatsvindt voordat die instructie wordt uitgevoerd, is een volledige proloogontwinding voldoende. Als de ontwinding na die instructie plaatsvindt, moeten alleen de latere operaties in overweging worden genomen.

Opnieuw gebruiken van code moet agressief zijn. De index voor elk epiloogbereik geeft punten aan op een willekeurig beginpunt in de matrix van afwikkelcodes. Het hoeft niet naar het begin van een eerdere reeks te verwijzen; het kan in het midden wijzen. De beste methode is om de codereeks tot rust te laten komen. Scan vervolgens op een exacte byte-overeenkomst in de al gecodeerde pool met reeksen. Gebruik elke perfecte match als uitgangspunt voor hergebruik.

Nadat epilogen met één instructie zijn genegeerd en er geen resterende epilogen zijn, kunt u overwegen een compacte .pdata-vorm te gebruiken; de kans hierop is veel groter bij afwezigheid van een epiloog.

Voorbeelden

In deze voorbeelden bevindt het imagebestand zich op 0x00400000.

Voorbeeld 1: Bladfunctie, geen lokale variabelen

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x000535F8 (= 0x004535F8-0x00400000)
  • Word 1

    • Flag = 1, waarmee canonieke proloog- en epiloognotaties worden aangegeven

    • Function Length = 0x31 (= 0x62/2)

    • Ret = 1, dat een 16-bit brancheterugkeer aangeeft

    • H = 0, wat aangeeft dat de parameters niet geconfigureerd zijn

    • R = 0 en Reg = 1, wat aangeeft push/pop van r4-r5

    • L = 0, waarmee wordt aangegeven dat er geen LR-opslag/herstelbewerking is

    • C = 0, wat aangeeft dat er geen kaderkoppeling is

    • Stack Adjust = 0, wat aangeeft dat er geen stapelaanpassing is

Voorbeeld 2: Geneste functie met lokale toewijzing

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 (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x000533AC (= 0x004533AC -0x00400000)
  • Word 1

    • Flag = 1, waarmee canonieke proloog- en epiloognotaties worden aangegeven

    • Function Length = 0x35 (= 0x6A/2)

    • Ret = 0, wat impliceert dat een pop {pc} terugkeert

    • H = 0, wat aangeeft dat de parameters niet geconfigureerd zijn

    • R = 0 en Reg = 3, wat aangeeft push/pop van r4-r7

    • L = 1, waarmee wordt aangegeven dat LR is opgeslagen/hersteld

    • C = 0, wat aangeeft dat er geen kaderkoppeling is

    • Stack Adjust = 3 (= 0x0C/4)

Voorbeeld 3: Geneste Variadic-functie

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 (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x00053988 (= 0x00453988-0x00400000)
  • Word 1

    • Flag = 1, waarmee canonieke proloog- en epiloognotaties worden aangegeven

    • Function Length = 0x2A (= 0x54/2)

    • Ret = 0, wat een pop {pc}-stijl terugkeer betekent (in dit geval een ldr pc,[sp],#0x14 terugkeer)

    • H = 1, waarmee wordt aangegeven dat de parameters zijn gekalibreerd

    • R = 0 en Reg = 2, wat aangeeft push/pop van r4-r6

    • L = 1, waarmee wordt aangegeven dat LR is opgeslagen/hersteld

    • C = 0, wat aangeeft dat er geen kaderkoppeling is

    • Stack Adjust = 0, wat aangeeft dat er geen stapelaanpassing is

Voorbeeld 4: Functie met meerdere epiloog

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 (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x000592F4 (= 0x004592F4-0x00400000)
  • Word 1

    • Flag = 0, dat aangeeft dat het record aanwezig is .xdata (vereist voor meerdere epilogen)

    • .xdata adres - 0x00400000

.xdata (variabele, 6 woorden):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, waarmee de eerste versie van.xdata wordt aangegeven

    • X = 0, waarmee geen uitzonderingsgegevens worden aangegeven

    • E = 0, waarmee een lijst met epiloogbereiken wordt aangegeven

    • F = 0, waarmee een volledige functiebeschrijving wordt aangegeven, inclusief proloog

    • Epilogue Count = 0x04, waarmee de 4 totale epiloogbereiken worden aangegeven

    • Code Words = 0x01, waarmee één 32-bits woord van afwikkelcodes wordt aangegeven

  • Woorden 1-4 die 4 epiloogbereiken beschrijven op 4 locaties. Elk bereik heeft een gemeenschappelijke set afwikkelcodes, gedeeld met de proloog, bij offset 0x00, en is onvoorwaardelijk met specificatie van voorwaarde 0x0E (altijd).

  • Ontwikkel codes, beginnend bij Word 5: (gedeeld tussen proloog/epiloog)

    • Ontwikkelcode 0 = 0x06: sp += (6 << 2)

    • Ontwinden code 1 = 0xDE: pop {r4-r10, lr}

    • Herstelcode 2 = 0xFF: einde

Voorbeeld 5: Functie met dynamische stack en interne epiloog

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 (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x00085A20 (= 0x00485A20-0x00400000)
  • Word 1

    • Flag = 0, wat de aanwezigheid van een record aangeeft (nodig voor meerdere epilogen) .xdata

    • .xdata adres - 0x00400000

.xdata (variabele, 3 woorden):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, waarmee de eerste versie van.xdata wordt aangegeven

    • X = 0, waarmee geen uitzonderingsgegevens worden aangegeven

    • E = 0, waarmee een lijst met epiloogbereiken wordt aangegeven

    • F = 0, waarmee een volledige functiebeschrijving wordt aangegeven, inclusief proloog

    • Epilogue Count = 0x001, wat het totale epiloogbereik van 1 aangeeft

    • Code Words = 0x01, waarmee één 32-bits woord van afwikkelcodes wordt aangegeven

  • Word 1: Epiloogbereik op offset 0xC6 (= 0x18C/2), beginnende bij de unwind-code-index 0x00 en met een voorwaarde van 0x0E (altijd)

  • Ontwikkel codes, beginnend bij Word 2: (gedeeld tussen proloog/epiloog)

    • Kom code 0 = 0xC6: sp = r6

    • Kom code 1 = 0xDC: pop {r4-r8, lr}

    • Ontwikkelcode 2 = 0x04: sp += (4 << 2)

    • Afwikkelingscode 3 = 0xFD: einde, telt als 16-bit instructie voor epiloogcode

Voorbeeld 6: Functie met een uitzonderingsverwerker

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 (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x00088C24 (= 0x00488C24-0x00400000)
  • Word 1

    • Flag = 0, wat de aanwezigheid van een record aangeeft (nodig voor meerdere epilogen) .xdata

    • .xdata adres - 0x00400000

.xdata (variabele, 5 woorden):

  • Word 0

    • Function Length =0x000027 (= 0x00004E/2)

    • Vers = 0, waarmee de eerste versie van.xdata wordt aangegeven

    • X = 1, waarmee uitzonderingsgegevens worden aangegeven

    • E = 1, waarmee één epiloog wordt aangegeven

    • F = 0, waarmee een volledige functiebeschrijving wordt aangegeven, inclusief proloog

    • Epilogue Count = 0x00, waarmee wordt aangegeven dat de epiloog ontrolcodes beginnen bij offset 0x00

    • Code Words = 0x02, waarmee twee 32-bits woorden van afwikkelcodes worden aangegeven

  • Ontwikkel codes, te beginnen bij Word 1:

    • Afwikkelcode 0 = 0xC7: sp = r7

    • Ontrafelcode 1 = 0x05: sp += (5 << 2)

    • Terugdraaicode 2 = 0xED/0x90: pop {r4, r7, lr}

    • Ontwikkel code 4 = 0xFF: end

  • In Word 3 wordt een uitzonderingshandler opgegeven = 0x0019A7ED (= 0x0059A7ED - 0x00400000)

  • Woorden 4 en hoger zijn inline-uitzonderingsgegevens

Voorbeeld 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 (vast, 2 woorden):

  • Word 0

    • Function Start RVA = 0x00088C72 (= 0x00488C72-0x00400000)
  • Word 1

    • Flag = 1, waarmee canonieke proloog- en epiloognotaties worden aangegeven

    • Function Length = 0x0B (= 0x16/2)

    • Ret = 0, wat impliceert dat een pop {pc} terugkeert

    • H = 0, wat aangeeft dat de parameters niet geconfigureerd zijn

    • R = 0 en Reg = 7, wat aangeeft dat er geen registers zijn opgeslagen/hersteld

    • L = 1, waarmee wordt aangegeven dat LR is opgeslagen/hersteld

    • C = 0, wat aangeeft dat er geen kaderkoppeling is

    • Stack Adjust = 1, waarmee een 1 × 4 bytestackaanpassing wordt aangegeven

Zie ook

Overzicht van ARM ABI-conventies
Veelvoorkomende problemen met arm-migratie van Visual C++