Visão geral das convenções ABI ARM64EC
ARM64EC é uma ABI (interface binária do aplicativo) que permite que binários ARM64 sejam executados nativamente e interoperavelmente com código x64. Especificamente, a ABI ARM64EC segue convenções de software x64, incluindo convenção de chamada, uso de pilha e alinhamento de dados, tornando o código ARM64EC e x64 interoperável. O sistema operacional emula a parte x64 do binário. (O EC em ARM64EC significa compatível com emulação.)
Para obter mais informações sobre as ABIs x64 e ARM64, confira Visão geral das convenções de ABI x64 e Visão geral das convenções da ABI ARM64.
O ARM64EC não resolve as diferenças de modelo de memória entre arquiteturas baseadas em x64 e ARM. Para obter mais informações, confira Problemas comuns de migração do ARM do Visual C++.
Definições
- ARM64 – o fluxo de código para processos ARM64 que contém o código ARM64 tradicional.
- ARM64EC – o fluxo de código que utiliza um subconjunto do conjunto de registros ARM64 para fornecer interoperabilidade com código x64.
Registrar mapeamento
Os processos x64 podem ter threads executando código ARM64EC. Portanto, é sempre possível recuperar um contexto de registro x64. O ARM64EC usa um subconjunto dos registros de núcleo ARM64 que são mapeados na relação 1:1 para registros x64 emulados. É importante ressaltar que o ARM64EC nunca usa registros fora desse subconjunto, exceto para ler o endereço x18
TEB (Thread Environment Block).
Os processos nativos do ARM64 não deverão apresentar perda de desempenho quando algumas ou muitas funções forem recompiladas como ARM64EC. Para manter o desempenho, a ABI segue estes princípios:
O subconjunto de registro ARM64EC inclui todos os registros que fazem parte da convenção de chamada de função ARM64.
A convenção de chamada ARM64EC é mapeada diretamente para a convenção de chamada ARM64.
Rotinas auxiliares especiais, como __chkstk_arm64ec
, usam registros e convenções de chamadas personalizados. Esses registros também estão incluídos no subconjunto de registros do ARM64EC.
Registrar mapeamento para registros inteiros
Registro ARM64EC | Registro x64 | Convenção de chamada ARM64EC | Convenção de chamada ARM64 | Convenção de chamada 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 (os 64 bits inferiores do registro x87 R1 ) |
volatile | volatile | volatile |
x7 |
mm2 (os 64 bits inferiores do registro x87 R2 ) |
volatile | volatile | volatile |
x8 |
rax |
volatile | volatile | volatile |
x9 |
mm3 (os 64 bits inferiores do registro x87 R3 ) |
volatile | volatile | volatile |
x10 |
mm4 (os 64 bits inferiores do registro x87 R4 ) |
volatile | volatile | volatile |
x11 |
mm5 (os 64 bits inferiores do registro x87 R5 ) |
volatile | volatile | volatile |
x12 |
mm6 (os 64 bits inferiores do registro x87 R6 ) |
volatile | volatile | volatile |
x13 |
N/D | sem permissão | volatile | N/D |
x14 |
N/D | sem permissão | volatile | N/D |
x15 |
mm7 (os 64 bits inferiores do registro x87 R7 ) |
volatile | volatile | volatile |
x16 |
Os 16 bits superiores de cada um dos registros x87 R0 -R3 |
volátil(xip0 ) |
volátil(xip0 ) |
volatile |
x17 |
Os 16 bits superiores de cada um dos registros x87 R4 -R7 |
volátil(xip1 ) |
volátil(xip1 ) |
volatile |
x18 |
GS.base | fixo(TEB) | fixo(TEB) | fixo(TEB) |
x19 |
r12 |
não volátil | não volátil | não volátil |
x20 |
r13 |
não volátil | não volátil | não volátil |
x21 |
r14 |
não volátil | não volátil | não volátil |
x22 |
r15 |
não volátil | não volátil | não volátil |
x23 |
N/D | sem permissão | não volátil | N/D |
x24 |
N/D | sem permissão | não volátil | N/D |
x25 |
rsi |
não volátil | não volátil | não volátil |
x26 |
rdi |
não volátil | não volátil | não volátil |
x27 |
rbx |
não volátil | não volátil | não volátil |
x28 |
N/D | sem permissão | sem permissão | N/D |
fp |
rbp |
não volátil | não volátil | não volátil |
lr |
mm0 (os 64 bits inferiores do registro x87 R0 ) |
ambos | ambos | ambos |
sp |
rsp |
não volátil | não volátil | não volátil |
pc |
rip |
ponteiro de instrução | ponteiro de instrução | ponteiro de instrução |
PSTATE subconjunto: N /Z /C /V /SS 1, 2 |
RFLAGS subconjunto: SF /ZF /CF /OF /TF |
volatile | volatile | volatile |
N/D | RFLAGS subconjunto: PF /AF |
N/D | N/D | volatile |
N/D | RFLAGS subconjunto: DF |
N/D | N/D | não volátil |
1 Evite ler, gravar ou calcular mapeamentos diretamente entre PSTATE
e RFLAGS
. Esses bits podem ser usados no futuro e estão sujeitos a alterações.
2 O sinalizador de transporte C
ARM64EC é o inverso do sinalizador de transporte x64 CF
para operações de subtração. Não há tratamento especial, pois o sinalizador é volátil e, portanto, é destruído ao fazer a transição entre funções (ARM64EC e x64).
Registrar mapeamento para registros de vetor
Registro ARM64EC | Registro x64 | Convenção de chamada ARM64EC | Convenção de chamada ARM64 | Convenção de chamada x64 |
---|---|---|---|---|
v0 -v5 |
xmm0 -xmm5 |
volatile | volatile | volatile |
v6 -v7 |
xmm6 -xmm7 |
volatile | volatile | não volátil |
v8 -v15 |
xmm8 -xmm15 |
volátil 1 | volátil 1 | não volátil |
v16 -v31 |
xmm16 -xmm31 |
sem permissão | volatile | sem permissão (o emulador x64 não dá suporte a AVX-512) |
FPCR 2 |
MXCSR[15:6] |
não volátil | não volátil | não volátil |
FPSR 2 |
MXCSR[5:0] |
volatile | volatile | volatile |
1 Esses registros ARM64 são especiais porque os 64 bits inferiores não são voláteis, mas os 64 bits superiores são voláteis. Do ponto de vista de um chamador x64, eles são efetivamente voláteis porque o computador chamado destruiria os dados.
2 Evite ler, gravar ou calcular mapeamentos de computação de FPCR
e FPSR
. Esses bits podem ser usados no futuro e estão sujeitos a alterações.
Empacotamento de struct
ARM64EC segue as mesmas regras de empacotamento de struct usadas para x64 para garantir a interoperabilidade entre o código ARM64EC e o código x64. Para obter mais informações e exemplos de empacotamento de struct x64, confira Visão geral das convenções do ABI x64.
Rotinas auxiliares de emulação da ABI
O código ARM64EC e thunks usam rotinas auxiliares de emulação para fazer a transição entre funções x64 e ARM64EC.
A tabela a seguir descreve cada rotina especial da ABI e os registros que a ABI usa. As rotinas não modificam os registros preservados listados na coluna da ABI. Nenhuma suposição deve ser feita sobre registros não listados. Em disco, os ponteiros de rotina da ABI são nulos. No tempo de carregamento, o carregador atualiza os ponteiros para apontar para as rotinas do emulador x64.
Nome | Descrição | ABI |
---|---|---|
__os_arm64x_dispatch_call_no_redirect |
Chamado por um thunk de saída para chamar um destino x64 (uma função x64 ou uma sequência de avanço rápido x64). A rotina envia por push o endereço de retorno ARM64EC (no registro LR ) seguido pelo endereço da instrução que sucede a uma instrução blr x16 que, por sua vez, invoca o emulador x64. Em seguida, ela executa a instrução blr x16 |
valor retornado em x8 (rax ) |
__os_arm64x_dispatch_ret |
Chamado por um thunk de entrada para retornar ao respectivo chamador x64. Ele remove o endereço de retorno x64 mais recente da pilha e invoca o emulador x64 para saltar até ele | N/D |
__os_arm64x_check_call |
Chamado pelo código ARM64EC com um ponteiro para um thunk de saída e o endereço de destino ARM64EC indireto a ser executado. O destino ARM64EC é considerado como qualificado para aplicação de patches, e a execução sempre retorna ao chamador com os mesmos dados com os qual ele foi chamado ou dados modificados | Argumentos:x9 : o endereço de destinox10 : o endereço do thunk de saídax11 : o endereço da sequência de avanço rápidoSaída: x9 : se a função de destino foi desviada, ela contém o endereço da sequência de avanço rápidox10 : o endereço do thunk de saídax11 : se a função foi desviada, ela contém o endereço thunk de saída. Caso contrário, o endereço de destino saltou paraRegistros preservados: x0 -x8 , x15 (chkstk ). e q0 -q7 |
__os_arm64x_check_icall |
Chamado pelo código ARM64EC, com um ponteiro para um thunk de saída, para manipular um salto para um endereço de destino que seja x64 ou ARM64EC. Se o destino for x64 e o código x64 não tiver sido corrigido, a rotina definirá o registro do endereço de destino. Ele aponta para a versão ARM64EC da função, se houver. Caso contrário, ele define o registro para apontar para o thunk de saída que faz a transição para o destino x64. Em seguida, ele retorna para o código ARM64EC de chamada que, em seguida, salta para o endereço no registro. Essa rotina é uma versão não otimizada de __os_arm64x_check_call , em que o endereço de destino não é conhecido em tempo de compilaçãoUsada em um site de chamada de uma chamada indireta |
Argumentos:x9 : o endereço de destinox10 : o endereço do thunk de saídax11 : o endereço da sequência de avanço rápidoSaída: x9 : se a função de destino foi desviada, ela contém o endereço da sequência de avanço rápidox10 : o endereço do thunk de saídax11 : se a função foi desviada, ela contém o endereço thunk de saída. Caso contrário, o endereço de destino saltou paraRegistros preservados: x0 -x8 , x15 (chkstk ) e q0 -q7 |
__os_arm64x_check_icall_cfg |
O mesmo que __os_arm64x_check_icall , mas também verifica se o endereço especificado é um destino de chamada indireto válido do grafo de fluxo de controle |
Argumentos:x10 : o endereço do thunk de saídax11 : o endereço da função de destinoSaída: x9 : se o destino for x64, o endereço para a função. Caso contrário, é indefinidox10 : o endereço do thunk de saídax11 : se o destino for x64, conterá o endereço do thunk de saída. Caso contrário, o endereço da funçãoRegistros preservados: x0 -x8 , x15 (chkstk ) e q0 -q7 |
__os_arm64x_get_x64_information |
Obtém a parte solicitada do contexto de registro x64 ativo | _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo) |
__os_arm64x_set_x64_information |
Define a parte solicitada do contexto de registro x64 ativo | _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo) |
__os_arm64x_x64_jump |
Usado no ajustador sem assinatura e em outros thunks que fazem encaminhamento direto (jmp ) de uma chamada para outra função que pode ter qualquer assinatura, adiando a aplicação potencial do thunk direito para o destino real |
Argumentos:x9 : destino para o qual saltarTodos os registros de parâmetro preservados (encaminhados) |
Thunks
Thunks são os mecanismos de nível baixo para dar suporte a funções ARM64EC e x64 chamando umas às outras. Há dois tipos: thunks de entrada para inserir funções ARM64EC e thunks de saída para chamar funções x64.
Thunk de entrada e thunks de entrada intrínseca: chamada de função x64 para ARM64EC
Para dar suporte a chamadores x64 quando uma função C/C++ é compilada como ARM64EC, a cadeia de ferramentas gera apenas um thunk de entrada que consiste no código do computador ARM64EC. Funções intrínsecas têm um thunk de entrada próprio. Todas as outras funções compartilham um thunk de entrada com todas as funções que têm uma convenção de chamada, parâmetros e um tipo de retorno correspondentes. O conteúdo do thunk depende da convenção de chamada da função C/C++.
Além de manipular parâmetros e o endereço de retorno, o thunk concilia as diferenças de volatilidade entre registros de vetores ARM64EC e x64 causados pelo mapeamento de registro de vetor ARM64EC:
Registro ARM64EC | Registro x64 | Convenção de chamada ARM64EC | Convenção de chamada ARM64 | Convenção de chamada x64 |
---|---|---|---|---|
v6 -v15 |
xmm6 -xmm15 |
volátil, mas salvo/restaurado no thunk de entrada (x64 para ARM64EC) | 64 bits superiores voláteis ou parcialmente voláteis | não volátil |
O thunk de entrada executa as seguintes ações:
Número do parâmetro | Uso da pilha |
---|---|
0-4 | Armazena ARM64EC v6 e v7 no espaço de despejo alocado pelo chamadorComo o chamador é ARM64EC, que não inclui um espaço de despejo, os valores armazenados não são sobrescritos. Aloca mais 128 bytes na pilha e armazena o v8 ARM64EC por meio de v15 . |
5-8 | x4 = 5º parâmetro da pilhax5 = 6º parâmetro da pilhax6 = 7º parâmetro da pilhax7 = 8º parâmetro da pilhaSe o parâmetro for SIMD, os registros v4 -v7 serão usados em lugar dele |
+9 | Aloca bytes de AlignUp(NumParams - 8 , 2) * 8 na pilha. *Copia o 9º parâmetro e os parâmetros restantes para essa área |
* Alinhar o valor a um número par garante que a pilha permaneça alinhada a 16 bytes
Se a função aceitar um parâmetro inteiro de 32 bits, o thunk terá permissão apenas para enviar por push 32 bits em vez dos 64 bits totais do registro pai.
Em seguida, o thunk usa uma instrução bl
do ARM64 para chamar a função ARM64EC. Depois que a função retorna, o thunk:
- Desfaz todas as alocações de pilha
- Chama o auxiliar do emulador
__os_arm64x_dispatch_ret
para remover o endereço de retorno x64 mais recente da pilha e retomar a emulação x64.
Thunk de saída: chamada de função ARM64EC para x64
Para cada chamada que uma função ARM64EC C/C++ faz para código x64 em potencial, a cadeia de ferramentas MSVC gera um thunk de saída. O conteúdo do thunk depende dos parâmetros do chamador x64 e se o receptor está usando a convenção de chamada padrão ou __vectorcall
. O compilador obtém essas informações de uma declaração de função para o computador chamado.
Primeiro, o thunk envia por push o endereço de retorno que está no registro lr
do ARM64EC e um valor fictício de 8 bytes para garantir que a pilha esteja alinhada a 16 bytes. Depois, o thunk manipula os parâmetros:
Número do parâmetro | Uso da pilha |
---|---|
0-4 | Aloca 32 bytes de espaço de despejo na pilha |
5-8 | Aloca mais AlignUp(NumParams - 4, 2) * 8 bytes mais acima na pilha. * Copia o 5º parâmetro e todos os parâmetros subsequentes do x4 -x7 do ARM64EC para esse espaço extra |
+9 | Copia o 9º parâmetro e os parâmetros restantes para o espaço extra |
* Alinhar o valor a um número par garante que a pilha permaneça alinhada a 16 bytes.
Em terceiro lugar, o thunk chama o auxiliar do emulador __os_arm64x_dispatch_call_no_redirect
para invocar o emulador x64 para executar a função x64. A chamada precisa ser uma instrução blr x16
(convenientemente, x16
é um registro volátil). Uma instrução blr x16
é necessária porque o emulador x64 analisa essa instrução como uma dica.
A função x64 geralmente tenta retornar ao auxiliar do emulador usando uma instrução x64 ret
. Neste ponto, o emulador x64 detecta que ele está em código ARM64EC. Em seguida, ele lê a dica anterior de 4 bytes que, por acaso, é a instrução blr x16
do ARM64. Como essa dica infere que o endereço de retorno está nesse auxiliar, o emulador salta diretamente para esse endereço.
A função x64 tem permissão para retornar ao auxiliar do emulador usando qualquer instrução de branch, incluindo x64 jmp
e call
. O emulador também manipula esses cenários.
Quando o auxiliar retorna ao thunk, o thunk:
- Desfazer qualquer alocação de pilha
- Remove o registro
lr
do ARM64EC mais recente da pilha - Executa uma instrução
ret lr
do ARM64.
Decoração do nome da função ARM64EC
Um nome de função ARM64EC tem uma decoração secundária aplicada após qualquer decoração específica da linguagem de programação. Para funções com vinculação C (sejam elas compiladas como C ou usando extern "C"
), um #
é pré-acrescentado ao nome. Para funções decoradas C++, uma marca $$h
é inserida no nome.
foo => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ
__vectorcall
A cadeia de ferramentas ARM64EC atualmente não dá suporte a __vectorcall
. O compilador emite um erro ao detectar o uso de __vectorcall
com ARM64EC.
Confira também
Noções básicas do código de assembly e da ABI ARM64EC
Problemas comuns de migração ARM do Visual C++
Nomes decorados