Panoramica delle convenzioni ABI di ARM64EC
ARM64EC è un'interfaccia ABI (Application Binary Interface) che consente l'esecuzione dei file binari ARM64 in modo nativo e interoperabile con codice x64. In particolare, il ARM64EC ABI segue convenzioni software x64, tra cui convenzione di chiamata, utilizzo dello stack e allineamento dei dati, rendendo ARM64EC e x64 codice interoperabile. Il sistema operativo emula la parte x64 del file binario. (L'EC in ARM64EC è l'acronimo di emulation compatible.)
Per altre informazioni sulle convenzioni ABI x64 e ARM64, vedere Panoramica delle convenzioni ABI x64 e Panoramica delle convenzioni ABI ARM64.
ARM64EC non risolve le differenze tra le architetture basate su ARM e x64. Per altre informazioni, vedere Problemi comuni di migrazione arm di Visual C++.
Definizioni
- ARM64 : flusso di codice per i processi ARM64 che contiene il codice ARM64 tradizionale.
- ARM64EC: flusso di codice che usa un subset del set di registri ARM64 per garantire l'interoperabilità con il codice x64.
Registrare il mapping
I processi x64 possono avere thread che eseguono ARM64EC codice. È quindi sempre possibile recuperare un contesto di registro x64, ARM64EC usa un subset dei registri di base ARM64 che eseguono il mapping 1:1 ai registri x64 emulati. Importante, ARM64EC non usa mai registri all'esterno di questo subset, tranne per leggere l'indirizzo TEB (Thread Environment Block) da x18
.
I processi ARM64 nativi non devono regredire le prestazioni quando alcune o più funzioni vengono ricompilate come ARM64EC. Per mantenere le prestazioni, l'ABI segue questi principi:
Il subset di registrazione ARM64EC include tutti i registri che fanno parte della convenzione di chiamata di funzione ARM64.
La convenzione di chiamata ARM64EC esegue direttamente il mapping alla convenzione di chiamata ARM64.
Routine helper speciali come __chkstk_arm64ec
l'uso di convenzioni di chiamata personalizzate e registri. Questi registri sono inclusi anche nel sottoinsieme di registri ARM64EC.
Registrare il mapping per i registri integer
ARM64EC registro | Registro x64 | convenzione di chiamata ARM64EC | Convenzione di chiamata ARM64 | Convenzione di chiamata x64 |
---|---|---|---|---|
x0 |
rcx |
volatile | volatile | volatile |
x1 |
rdx |
volatile | volatile | volatile |
x2 |
r8 |
volatile | volatile | volatile |
x3 |
r9 |
volatile | volatile | volatile |
x4 |
r10 |
volatile | volatile | volatile |
x5 |
r11 |
volatile | volatile | volatile |
x6 |
mm1 (basso 64 bit di registro x87 R1 ) |
volatile | volatile | volatile |
x7 |
mm2 (basso 64 bit di registro x87 R2 ) |
volatile | volatile | volatile |
x8 |
rax |
volatile | volatile | volatile |
x9 |
mm3 (basso 64 bit di registro x87 R3 ) |
volatile | volatile | volatile |
x10 |
mm4 (basso 64 bit di registro x87 R4 ) |
volatile | volatile | volatile |
x11 |
mm5 (basso 64 bit di registro x87 R5 ) |
volatile | volatile | volatile |
x12 |
mm6 (basso 64 bit di registro x87 R6 ) |
volatile | volatile | volatile |
x13 |
N/D | Consentita | volatile | N/D |
x14 |
N/D | Consentita | volatile | N/D |
x15 |
mm7 (basso 64 bit di registro x87 R7 ) |
volatile | volatile | volatile |
x16 |
Alto 16 bit di ognuno dei registri x87 R0 -R3 |
volatile(xip0 ) |
volatile(xip0 ) |
volatile |
x17 |
Alto 16 bit di ognuno dei registri x87 R4 -R7 |
volatile(xip1 ) |
volatile(xip1 ) |
volatile |
x18 |
GS.base | fixed(TEB) | fixed(TEB) | fixed(TEB) |
x19 |
r12 |
non volatile | non volatile | non volatile |
x20 |
r13 |
non volatile | non volatile | non volatile |
x21 |
r14 |
non volatile | non volatile | non volatile |
x22 |
r15 |
non volatile | non volatile | non volatile |
x23 |
N/D | Consentita | non volatile | N/D |
x24 |
N/D | Consentita | non volatile | N/D |
x25 |
rsi |
non volatile | non volatile | non volatile |
x26 |
rdi |
non volatile | non volatile | non volatile |
x27 |
rbx |
non volatile | non volatile | non volatile |
x28 |
N/D | Consentita | Consentita | N/D |
fp |
rbp |
non volatile | non volatile | non volatile |
lr |
mm0 (basso 64 bit di registro x87 R0 ) |
Entrambi | Entrambi | Entrambi |
sp |
rsp |
non volatile | non volatile | non volatile |
pc |
rip |
puntatore all'istruzione | puntatore all'istruzione | puntatore all'istruzione |
PSTATE subset: N Z SS /C //V /1, 2 |
RFLAGS sottoinsieme: SF /ZF /CF /OF /TF |
volatile | volatile | volatile |
N/D | RFLAGS sottoinsieme: PF /AF |
N/D | N/D | volatile |
N/D | RFLAGS sottoinsieme: DF |
N/D | N/D | non volatile |
1 Evitare di leggere, scrivere o calcolare direttamente i mapping tra PSTATE
e RFLAGS
. Questi bit possono essere usati in futuro e sono soggetti a modifiche.
2 La bandiera C
porta ARM64EC è l'inverso della bandiera CF
di trasporto x64 per le operazioni di sottrazione. Non esiste una gestione speciale, perché il flag è volatile e pertanto viene cestinato durante la transizione tra le funzioni (ARM64EC e x64).
Registrare il mapping per i registri vettoriali
ARM64EC registro | Registro x64 | convenzione di chiamata ARM64EC | Convenzione di chiamata ARM64 | Convenzione di chiamata x64 |
---|---|---|---|---|
v0 -v5 |
xmm0 -xmm5 |
volatile | volatile | volatile |
v6 -v7 |
xmm6 -xmm7 |
volatile | volatile | non volatile |
v8 -v15 |
xmm8 -xmm15 |
volatile 1 | volatile 1 | non volatile |
v16 -v31 |
xmm16 -xmm31 |
Consentita | volatile | non consentito (l'emulatore x64 non supporta AVX-512) |
FPCR 2 |
MXCSR[15:6] |
non volatile | non volatile | non volatile |
FPSR 2 |
MXCSR[5:0] |
volatile | volatile | volatile |
1 Questi registri ARM64 sono speciali in quanto i 64 bit inferiori sono non volatili, ma i 64 bit superiori sono volatili. Dal punto di vista di un chiamante x64, sono effettivamente volatili perché il chiamato avrebbe spazzatura i dati.
2 Evitare di leggere, scrivere o calcolare direttamente mapping di FPCR
e FPSR
. Questi bit possono essere usati in futuro e sono soggetti a modifiche.
Compressione degli struct
ARM64EC segue le stesse regole di compressione degli struct usate per x64 per garantire l'interoperabilità tra codice ARM64EC e codice x64. Per altre informazioni ed esempi di compressione degli struct x64, vedere Panoramica delle convenzioni ABI x64.
Routine ABI dell'helper di emulazione
ARM64EC codice e i python usano routine helper di emulazione per passare tra le funzioni x64 e ARM64EC.
La tabella seguente descrive ogni routine ABI speciale e registra gli usi di ABI. Le routine non modificano i registri conservati elencati nella colonna ABI. Non devono essere fatte ipotesi sui registri non elencati. Su disco, i puntatori di routine ABI sono Null. In fase di caricamento, il caricatore aggiorna i puntatori in modo che puntino alle routine dell'emulatore x64.
Nome | Descrizione | ABI |
---|---|---|
__os_arm64x_dispatch_call_no_redirect |
Chiamato da un threadk di uscita per chiamare una destinazione x64 (una funzione x64 o una sequenza di inoltro rapido x64). La routine esegue il push dell'indirizzo restituito ARM64EC (nel LR registro) seguito dall'indirizzo dell'istruzione che ha esito positivo su un'istruzione blr x16 che richiama l'emulatore x64. Esegue quindi l'istruzione blr x16 |
valore restituito in x8 (rax ) |
__os_arm64x_dispatch_ret |
Chiamato da un entry thunk per tornare al suo chiamante x64. Viene visualizzato l'indirizzo restituito x64 dallo stack e richiama l'emulatore x64 per passare a esso | N/D |
__os_arm64x_check_call |
Chiamato da ARM64EC codice con un puntatore a un thread di uscita e l'indirizzo di destinazione ARM64EC indiretto da eseguire. La destinazione ARM64EC è considerata patchable e l'esecuzione torna sempre al chiamante con gli stessi dati con cui è stato chiamato o con dati modificati | Argomenti:x9 : indirizzo di destinazionex10 : l'indirizzo dell'uscita del threadkx11 : l'indirizzo della sequenza di inoltro rapidoCambio: x9 : se la funzione di destinazione è stata deviazionata, contiene l'indirizzo della sequenza di inoltro rapidox10 : l'indirizzo dell'uscita del threadkx11 : se la funzione è stata deviazionata, contiene l'indirizzo di uscita del threadk. In caso contrario, l'indirizzo di destinazione è saltato aRegistri conservati: x0 -x8 , x15 ().chkstk e q0 -q7 |
__os_arm64x_check_icall |
Chiamato da ARM64EC codice, con un puntatore a un thread di uscita, per gestire un passaggio a un indirizzo di destinazione x64 o ARM64EC. Se la destinazione è x64 e il codice x64 non è stato sottoposto a patch, la routine imposta il registro degli indirizzi di destinazione. Punta alla versione ARM64EC della funzione, se presente. In caso contrario, imposta il registro in modo che punti al thread di uscita che passa alla destinazione x64. Viene quindi restituito al codice ARM64EC chiamante, che quindi passa all'indirizzo nel registro. Questa routine è una versione non ottimizzata di , in cui l'indirizzo di destinazione non è noto in fase di __os_arm64x_check_call compilazioneUsato in un sito di chiamata di una chiamata indiretta |
Argomenti:x9 : indirizzo di destinazionex10 : l'indirizzo dell'uscita del threadkx11 : l'indirizzo della sequenza di inoltro rapidoCambio: x9 : se la funzione di destinazione è stata deviazionata, contiene l'indirizzo della sequenza di inoltro rapidox10 : l'indirizzo dell'uscita del threadkx11 : se la funzione è stata deviazionata, contiene l'indirizzo di uscita del threadk. In caso contrario, l'indirizzo di destinazione è saltato aRegistri conservati: x0 -x8 , x15 (chkstk ) e q0 -q7 |
__os_arm64x_check_icall_cfg |
__os_arm64x_check_icall Uguale a ma controlla anche che l'indirizzo specificato sia una destinazione di chiamata indiretta di Control Flow Graph valida |
Argomenti:x10 : l'indirizzo dell'uscita del threadkx11 : indirizzo della funzione di destinazioneCambio: x9 : se la destinazione è x64, l'indirizzo della funzione. In caso contrario, non definitox10 : l'indirizzo dell'uscita del threadkx11 : se la destinazione è x64, contiene l'indirizzo del thread di uscita. In caso contrario, l'indirizzo della funzioneRegistri conservati: x0 -x8 , x15 (chkstk ) e q0 -q7 |
__os_arm64x_get_x64_information |
Ottiene la parte richiesta del contesto del registro live x64 | _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo) |
__os_arm64x_set_x64_information |
Imposta la parte richiesta del contesto del registro live x64 | _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo) |
__os_arm64x_x64_jump |
Usato nel regolatore senza firma e in altri batch che inoltrano direttamente (jmp ) una chiamata a un'altra funzione che può avere qualsiasi firma, rinviando l'applicazione potenziale del diritto del montenegrok al vero obiettivo |
Argomenti:x9 : destinazione per passare aTutti i registri dei parametri mantenuti (inoltrati) |
Thunk
I batch sono i meccanismi di basso livello per supportare le funzioni ARM64EC e x64 che si chiamano tra loro. Esistono due tipi: i batch di ingresso per l'immissione di funzioni di ARM64EC e i thread di uscita per chiamare le funzioni x64.
Entry kubernetes e entry kubernetes intrinseci: da x64 a ARM64EC chiamata di funzione
Per supportare i chiamanti x64 quando una funzione C/C++ viene compilata come ARM64EC, la toolchain genera un'unica voce basata su ARM64EC codice computer. Gli intrinseci hanno un entry thunk proprio. Tutte le altre funzioni condividono unavocea con tutte le funzioni che dispongono di una convenzione di chiamata corrispondente, parametri e tipo restituito. Il contenuto del threadk dipende dalla convenzione di chiamata della funzione C/C++.
Oltre a gestire i parametri e l'indirizzo restituito, il bridgek collega le differenze nella volatilità tra i registri vettoriali ARM64EC e x64 causati dal mapping dei registri vettoriali ARM64EC:
ARM64EC registro | Registro x64 | convenzione di chiamata ARM64EC | Convenzione di chiamata ARM64 | Convenzione di chiamata x64 |
---|---|---|---|---|
v6 -v15 |
xmm6 -xmm15 |
volatile, ma salvato/ripristinato nellavocea (x64 a ARM64EC) | volatile o parzialmente volatile superiore a 64 bit | non volatile |
La voce thunk esegue le azioni seguenti:
Numero parametro | Uso dello stack |
---|---|
0-4 | Archivia ARM64EC v6 e v7 nello spazio home allocato dal chiamantePoiché il chiamato è ARM64EC, che non ha la nozione di spazio domestico, i valori archiviati non sono clobbered. Alloca 128 byte aggiuntivi nello stack e archivia ARM64EC v8 tramite v15 . |
5-8 | x4 = 5° parametro dallo stackx5 = 6° parametro dallo stackx6 = 7° parametro dallo stackx7 = 8° parametro dallo stackSe il parametro è SIMD, vengono invece usati i v4 -v7 registri |
+9 | AlignUp(NumParams - 8 , 2) * 8 Alloca byte nello stack. *Copia i 9 e i parametri rimanenti in questa area |
* Allineamento del valore a un numero pari garantisce che lo stack rimanga allineato a 16 byte
Se la funzione accetta un parametro integer a 32 bit, è consentito eseguire il push solo di 32 bit anziché dei 64 bit completi del registro padre.
Successivamente, il thunk usa un'istruzione ARM64 bl
per chiamare la funzione ARM64EC. Dopo la restituzione della funzione, il thunk:
- Annulla le allocazioni dello stack
- Chiama l'helper dell'emulatore
__os_arm64x_dispatch_ret
per visualizzare l'indirizzo restituito x64 e riprendere l'emulazione x64.
Uscita da thunk: ARM64EC alla chiamata di funzione x64
Per ogni chiamata eseguita da una funzione C/C++ ARM64EC al codice x64 potenziale, la toolchain MSVC genera un thread di uscita. Il contenuto del threadk dipende dai parametri del chiamato x64 e dal fatto che il chiamato usi la convenzione di chiamata standard o __vectorcall
. Il compilatore ottiene queste informazioni da una dichiarazione di funzione per il chiamato.
Prima di tutto, il csvk inserisce l'indirizzo restituito presente nel registro ARM64EC lr
e un valore fittizio a 8 byte per garantire che lo stack sia allineato a 16 byte. In secondo luogo, il thunk gestisce i parametri:
Numero parametro | Uso dello stack |
---|---|
0-4 | Alloca 32 byte di spazio home nello stack |
5-8 | AlignUp(NumParams - 4, 2) * 8 Alloca più byte in alto nello stack. * Copia il 5° e i parametri successivi da ARM64EC x4 -x7 a questo spazio aggiuntivo |
+9 | Copia il 9° e i parametri rimanenti nello spazio aggiuntivo |
* L'allineamento del valore a un numero pari garantisce che lo stack rimanga allineato a 16 byte.
In terzo luogo, il helper dell'emulatore chiama l'helper __os_arm64x_dispatch_call_no_redirect
dell'emulatore x64 per eseguire la funzione x64. La chiamata deve essere un'istruzione blr x16
(convenientemente, x16
è un registro volatile). È necessaria un'istruzione blr x16
perché l'emulatore x64 analizza questa istruzione come hint.
La funzione x64 tenta in genere di tornare all'helper dell'emulatore usando un'istruzione x64 ret
. A questo punto, l'emulatore x64 rileva che si trova nel codice ARM64EC. Legge quindi l'hint a 4 byte precedente che si verifica come istruzione ARM64 blr x16
. Poiché questo hint indica che l'indirizzo restituito si trova in questo helper, l'emulatore passa direttamente a questo indirizzo.
La funzione x64 può tornare all'helper dell'emulatore usando qualsiasi istruzione di ramo, tra cui x64 jmp
e call
. L'emulatore gestisce anche questi scenari.
Quando l'helper torna poi al thunk, il thunk:
- Annulla qualsiasi allocazione dello stack
- Visualizza il registro ARM64EC
lr
- Esegue un'istruzione ARM64
ret lr
.
ARM64EC decorazione del nome della funzione
Un nome di funzione ARM64EC ha una decorazione secondaria applicata dopo qualsiasi decorazione specifica del linguaggio. Per le funzioni con collegamento C (compilato come C o tramite extern "C"
), un #
oggetto viene anteporto al nome. Per le funzioni decorate in C++, viene inserito un $$h
tag nel nome.
foo => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ
__vectorcall
La toolchain ARM64EC attualmente non supporta __vectorcall
. Il compilatore genera un errore quando rileva __vectorcall
l'utilizzo con ARM64EC.
Vedi anche
Informazioni ARM64EC codice ABI e assembly
Problemi comuni di migrazione arm di Visual C++
Nomi decorati