Dela via


Ö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.

  1. NGRN (Next General-purpose Register Number) är inställt på noll.

  2. NSRN (Nästa SIMD- och flyttalsregisternummer) är satt till noll.

  3. 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.

  1. 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).

  2. Om argumenttypen är en HFA eller en HVA används argumentet omodifierat.

  3. 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.

  4. 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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. Om argumentet har en justering på 16 avrundas NGRN upp till nästa jämna tal.

  9. 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.

  10. 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.

  11. NGRN är inställt på 8.

  12. NSAA avrundas upp till det större av 8 eller den naturliga justeringen av argumentets typ.

  13. Om argumentet är en sammansatt typ kopieras argumentet till minnet vid den justerade NSAA:n. NSAA ökas med argumentets storlek. Argumentet har nu allokerats.

  14. 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.

  15. 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:

  1. Alla kompositer behandlas likadant; ingen särskild behandling av HFA eller HVA.

  2. 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