Visão geral das convenções ARM ABI
A interface binária do aplicativo (ABI) de código compilado para Windows em processadores ARM é baseada na EABI padrão do ARM.Este artigo destaca diferenças chave entre Windows em ARM e o padrão.Para obter mais informações sobre a EABI padrão do ARM, consulte Interface Binária do Aplicativo (ABI) da Arquitetura ARM.
Requisitos Base
O Windows em ARM presume estar sendo executado em uma arquitetura ARMv7 continuamente.É necessário haver suporte a ponto flutuante na forma de VFPv3-D32 ou mais recente no hardware.O VFP deve suportar ponto flutuante de precisão única e de precisão dupla no hardware.O tempo de execução do Windows não suporta emulação de ponto flutuante para habilitar execução em hardware não VFP.
Suporte a Extensões Advanced SIMD (NEON)—isso inclui operações de ponto inteiro e flutuante—também deve estar presente no hardware.Não é fornecido suporte de tempo de execução para emulação.
Suporte a divisão de inteiros (UDIV/SDIV) é altamente recomendável mas não exigido.As plataformas sem suporte a divisão de inteiros podem incorrer uma penalidade de desempenho, pois essas operações devem ser capturadas e, possivelmente, atualizadas.
Endianness
O Windows em ARM é executado em modo little-endian.O compilador do Visual C++ e o tempo de execução do Windows sempre esperam dados little-endian.Apesar de a instrução SETEND na arquitetura do conjunto de instruções (ISA) do ARM permitir até mesmo código de modo de usuário para alterar a endianness atual, não é aconselhável fazer isso pois é perigoso para um aplicativo.Se uma exceção é gerada em modo big-endian, o comportamento é imprevisível e pode levar a uma falha do aplicativo em modo de usuário ou em uma verificação de erro em modo kernel.
Alinhamento
Apesar de o Windows habilitar o hardware ARM para manipular acessos inteiros desalinhados de maneira transparente, falhas de alinhamento ainda podem ser geradas em algumas situações.Segue essas regras de alinhamento:
Cargas e armazenamentos inteiros com tamanho de meia palavra (16-bit) e de palavra inteira (32-bit) não precisam ser alinhados.O hardware os manipula de maneira eficiente e transparente.
Cargas e armazenamentos de ponto flutuante devem ser alinhados.O kernel manipula cargas e armazenamentos não alinhados de maneira transparente, porém, com sobrecarga significativa.
Operações Carregar ou armazenar duplo (LDRD/STRD) e múltiplo (LDM/STM) devem ser alinhadas.O kernel manipula a maioria delas de maneira transparente, porém, com sobrecarga significativa.
Todos os acessos a memória não armazenada em cache devem ser alinhados, mesmo para acessos inteiros.Acessos desalinhados causam uma falha de alinhamento.
Conjunto de instruções
O conjunto de instruções para Windows em ARM é estritamente limitado a Thumb-2.Qualquer código executado nessa plataforma é sempre esperado começar e permanecer em modo Thumb.Uma tentativa de alternar para o conjunto de instruções herdado do ARM pode ser bem-sucedida, porém, qualquer exceção ou interrupção que ocorrer pode levar a uma falha do aplicativo em modo de usuário ou a uma verificação de erro em modo kernel.
Um efeito colateral desse requisito é que todos os ponteiros de código devem conter o conjunto de bits inferior.Isso ocorre para quando forem carregados e ramificados via BLX ou BX, o processador permanecerá em modo Thumb e não tentará executar o código de destino como instruções ARM de 32 bits.
Instruções de IT
O uso de Instruções de IT em código Thumb-2 não é permitido, exceto para esses casos específicos:
A Instrução de IT pode ser usada apenas para modificar uma instrução de destino.
A instrução de destino deve ser uma instrução de 16 bits.
A instrução de destino deve ser uma das seguintes:
Opcodes de 16 bits
Classe
Restrições
MOV, MVN
Mover
Rm != PC, Rd != PC
LDR, LDR[S]B, LDR[S]H
Carregar da memória
Mas não formulários literais LDR
STR, STRB, STRH
Armazenar na memória
ADD, ADC, RSB, SBC, SUB
Somar ou subtrair
Mas não formulários ADD/SUB SP, SP, imm7
Rm != PC, Rdn != PC, Rdm != PC
CMP, CMN
Comparar
Rm != PC, Rn != PC
MUL
Multiplicar
ASR, LSL, LSR, ROR
Deslocamento de bits
AND, BIC, EOR, ORR, TST
Aritmética de bits
BX
Ramificar para registrar
Rm != PC
Apesar de CPUs ARMv7 atuais não poderem relatar o uso de formulários de instrução não permitidos, é esperado que as gerações futuras poderão.Se esses formulários forem detectados, qualquer programa que os utilizam podem ser fechados com uma exceção de instrução indefinida.
Instruções SDIV/UDIV
O uso de instruções de divisão de inteiros SDIV e UDIV tem suporte total, mesmo em plataformas sem hardware nativo para manipulá-las.A divisão de sobrecarga por SDIV ou UDIV em um processador Cortex-A9 é de aproximadamente 80 ciclos, além do tempo de divisão geral de 20-250 ciclos, dependendo das entradas.
Registros inteiros
O processador ARM oferece suporte a 16 registros inteiros:
Registro |
Volátil? |
Função |
---|---|---|
r0 |
Volátil |
Parâmetro, resultado, registro de rascunho 1 |
r1 |
Volátil |
Parâmetro, resultado, registro de rascunho 2 |
r2 |
Volátil |
Parâmetro, registro de rascunho 3 |
r3 |
Volátil |
Parâmetro, registro de rascunho 4 |
r4 |
Não volátil |
|
r5 |
Não volátil |
|
r6 |
Não volátil |
|
r7 |
Não volátil |
|
r8 |
Não volátil |
|
r9 |
Não volátil |
|
r10 |
Não volátil |
|
r11 |
Não volátil |
Ponteiro de quadro |
r12 |
Volátil |
Registro de rascunho de chamada dentro do procedimento |
r13 (SP) |
Não volátil |
Ponteiro de pilha |
r14 (LR) |
Não volátil |
Registro de link |
r15 (PC) |
Não volátil |
Contador de programa |
Para obter detalhes sobre como usar o parâmetro e os registros de valores retornados, consulte a seção Passagem de Parâmetro neste artigo.
O Windows usa r11 para passagem rápida do quadro de pilha.Para obter mais informações, consulte a seção Passagem de Pilha.Devido a esse requisito, o r11 sempre deve apontar para o link superior na cadeia.Não use r11 para propósitos gerais—o código não gerará passagens de pilha corretas durante a análise.
Registros VFP
O Windows tem suporte apenas a variações de ARM com suporte no coprocessador VFPv3-D32.Isso significa que registros de ponto flutuante estão sempre presentes e podem ser considerados para passagem de parâmetro e que o conjunto inteiro de 32 registros está disponível para uso.Os registros VFP e seu uso são resumidos nesta tabela:
Únicos |
Duplos |
Quads |
Volátil? |
Função |
---|---|---|---|---|
s0-s3 |
d0-d1 |
q0 |
Volátil |
Parâmetros, resultado, registro de rascunho |
s4-s7 |
d2-d3 |
q1 |
Volátil |
Parâmetros, registro de rascunho |
s8-s11 |
d4-d5 |
q2 |
Volátil |
Parâmetros, registro de rascunho |
s12-s15 |
d6-d7 |
q3 |
Volátil |
Parâmetros, registro de rascunho |
s16-s19 |
d8-d9 |
q4 |
Não volátil |
|
s20-s23 |
d10-d11 |
q5 |
Não volátil |
|
s24-s27 |
d12-d13 |
q6 |
Não volátil |
|
s28-s31 |
d14-d15 |
q7 |
Não volátil |
|
d16-d31 |
q8-q15 |
Volátil |
A próxima tabela ilustra os campos de status do ponto flutuante e do registro de controle (FPSCR):
Bits |
Significado |
Volátil? |
Função |
---|---|---|---|
31-28 |
NZCV |
Volátil |
Sinalizadores de status |
27 |
QC |
Volátil |
Saturação cumulativa |
26 |
AHP |
Não volátil |
Controle de meia precisão alternativo |
25 |
DN |
Não volátil |
Controle de modo NaN padrão |
24 |
FZ |
Não volátil |
Controle de modo Flush-to-zero |
23-22 |
RMode |
Não volátil |
Controle de modo de arredondamento |
21-20 |
Passo |
Não volátil |
Passo do Vetor, deve ser sempre 0 |
18-16 |
Len |
Não volátil |
Comprimento do Vetor, deve ser sempre 0 |
15, 12-8 |
IDE, IXE etc. |
Não volátil |
Bits de habilitação de captura de exceção, deve ser sempre 0 |
7, 4-0 |
IDC, IXC etc. |
Volátil |
Sinalizadores de exceção cumulativa |
Exceções de ponto flutuante
A maior parte do hardware ARM não suporta exceções de ponto flutuante IEEE.Em variações de processador que não contêm exceções de ponto flutuante de hardware, o kernel do Windows silenciosamente captura as exceções e implicitamente as desabilita no registro de FPSCR.Isso garante comportamento normalizado entre variações do processador.Caso contrário, código desenvolvido em uma plataforma que não possui suporte de exceção pode receber exceções inesperadas quando estiver executando em uma plataforma com suporte de exceção.
Passagem de parâmetro
Para funções não variadic, a ABI do Windows em ARM segue as regras do ARM para passagem de parâmetro—isso inclui as extensões VFP e Advanced SIMD.Essas regras seguem o Padrão de Chamada de Procedimento para a Arquitetura ARM, consolidado com as extensões VFP.Por padrão, os primeiros quatro argumentos inteiros e até oito argumentos de ponto flutuante ou vetor são passados em registros e argumentos adicionais são passados na pilha.Argumentos são atribuídos a registros ou à pilha usando este procedimento:
Estágio A—Inicialização
A inicialização é realizada exatamente uma vez, antes do início do processamento do argumento:
O Próximo Número de Registro Principal (NCRN) é definido para r0.
Os registros VFP são marcados como não alocados.
O Próximo Endereço de Argumento Empilhado (NSAA) é definido para o SP atual.
Se uma função que retorna um resultado na memória for chamada, o endereço do resultado é colocado em r0 e o NCRN é definido para r1.
Estágio B—Pré-enchimento e Extensão de Argumentos
Para cada argumento na lista, a primeira regra correspondente da seguinte lista é aplicada:
Se o argumento for um tipo composto cujo tamanho não pode ser determinado estaticamente pelo chamador e pelo receptor, o argumento é copiado para a memória e substituído por um ponteiro para a cópia.
Se o argumento for um byte ou uma meia palavra de 16 bits, será estendido em zero ou estendido em sinal para uma palavra inteira de 32 bits e tratado como um argumento de 4 bytes.
Se o argumento for um tipo composto, seu tamanho é arredondado para cima para o próximo múltiplo de 4.
Estágio C—Atribuição de Argumentos a Registros e à Pilha
Para cada argumento na lista, as seguintes regras são aplicadas em turnos até o argumento ser alocado:
Se o argumento for um tipo VFP e não houver registros VFP consecutivos não alocados suficientes do tipo adequado, o argumento será alocado para a sequência com menor número de tais registros.
Se o argumento for um tipo VFP, todos os registros não alocados restantes serão marcados como não disponíveis.O NSAA é ajustado para cima até ser alinhado corretamente com o tipo de argumento e o argumento ser copiado para a pilha no NSAA ajustado.Em seguida, o NSAA é incrementado pelo tamanho do argumento.
Se o argumento exige alinhamento de 8 bytes, o NCRN é arredondado para cima para o próximo número de registro par.
Se o tamanho do argumento em palavras de 32 bits não for maior que r4 menos o NCRN, o argumento será copiado nos registros principais, começando no NCRN, com os bits menos significativos ocupando os registros de números inferiores.O NCRN é incrementado pelo número de registros usados.
Se o NCRN for menor que r4 e o NSAA for igual ao SP, o argumento será dividido entre registros principais e a pilha.A primeira parte do argumento é copiado para os registros principais, começando no NCRN até e incluindo o r3.O lembrete do argumento é copiado para a pilha, começando no NSAA.O NCRN é definido para r4 e o NSAA é incrementado pelo tamanho do argumento menos a quantidade passada em registros.
Se o argumento exige alinhamento de 8 bytes, o NSAA é arredondado para cima para o próximo endereço de 8 bytes alinhado.
O argumento é copiado para a memória no NSAA.O NSAA é incrementado pelo tamanho do argumento.
Os registros VFP não são usados para funções variadic e as regras 1 e 2 do Estágio C são ignoradas.Isso significa que uma função variadic pode começar com um push adicional {r0-r3} para preceder os argumentos do registro a qualquer argumento adicional passado pelo chamador e acessar a lista de argumentos inteira diretamente da pilha.
Valores de tipo inteiro são retornados em r0, opcionalmente estendidos para r1 para valores retornados de 64 bits.Valores de tipo SIMD ou ponto flutuante VFP/NEON são retornados em s0, d0 ou q0 conforme adequado.
Pilha
A pilha deve permanecer sempre alinhada a 4 bytes e deve se alinhada a 8 bytes em qualquer limite de função.Isso é necessário para suportar o uso frequente de operações sincronizadas em variáveis de pilha de 64 bits.A EABI do ARM declara que a pilha é alinhada a 8 bytes em qualquer interface pública.Para consistência, a ABI do Windows em ARM considera qualquer limite de função como uma interface pública.
Funções que devem usar um ponteiro de quadro—por exemplo, funções que chamam alloca ou alteram o ponteiro de pilha dinamicamente—devem manter o ponteiro de quadro em r11 no prólogo da função e deixá-lo inalterado até o epílogo.Funções que não exigem um ponteiro de quadro devem realizar todas as atualizações de pilha no prólogo e deixar o ponteiro de pilha inalterado até o epílogo.
Funções que alocam 4 KB ou mais na pilha devem garantir que cada página antes da página final será tocada em ordem.Isso garante que nenhum código poderá “saltar sobre” as páginas de proteção que o Windows usa para expandir a pilha.Geralmente, isso é feito pelo auxiliar de __chkstk, para o qual é passado a alocação de pilha total em bytes dividida por 4 em r4, e que retorna a quantidade final de alocação de pilha em bytes de volta em r4.
Zona Vermelha
A área de 8 bytes imediatamente abaixo do ponteiro de pilha atual é reservada para análise e atualização dinâmica.Isso permite inserir código gerado cuidadosamente, que armazena 2 registros em [sp, #-8] e os usa temporariamente para propósitos arbitrários.O kernel do Windows garante que esses 8 bytes não serão sobrescritos ao ocorrer uma exceção ou interrupção no modo de usuário e no modo kernel.
Pilha de Kernel
A pilha padrão de modo kernel no Windows é de três páginas (12 KB).Tenha cuidado para não criar funções que possuem grandes buffers de pilha em modo kernel.Uma interrupção pode ocorrer com muito pouco espaço de pilha e causar uma verificação de erros de pânico de pilha.
Especificações C/C++
Enumerações são tipos inteiros de 32 bits, exceto quando ao menos um valor na enumeração exigir armazenamento de palavra dupla de 64 bits.Nesse caso, a enumeração é promovida para um tipo inteiro de 64 bits.
wchar_t é definido como equivalente a unsigned short, para preservar a compatibilidade com outras plataformas.
Passagem de Pilha
Código do Windows é compilado com ponteiros de quadro habilitados (/Oy (omissão do ponteiro de quadro)) para permitir a passagem rápida de pilha.Geralmente, o registro r11 aponta para o próximo link na cadeia, que é um par {r11, lr} que especifica o ponteiro para o quadro anterior na pilha e o endereço de retorno.Também é recomendável habilitar ponteiros de quadro no código para criação de perfis e rastreamento melhorados.
Desenrolamento de exceção
O desenrolamento de pilha durante manipulação de exceções é habilitado pelo uso de códigos de desenrolamento.Os códigos de desenrolamento são uma sequência de bytes armazenada na seção .xdata da imagem executável.Descrevem a operação do código de prólogo e epílogo da função de maneira abstrata, de modo que os efeitos do prólogo de uma função podem ser desfeitos em preparação para desenrolamento para o quadro de pilha do chamador.
A EABI do ARM especifica um modelo de desenrolamento de exceção que usa códigos de desenrolamento.No entanto, essa especificação não é suficiente para desenrolamento no Windows, que deve manipular casos em que o processador está no meio do prólogo ou epílogo de uma função.Para obter mais informações sobre Windows desenrolamento e dados de exceção do ARM, consulte Tratamento de exceção ARM.
É recomendável descrever o código gerado dinamicamente usando tabelas de função dinâmica especificadas em chamas para RtlAddFunctionTable e funções associadas, para o código gerado poder participar da manipulação de exceções.
Contador de Ciclo
Processadores ARM executando Windows devem suportar um contador de ciclo, porém, usar o contador diretamente pode causar problemas.Para evitar esses problemas, o Windows em ARM usa um opcode indefinido para solicitar um valor de contador de ciclo de 64 bits normalizado.A partir de C ou C++, use a intrínseca __rdpmccntr64 para emitir o opcode adequada; a partir de assembly, use a instrução __rdpmccntr64.A leitura do contador de ciclo leva aproximadamente 60 ciclos em um Cortex-A9.
O contador é um verdadeiro contador de ciclo, não um relógio; portanto, a frequência de contagem varia com a frequência do processador.Se desejar medir o tempo de relógio decorrido, use QueryPerformanceCounter.
Consulte também
Referência
Problemas de migração ARM do Visual C++