Översikt över ARM64 ABI-konventioner
Det grundläggande binärt programgränssnittet (ABI) för Windows när det kompileras och körs på ARM-processorer i 64-bitarsläge (ARMv8 eller senare arkitekturer) följer för det mesta ARM:s standard-AArch64 EABI. Den här artikeln belyser några av de viktigaste antagandena och ändringarna från det som dokumenteras i EABI. Information om 32-bitars ABI finns i Översikt över ARM ABI-konventioner. För mer information om den standardiserade ARM EABI, se Application Binary Interface (ABI) för ARM-arkitekturen (extern länk).
Definitioner
Med introduktionen av 64-bitarsstöd har ARM definierat flera termer:
- AArch32 – den äldre isa-arkitekturen (32-bitars instruktionsuppsättning) som definierats av ARM, inklusive körning av tumläge.
- AArch64 – den nya isa-arkitekturen (64-bitars instruktionsuppsättning) som definierats av ARM.
- ARMv7 – specifikationen av "7:e generationens" ARM-maskinvara, som endast innehåller stöd för AArch32. Den här versionen av ARM-maskinvaran är den första versionen av Windows för ARM som stöds.
- ARMv8 – specifikationen av "8:e generationens" ARM-maskinvara, som innehåller stöd för både AArch32 och AArch64.
Windows använder också följande termer:
- ARM- – refererar till 32-bitars ARM-arkitekturen (AArch32), som ibland kallas WoA (Windows på ARM).
- ARM32 – samma som ARM, ovan; används i det här dokumentet för tydlighetens skull.
- ARM64 – refererar till 64-bitars ARM-arkitekturen (AArch64). Det finns inget sådant som WoA64.
När du refererar till datatyper refereras slutligen följande definitioner från ARM:
- Short-Vector – en datatyp som kan representeras direkt i SIMD, en vektor bestående av antingen 8 eller 16 byte element. Den är justerad efter dess storlek, antingen 8 byte eller 16 byte, där varje element kan vara 1, 2, 4 eller 8 byte.
- HFA (Homogen flyttalaggregat) – en datatyp med 2 till 4 identiska flyttalsmedlemmar, antingen flytande eller dubblar.
- HVA (Homogen Short-Vector Aggregate) – en datatyp med 2 till 4 identiska Short-Vector medlemmar.
Grundläggande krav
ARM64-versionen av Windows förutsätter att den körs på en ARMv8- eller senare arkitektur hela tiden. Både flyttals- och NEON-stöd antas finnas i maskinvara.
ARMv8-specifikationen beskriver nya frivilliga krypto- och CRC-hjälpkoder för både AArch32 och AArch64. Stöd för dem är för närvarande valfritt, men rekommenderas. För att dra nytta av dessa opcodes bör appar först göra kontroller vid körning för deras existens.
Byteordning
Precis som med ARM32-versionen av Windows körs Windows på ARM64 i little-endian-läge. Det är svårt att växla endianitet utan stöd för kernelläge i AArch64, så det är enklare att tillämpa.
Justering
Windows som körs på ARM64 gör att processormaskinvaran kan hantera feljusterade åtkomster transparent. I en förbättring från AArch32 fungerar det här stödet nu även för alla heltalsåtkomster (inklusive åtkomst med flera ord) och för flyttalsåtkomster.
Åtkomst till otillgängligt minne (enhet) måste dock alltid justeras. Om kod eventuellt kan läsa eller skriva feljusterade data från oåtkomligt minne måste den se till att justera alla åtkomster.
Standardlayoutjustering för lokalbefolkningen:
Storlek i byte | Justering i byte |
---|---|
1 | 1 |
2 | 2 |
3, 4 | 4 |
> 4 | 8 |
Standardlayoutjustering för globala och statiska variabler.
Storlek i byte | Justering av byte |
---|---|
1 | 1 |
2 - 7 | 4 |
8 - 63 | 8 |
>= 64 | 16 |
Heltalsregister
AArch64-arkitekturen stöder 32 heltalsregister:
Registrera dig | Flyktighet | Roll |
---|---|---|
x0-x8 | Flyktig | Scratch-register för parametrar/resultat |
x9-x15 | Flyktig | Scratchregister |
x16-x17 | Flyktig | Anropsregistreringar inom proceduren |
x18 | Ej tillämpligt | Reserverat plattformsregister: i kernelläge pekar på KPCR för den aktuella processorn; I användarläge pekar på TEB |
x19-x28 | Icke-flyktig | Scratchregister |
x29/fp | Icke-flyktig | Bildrutepekare |
x30/lr | Båda | Länkregister: Callee-funktionen måste bevara den för sin egen retur, men anroparens värde går förlorat. |
Varje register kan nås som ett fullständigt 64-bitarsvärde (via x0-x30) eller som ett 32-bitarsvärde (via w0-w30). 32-bitars operationer nollfyller sina resultat upp till 64 bitar.
Mer information om användningen av parameterregister finns i avsnittet Parameteröverföring.
Till skillnad från AArch32 är programräknaren (PC) och stackpekaren (SP) inte indexerade register. De är begränsade i hur de kan nås. Observera också att det inte finns något x31-register. Den kodningen används för särskilda ändamål.
Ramarpekaren (x29) krävs för kompatibilitet med snabb stackvandring, vilket används av ETW och andra tjänster. Det måste peka på det tidigare {x29 x30}-paret i stacken.
Flyttal/SIMD-register
AArch64-arkitekturen stöder även 32 flyttals-/SIMD-register, som sammanfattas nedan:
Registrera dig | Flyktighet | Roll |
---|---|---|
v0-v7 | Flyktig | Scratch-register för parametrar/resultat |
v8-v15 | Båda | Lägre 64 bitar är icke-flyktiga. Höga 64 bitar är flyktiga. |
v16-v31 | Flyktig | Scratchregisters |
Varje register kan nås som ett fullständigt 128-bitarsvärde (via v0-v31 eller q0-q31). Det kan nås som ett 64-bitarsvärde (via d0-d31), som ett 32-bitarsvärde (via s0-s31), som ett 16-bitarsvärde (via h0-h31) eller som ett 8-bitars värde (via b0-b31). Åtkomst som är mindre än 128 bitar åtkommer bara de lägre bitarna i hela 128-bitarsregistret. De lämnar de återstående bitarna orörda om inget annat anges. (AArch64 skiljer sig från AArch32, där de mindre registren packades ovanpå de större registren.)
FPCR (Floating Point Control Register) har vissa krav på de olika bitfälten i det:
Bitar | Betydelse | Flyktighet | Roll |
---|---|---|---|
26 | AHP | Icke-flyktig | Alternativ halvprecisionskontroll. |
25 | Dagens Nyheter | Icke-flyktig | Standardkontroll för NaN-läge. |
24 | FZ | Icke-flyktig | Kontroll av tömning till nollläge. |
23-22 | RMode | Icke-flyktig | Avrundningslägeskontroll. |
15,12-8 | IDE/IXE/etc | Icke-flyktig | Undantagsfälla aktiverar bitar, måste alltid vara 0. |
Systemregister
Liksom AArch32 innehåller AArch64-specifikationen tre systemstyrda "tråd-ID"-register:
Registrera dig | Roll |
---|---|
TPIDR_EL0 | Reserverad. |
TPIDRRO_EL0 | Innehåller CPU-nummer för den aktuella processorn. |
TPIDR_EL1 | Pekar på KPCR-strukturen för den aktuella processorn. |
Flyttalsundantag
Stöd för IEEE-flyttalsundantag på AArch64-system är valfritt. Detta kan verifieras genom att skriva ett värde som möjliggör undantag i FPCR
-registret och sedan läsa tillbaka det. De bitar som motsvarar undantag som stöds förblir inställda, medan de bitar som motsvarar undantag som inte stöds återställs av processorn.
Parameteröverföring
För icke-variadiska funktioner följer Windows ABI de regler som anges av ARM för parameteröverföring. Dessa regler hämtas direkt från Procedure Call Standard för AArch64-arkitekturen:
Fas A – Initiering
Det här steget görs exakt en gång innan bearbetningen av argumenten börjar.
NGRN (Next General-purpose Register Number) är inställt på noll.
NSRN (Nästa SIMD- och flyttalsregisternummer) är satt till noll.
Nästa staplade argumentadress (NSAA) ställs in på det aktuella stackpekarvärdet (SP).
Steg B – Förutfyllnad och förlängning av argument
För varje argument i listan tillämpas den första matchande regeln från följande lista. Om ingen regel matchar används argumentet omodifierat.
Om argumenttypen är en sammansatt typ vars storlek inte kan bestämmas statiskt av både anroparen och den som blir anropad, kopieras argumentet till minnet och argumentet ersätts med en pekare till kopian. (Det finns inga sådana typer i C/C++ men de finns på andra språk eller i språktillägg).
Om argumenttypen är en HFA eller en HVA används argumentet omodifierat.
Om argumenttypen är en sammansatt typ som är större än 16 byte kopieras argumentet till det minne som allokeras av anroparen och argumentet ersätts med en pekare till kopian.
Om argumenttypen är en sammansatt typ avrundas argumentets storlek upp till närmaste multipel på 8 byte.
Steg C – Tilldelning av argument till register och stack
För varje argument i listan tillämpas följande regler i tur och ordning tills argumentet har allokerats. När ett argument tilldelas till ett register har alla oanvända bitar i registret ospecificerat värde. Om ett argument tilldelas till ett stackfack har alla oanvända utfyllnadsbyte ospecificerat värde.
Om argumentet är en flyttal med halv, enkel, dubbel eller quad-precision eller kort vektortyp, och NSRN är mindre än 8, allokeras argumentet till de minst betydande bitarna av register v[NSRN]. NSRN ökas med ett. Argumentet har nu allokerats.
Om argumentet är en HFA eller en HVA, och det finns tillräckligt med oallokerade SIMD- och flyttalsregister (NSRN + antal medlemmar ≤ 8), allokeras argumentet till SIMD- och flyttalregister, ett register per medlem i HFA eller HVA. NSRN ökas med antalet register som används. Argumentet har nu allokerats.
Om argumentet är en HFA eller en HVA är NSRN inställt på 8 och storleken på argumentet avrundas upp till närmaste multipel på 8 byte.
Om argumentet är en HFA, en HVA, ett flyttal med fyrdubbel precision eller en kort vektortyp, avrundas NSAA upp till den större av 8 eller den Naturliga Justeringen av argumentets typ.
Om argumentet är ett flyttal av typen halvprecision eller enkelprecision, sätts argumentets storlek till 8 byte. Effekten är som om argumentet hade kopierats till de minst betydande bitarna i ett 64-bitarsregister och de återstående bitarna fyllda med ospecificerade värden.
Om argumentet är en HFA, en HVA, en halv-, enkel-, dubbel- eller kvadruppelprecision flyttal eller kort vektortyp, kopieras argumentet till minnet vid den justerade NSAA. NSAA ökas med argumentets storlek. Argumentet har nu allokerats.
Om argumentet är en integral- eller pekartyp är argumentets storlek mindre än eller lika med 8 byte och NGRN är mindre än 8, kopieras argumentet till de minst signifikanta bitarna i x[NGRN]. NGRN ökas med ett. Argumentet har nu allokerats.
Om argumentet har en justering på 16 avrundas NGRN upp till nästa jämna tal.
Om argumentet är en integraltyp är argumentets storlek lika med 16 och NGRN är mindre än 7, kopieras argumentet till x[NGRN] och x[NGRN+1]. x[NGRN] ska innehålla det lägre adresserade dubbelordet i argumentets minnesrepresentation. NGRN ökas med två. Argumentet har nu allokerats.
Om argumentet är en sammansatt typ, och storleken i dubbla ord i argumentet inte är mer än 8 minus NGRN, kopieras argumentet till på varandra följande allmänna register, med början vid x[NGRN]. Argumentet skickas som om det hade lästs in i registren från en dubbelordsjusterad adress, med en lämplig sekvens med LDR-instruktioner som läser in på varandra följande register från minnet. Innehållet i oanvända delar av registren är ospecificerat enligt denna standard. NGRN ökas med antalet register som används. Argumentet har nu allokerats.
NGRN är inställt på 8.
NSAA avrundas upp till det större av 8 eller den naturliga justeringen av argumentets typ.
Om argumentet är en sammansatt typ kopieras argumentet till minnet vid den justerade NSAA:n. NSAA ökas med argumentets storlek. Argumentet har nu allokerats.
Om argumentets storlek är mindre än 8 byte anges argumentets storlek till 8 byte. Effekten är som om argumentet kopierades till de minst betydande bitarna i ett 64-bitarsregister och de återstående bitarna fylldes med ospecificerade värden.
Argumentet kopieras till minnet vid den justerade NSAA:n. NSAA ökas med argumentets storlek. Argumentet har nu allokerats.
Tillägg: Variadic-funktioner
Funktioner som tar ett variabelt antal argument hanteras på ett annat sätt än ovan, enligt följande:
Alla kompositer behandlas likadant; ingen särskild behandling av HFA eller HVA.
SIMD- och flyttalsregister används inte.
I själva verket är det samma som följande regler C.12–C.15 för att allokera argument till en imaginär stack, där de första 64 bytena av stacken läses in i x0-x7 och eventuella återstående stackargument placeras normalt.
Returnera värden
Integralvärden returneras som x0.
Flyttalsvärden returneras i s0, d0 eller v0 efter behov.
En typ anses vara en HFA eller HVA om alla följande kriterier gäller:
- Den är inte tom.
- Den har inga icke-triviala standard- eller kopieringskonstruktorer, destruktorer eller tilldelningsoperatorer,
- Alla dess medlemmar har samma HFA- eller HVA-typ, eller är flyt-, dubbel- eller neontyper som matchar de andra medlemmarnas HFA- eller HVA-typer.
HVA-värden med fyra eller färre element returneras i s0-s3, d0-d3 eller v0-v3 efter behov.
Typer som returneras av värde hanteras på olika sätt beroende på om de har vissa egenskaper och om funktionen är en icke-statisk medlemsfunktion. Typer som har alla dessa egenskaper,
- de är aggregerade av C++14-standarddefinitionen, dvs. de har inga konstruktorer från användaren, inga privata eller skyddade icke-statiska datamedlemmar, inga basklasser och inga virtuella funktioner, och
- de har en trivial kopieringstilldelningsoperator och
- de har en trivial destruktor
och returneras av icke-medlemsfunktioner eller statiska medlemsfunktioner ska du använda följande returformat:
- Typer som är HFA:er med fyra eller färre element returneras i s0-s3, d0-d3 eller v0-v3 efter behov.
- Typer som är mindre än eller lika med 8 byte returneras i x0.
- Typer som är mindre än eller lika med 16 byte returneras i x0 och x1, med x0 som innehåller 8 byte i lägre ordning.
- För andra aggregeringstyper ska anroparen reservera ett minnesblock av tillräcklig storlek och justering för att hålla resultatet. Adressen till minnesblocket ska skickas som ytterligare ett argument till funktionen i x8. Anroparen kan ändra minnesblocket för resultat när som helst under körningen av underrutinen. Den som blir anropad behöver inte bevara värdet som lagras i x8.
Alla andra typer använder den här konventionen:
- Anroparen måste reservera ett minnesblock med tillräcklig storlek och anpassning för att rymma resultatet. Adressen för minnesblocket ska skickas som ytterligare ett argument till funktionen i x0 eller x1 om $this skickas i x0. Anroparen kan ändra minnesblocket för resultat när som helst under körningen av underrutinen. Den uppringde returnerar adressen till minnesblocket i x0.
Stack
Enligt ABI som lagts fram av ARM måste stacken alltid vara 16-byte alignerad. AArch64 innehåller en maskinvarufunktion som genererar stackjusteringsfel när SP inte är 16 byte justerat och en SP-relativ belastning eller lagring utförs. Windows körs med den här funktionen aktiverad hela tiden.
Funktioner som allokerar 4k eller mer av stackutrymme måste se till att varje sida före den sista sidan berörs i ordningsföljd. Den här åtgärden säkerställer att ingen kod kan "hoppa över" de skyddssidor som Windows använder för att expandera stacken. Vanligtvis utförs beröringen av __chkstk
-hjälparen, som har en anpassad anropskonvention som hanterar den totala stackallokeringen dividerad med 16 i x15.
Röd zon
Området med 16 byte direkt under den aktuella stackpekaren är reserverat för användning av analys- och dynamiska korrigeringsscenarier. Det här området tillåter att noggrant genererad kod infogas som lagrar två register på [sp, #-16] och tillfälligt använder dem för godtyckliga ändamål. Windows-kärnan garanterar att dessa 16 byte inte skrivs över om ett undantag eller avbrott inträffar, i både användar- och kernelläge.
Kernelstack
Standardkärnlägesstacken i Windows är sex sidor (24k). Var extra uppmärksam på funktioner med stora stackbuffertar i kernelläge. Ett olämpligt tajmat avbrott kan komma in utan mycket spelrum och orsaka en stackpanikfelkontroll.
Stackgång
Kod i Windows kompileras med bildrutepekare aktiverade (/Oy-) för att aktivera snabb stackvandring. I allmänhet pekar x29 (fp) på nästa länk i kedjan, som är ett {fp, lr} par, som anger pekaren till föregående bildruta i stacken och returadressen. Kod från tredje part uppmuntras även att aktivera bildrutepekare för att möjliggöra förbättrad profilering och spårning.
Undantagsdewinding
När du varvar ned under undantagshanteringen får du hjälp med att varva ned koder. Avlindningskoderna är en bytesekvens som lagras i avsnittet .xdata i den körbara filen. De beskriver verkningen av prologen och epilogen på ett abstrakt sätt, så att effekterna av en funktions prolog kan återställas som förberedelse för att återgå till anroparens stackram. Mer information om varva ned-koderna finns i ARM64-undantagshantering.
ARM EABI anger också en undantagsmodell som använder avspolningskoder. Specifikationen som presenteras är dock otillräcklig för att varva ned i Windows, som måste hantera fall där datorn är mitt i en funktionsprolog eller epilog.
Kod som genereras dynamiskt bör beskrivas med dynamiska funktionstabeller via RtlAddFunctionTable
och associerade funktioner, så att den genererade koden kan delta i undantagshantering.
Cykelräknare
Alla ARMv8-processorer krävs för att stödja ett cykelräknareregister, ett 64-bitarsregister som Windows konfigurerar för att vara läsbart på alla undantagsnivå, inklusive användarläge. Den kan nås via det särskilda PMCCNTR_EL0 registret, med hjälp av MSR-opcode i sammansättningskoden eller _ReadStatusReg
inbyggd i C/C++-kod.
Cykelräknaren här är en sann cykelräknare, inte en väggklocka. Beräkningsfrekvensen varierar med processorfrekvensen. Om du känner att du måste känna till cykelräknarens frekvens bör du inte använda cykelräknaren. I stället vill du mäta klocktiden i väggen, som du bör använda QueryPerformanceCounter
.
Se även
vanliga problem med migrering av Visual C++ ARM
ARM64-undantagshantering