Condividi tramite


Panoramica delle convenzioni ABI arm32

L'interfaccia applicativa binaria (ABI, Application Binary Interface) per il codice compilato per Windows su processori ARM si basa sull'interfaccia EABI ARM standard. Questo articolo evidenzia le principali differenze tra Windows su ARM e lo standard. Questo documento illustra l'ABI ARM32. Per informazioni sull'ABI ARM64, vedere Panoramica delle convenzioni ABI arm64. Per altre informazioni sullo standard ARM EABI, vedere Application Binary Interface (ABI) per l'architettura arm (collegamento esterno).

Requisiti di base

Windows in ARM presuppone sempre che sia in esecuzione in un'architettura ARMv7. Il supporto della virgola mobile nel formato VFPv3-D32 o successivo deve essere presente nell'hardware. VFP deve supportare la virgola mobile sia a precisione singola che a precisione doppia nell'hardware. Windows Runtime non supporta l'emulazione di virgola mobile per abilitare l'esecuzione su hardware non VFP.

Il supporto avanzato delle estensioni SIMD (NEON), incluse le operazioni a virgola mobile e integer, deve essere presente anche nell'hardware. Non viene offerto supporto in fase di esecuzione per l'emulazione.

Il supporto per le divisioni intere (UDIV/SDIV) è consigliato ma non obbligatorio. Le piattaforme prive del supporto per la divisione dei numeri interi possono subire una riduzione delle prestazioni perché queste operazioni devono essere intercettate ed eventualmente corrette.

Ordine dei byte

Windows su ARM viene eseguito in modalità little endian. Sia il compilatore MSVC che Windows Runtime prevedono sempre dati little-endian. L'istruzione SETEND nell'architettura del set di istruzioni ARM (ISA) consente anche al codice in modalità utente di modificare l'endianness corrente. Tuttavia, farlo è sconsigliato perché è pericoloso per un'applicazione. Se viene generata un'eccezione in modalità big-endian, il comportamento è imprevedibile. Potrebbe verificarsi un errore dell'applicazione in modalità utente o un controllo di bug in modalità kernel.

Allineamento

Anche se Windows consente all'hardware ARM di gestire in modo trasparente gli accessi ai numeri interi disallineati, in alcune situazioni possono tuttavia essere generati errori di allineamento. Seguire queste regole per l'allineamento:

  • Non è necessario allineare i caricamenti e gli archivi di interi di dimensioni medie (a 16 bit) e di dimensioni delle parole (a 32 bit). L'hardware li gestisce in modo efficiente e trasparente.

  • I carichi e gli archivi a virgola mobile devono essere allineati. Il kernel gestisce i carichi e gli archivi non allineati in modo trasparente, ma con un considerevole sovraccarico.

  • Le operazioni di caricamento o archiviazione doppie (LDRD/STRD) e multiple (LDM/STM) devono essere allineate. Il kernel ne gestisce la maggior parte in modo trasparente, ma con un considerevole sovraccarico.

  • Tutti gli accessi alla memoria non cache devono essere allineati, anche per gli accessi ai numeri interi. Gli accessi non allineati causano un errore di allineamento.

Set di istruzioni

Il set di istruzioni per Windows su ARM è limitato esclusivamente a Thumb-2. Tutto il codice eseguito in questa piattaforma dovrebbe essere avviato e rimane sempre in modalità Thumb. Un tentativo di passare al set di istruzioni ARM legacy può avere esito positivo. Tuttavia, in caso affermativo, eventuali eccezioni o interrupt che si verificano possono causare un errore dell'applicazione in modalità utente o un controllo di bug in modalità kernel.

Un effetto collaterale di questo requisito è che per tutti i puntatori di codice deve essere impostata una velocità in bit bassa. Quindi, quando vengono caricati e diramati in tramite BLX o BX, il processore rimane in modalità Thumb. Non tenta di eseguire il codice di destinazione come istruzioni arm a 32 bit.

Istruzioni SDIV/UDIV

L'uso delle istruzioni di divisione dei numeri interi SDIV e UDIV è completamente supportato, anche su piattaforme senza hardware nativo per gestirle. Il sovraccarico aggiuntivo per SDIV o UDIV diviso in un processore Cortex-A9 è di circa 80 cicli. Questo valore viene aggiunto al tempo di divisione complessivo di 20-250 cicli, a seconda degli input.

Registri interi

Il processore ARM supporta registri con 16 Integer:

Registrazione Volatile? Ruolo
r0 Volatile Parametro, risultato, registro temporaneo 1
r1 Volatile Parametro, risultato, registro temporaneo 2
r2 Volatile Parametro, risultato, registro temporaneo 3
r3 Volatile Parametro, risultato, registro temporaneo 4
r4 Non volatile
r5 Non volatile
r6 Non volatile
r7 Non volatile
r8 Non volatile
r9 Non volatile
r10 Non volatile
r11 Non volatile Puntatori ai frame
r12 Volatile Registro temporaneo delle chiamate di procedura interne
r13 (SP) Non volatile Puntatore dello stack
r14 (LR) Non volatile Registro dei collegamenti
r15 (PC) Non volatile Contatore di programma

Per i dettagli sull'uso del parametro e dei registri dei valori restituiti, vedere la sezione Passaggio dei parametri in questo articolo.

Windows usa r11 per analizzare rapidamente lo stack frame. Per altre informazioni, vedere la sezione Analisi dello stack. A causa di questo requisito, r11 deve sempre puntare al collegamento più in alto nella catena. Non usare r11 per scopi generali, perché il codice non genererà procedure di stack corrette durante l'analisi.

Registri VFP

Windows supporta solo varianti ARM con supporto per il coprocessore VFPv3-D32. Significa che i registri a virgola mobile sono sempre presenti e possono essere basati sul passaggio dei parametri. E, il set completo di 32 registri è disponibile per l'uso. I registri VFP e il loro utilizzo sono riepilogati in questa tabella:

Singoli Doppi Quadrupli Volatile? Ruolo
s0-s3 d0-d1 q0 Volatile Parametri, risultato, registro temporaneo
s4-s7 d2-d3 q1 Volatile Parametri, registro temporaneo
s8-s11 d4-d5 q2 Volatile Parametri, registro temporaneo
s12-s15 d6-d7 q3 Volatile Parametri, registro temporaneo
s16-s19 d8-d9 q4 Non volatile
s20-s23 d10-d11 q5 Non volatile
s24-s27 d12-d13 q6 Non volatile
s28-s31 d14-d15 q7 Non volatile
d16-d31 q8-q15 Volatile

La tabella successiva illustra i campi di bit FPSCR (Floating-Point Status and Control Register):

BITS significato Volatile? Ruolo
31-28 NZCV Volatile Flag di stato
27 QC Volatile Saturazione cumulativa
26 AHP Non volatile Controllo di mezza precisione alternativa
25 DN Non volatile Controllo modalità NaN predefinito
24 FZ Non volatile Controllo modalità scaricamento fino ad azzeramento
23-22 RMode Non volatile Controllo modalità di arrotondamento
21-20 Stride Non volatile Stride vettore, deve sempre essere 0
18-16 Len Non volatile Lunghezza vettore, deve sempre essere 0
15, 12-8 IDE, IXE e così via Non volatile Bit abilitazione trap eccezioni, deve sempre essere 0
7, 4-0 IDC, IXC e così via Volatile Flag di eccezione cumulativi

Eccezioni a virgola mobile

La maggior parte dell'hardware ARM non supporta eccezioni a virgola mobile IEEE. Nelle varianti del processore in cui si verificano eccezioni a virgola mobile dell'hardware, il kernel Windows intercetta le eccezioni senza avvisare e le disabilita in modo implicito nel registro FPSCR. Questa azione garantisce il comportamento normalizzato tra le varianti del processore. In caso contrario, il codice sviluppato in una piattaforma che non dispone del supporto delle eccezioni potrebbe ricevere eccezioni impreviste quando è in esecuzione in una piattaforma che dispone del supporto delle eccezioni.

Passaggio dei parametri

Windows in ARM ABI segue le regole arm per il passaggio dei parametri per le funzioni non variadic. Le regole ABI includono le estensioni VFP e ADVANCED SIMD. Queste regole seguono procedure Call Standard per l'architettura arm, combinate con le estensioni VFP. Per impostazione predefinita, i primi quattro argomenti integer e fino a otto argomenti a virgola mobile o vettoriale vengono passati nei registri. Tutti gli argomenti aggiuntivi vengono passati nello stack. Gli argomenti vengono assegnati ai registri o allo stack con questa procedura:

Fase A: Inizializzazione

L'inizializzazione viene eseguita una sola volta, prima dell'elaborazione degli argomenti:

  1. Il numero NCRN (Next Core Register Number) viene impostato su r0.

  2. I registri VFP vengono contrassegnati come non allocati.

  3. L'indirizzo NSAA (Next Stacked Argument Address) viene impostato sull'SP corrente.

  4. Se viene chiamata una funzione che restituisce un risultato in memoria, l'indirizzo per il risultato viene inserito in r0 e il numero NCRN viene impostato su r1.

Fase B: Riempimento preliminare ed estensione degli argomenti

Per ogni argomento nell'elenco, viene applicata la prima regola corrispondente dell'elenco seguente:

  1. Se l'argomento è un tipo composito la cui dimensione non può essere determinata in modo statico sia dal chiamante che dal computer chiamato, l'argomento viene copiato in memoria e sostituito da un puntatore alla copia.

  2. Se l'argomento è un byte o una half word a 16 bit, viene esteso in base a zero o con segno a una full word a 32 bit e trattato come argomento a 4 byte.

  3. Se l'argomento è un tipo composito, la dimensione viene arrotondata per eccesso al più vicino multiplo di 4.

Fase C: assegnazione di argomenti per la registrazione e lo stack

Per ogni argomento nell'elenco, vengono applicate una alla volta le regole seguenti finché l'argomento non viene allocato:

  1. Se l'argomento è un tipo VFP e ci sono abbastanza registri VFP non allocati consecutivi del tipo appropriato, l'argomento viene allocato alla sequenza con la numerazione più bassa di questi registri.

  2. Se l'argomento è un tipo VFP, tutti i rimanenti registri non allocati vengono contrassegnati come non disponibili. L'indirizzo NSAA viene adeguato verso l'alto finché non viene correttamente allineato per il tipo di argomento e l'argomento viene copiato nello stack in corrispondenza dell'indirizzo NSAA adeguato. L'indirizzo NSAA viene quindi incrementato della dimensione dell'argomento.

  3. Se l'argomento richiede l'allineamento a 8 byte, il numero NCRN viene arrotondato per eccesso al successivo numero di registro pari.

  4. Se la dimensione dell'argomento in word a 32 bit non è superiore a r4 meno NCRN, l'argomento viene copiato nei registri principali, a partire dal numero NCRN, con i bit più significativi che occupano i registri con la numerazione più bassa. Il numero NCRN viene incrementato del numero di registri usati.

  5. Se il numero NCRN è minore di r4 e l'indirizzo NSAA è uguale all'SP, l'argomento viene suddiviso tra i registri principali e lo stack. La prima parte dell'argomento viene copiata nei registri principali, a partire dal numero NCRN, fino a includere r3. Il resto dell'argomento viene copiato nello stack, a partire dall'NSAA. Il numero NCRN viene impostato su r4 e l'indirizzo NSAA viene incrementato della dimensione dell'argomento meno la quantità passata nei registri.

  6. Se l'argomento richiede l'allineamento a 8 byte, l'indirizzo NSAA viene arrotondato per eccesso al successivo indirizzo allineato a 8 byte.

  7. L'argomento viene copiato in memoria in corrispondenza dell'indirizzo NSAA. L'indirizzo NSAA viene incrementato della dimensione dell'argomento.

I registri VFP non vengono usati per le funzioni variadic e le regole 1 e 2 della fase C vengono ignorate. Significa che una funzione variadic può iniziare con un push facoltativo {r0-r3} per anteporre gli argomenti del registro a qualsiasi argomento aggiuntivo passato dal chiamante e quindi accedere all'intero elenco di argomenti direttamente dallo stack.

I valori di tipo Integer vengono restituiti in r0 e facoltativamente estesi a r1 per i valori restituiti a 64 bit. I valori di tipo SIMD o a virgola mobile VFP/NEON vengono restituiti in s0, d0 o q0, come appropriato.

Stack

Lo stack deve sempre rimanere allineato a 4 byte e deve essere allineato a 8 byte in corrispondenza di qualsiasi limite di funzione. È necessario supportare l'uso frequente di operazioni interlocked nelle variabili dello stack a 64 bit. L'interfaccia EABI ARM indica che lo stack è allineato a 8 byte in tutte le interfacce pubbliche. Per coerenza, l'interfaccia ABI di Windows su ARM considera i limiti delle funzioni un'interfaccia pubblica.

Le funzioni che devono usare un puntatore a un frame, ad esempio le funzioni che chiamano alloca o che cambiano il puntatore dello stack in modo dinamico, devono impostare il puntatore al frame in r11 nel prologo della funzione e lasciarlo invariato fino all'epilogo. Le funzioni che non richiedono un puntatore a fotogrammi devono eseguire tutti gli aggiornamenti dello stack nel prologo e lasciare invariato il puntatore dello stack fino all'epilogo.

Le funzioni che allocano 4 KB o più nello stack devono verificare che ogni pagina prima di quella finale sia elaborata in ordine. Questo ordine garantisce che nessun codice possa "superare" le pagine di protezione usate da Windows per espandere lo stack. In genere, l'espansione viene eseguita dall'helper __chkstk , che viene passata l'allocazione totale dello stack in byte diviso per 4 in r4 e che restituisce la quantità di allocazione dello stack finale in byte in r4.

Zona rossa

L'area a 8 byte immediatamente sotto il puntatore dello stack corrente è riservata all'analisi e all'applicazione di patch dinamica. Consente l'inserimento di codice generato con attenzione, che archivia 2 registri in [sp, #-8] e li usa temporaneamente per scopi arbitrari. Il kernel di Windows garantisce che questi 8 byte non vengano sovrascritti se si verifica un'eccezione o un interrupt sia in modalità utente che in modalità kernel.

Stack del kernel

Lo stack in modalità kernel predefinito in Windows è di tre pagine (12 KB). Prestare attenzione a non creare funzioni con grandi buffer di stack in modalità kernel. Potrebbe verificarsi un interrupt con una capacità aggiuntiva dello stack molto ridotta e causare un controllo errori gravi dello stack.

Specifiche di C/C++

Le enumerazioni sono tipi Integer a 32 bit a meno che almeno un valore nell'enumerazione non richieda memoria double word a 64 bit. In questo caso, il livello dell'enumerazione viene alzato a un tipo Integer a 64 bit.

wchar_t è definito come equivalente a unsigned short, per mantenere la compatibilità con le altre piattaforme.

Camminare in pila

Il codice Di Windows viene compilato con puntatori a fotogrammi abilitati (/Oy (omissione del puntatore a fotogrammi) per abilitare l'esecuzione rapida dello stack. In genere, il registro r11 punta al collegamento successivo nella catena, che è una coppia {r11, lr} che specifica il puntatore al frame precedente nello stack e l'indirizzo restituito. È consigliabile che anche il codice abiliti i puntatori ai frame per una profilatura e un'analisi migliori.

Rimozione delle eccezioni

Lo svuotamento dello stack durante la gestione delle eccezioni è abilitato dall'uso di codici di rimozione. I codici di rimozione sono sequenze di byte memorizzate nella sezione .xdata dell'immagine eseguibile. Descrivono l'operazione del prologo della funzione e del codice dell'epilogo in modo astratto, in modo che gli effetti del prologo di una funzione possano essere annullati in preparazione alla rimozione dello stack frame del chiamante.

L'interfaccia EABI ARM specifica un modello di rimozione delle eccezioni che usa codici di rimozione. Tuttavia, questa specifica non è sufficiente per la rimozione in Windows, che deve gestire i casi in cui il processore si trova al centro del prologo o dell'epilogo di una funzione. Per altre informazioni su Windows sui dati delle eccezioni arm e sulla rimozione, vedere Gestione delle eccezioni arm.

È consigliabile che il codice generato in modo dinamico venga descritto con tabelle di funzioni dinamiche nelle Chiamate a RtlAddFunctionTable e le funzioni associate, in modo che il codice generato possa partecipare alla gestione delle eccezioni.

Contatore ciclo

Per supportare un contatore di cicli, sono necessari processori ARM che eseguono Windows, ma l'uso diretto del contatore può causare problemi. Per evitare questi problemi, Windows su ARM usa un codice operativo non definito per richiedere un valore del contatore di cicli a 64 bit normalizzato. Da C o C++ usare l'intrinseco __rdpmccntr64 per creare il codice operativo appropriato. Dall'assembly usare l'istruzione __rdpmccntr64. La lettura del contatore di cicli richiede circa 60 cicli su Cortex-A9.

Il contatore è un vero e proprio contatore di cicli, non un orologio, e quindi la frequenza del conteggio varia con la frequenza del processore. Per misurare il tempo trascorso, usare QueryPerformanceCounter.

Vedi anche

Problemi comuni relativi alla migrazione di Visual C++ ARM
Gestione delle eccezioni ARM