Regras de chamada de programa STM32 (AAPCS): as regras de chamada de sub-rotina ARM mais recentes

        AAPCS é a interface de chamada de programa binário de aplicativo de arquitetura ARM (ABI) lançada pela arm. Este documento tem várias versões. As primeiras regras de chamada de programa ARM do blogueiro estavam em "Sistema ARM e programação estrutural", mas o que é descrito em o livro é ATPCS e AAPCS é uma versão atualizada do ATPCS. Mais tarde, fui ao site oficial da ARM e vi o documento AAPCS, então gravei. Existem vários documentos para o padrão ABI do ARM, dos quais este documento é apenas uma parte. O link abaixo contém documentos relevantes, você pode dar uma olhada neles se quiser. Link para a versão mais recente:GitHub - ARM-software/abi-aa: Interface binária de aplicativo para a arquitetura Arm®

1. Âmbito de aplicação

        AAPCS define como as sub-rotinas são escritas, compiladas e montadas individualmente para trabalharem em conjunto.Descreve a relação entre a rotina de chamada e a rotina chamada:

  • Chamar uma função requer a criação de um estado de programa no qual a rotina chamada possa iniciar a execução.

  • A função chamada é obrigada a preservar o estado do programa da função chamadora durante a chamada.

  • A função chamada tem o direito de alterar o estado do programa da função chamadora.

        Este padrão é a base para uma série de variantes do Padrão de Chamada de Procedimento (PCS) geradas por escolhas que refletem as seguintes prioridades alternativas:

  • código

  • desempenho

  • Funcionalidade (por exemplo, facilidade de depuração, verificações de tempo de execução, suporte para bibliotecas compartilhadas)

Alguns aspectos de cada variante (como o uso do R9) dependem do ambiente de execução, portanto:

  • É possível que o código que segue os padrões básicos seja compatível com PCS com todas as variantes.

  • É incomum que o código que está em conformidade com uma variante seja compatível com o código que está em conformidade com qualquer outra variante.

  • Não há garantia de que o código que esteja em conformidade com uma variante ou padrão base seja compatível com ambientes de execução que exigem esses padrões. O ambiente de execução pode impor requisitos adicionais além do escopo do padrão de chamada de procedimento.

A norma é dividida em quatro partes:

  • O layout dos dados.

  • Layout de pilha e padrões de chamada entre funções com uma interface comum.

  • As variantes estão disponíveis com extensões de processador ou ao executar um modelo de endereçamento restrito ao ambiente.

  • Ligações de linguagem C e C++ para tipos de dados comuns.

        Esta especificação não padroniza a representação de entidades de linguagem não-C visíveis publicamente (estas são descritas em CPPABI32) e não impõe quaisquer requisitos à representação de entidades de linguagem que não são visíveis em interfaces públicas.

2. Tipo de dados e alinhamento

2.1 Tipos básicos de dados

A tabela a seguir mostra os tipos de dados básicos da máquina (tipos de máquina).Um ponteiro NULL é sempre representado por zeros.

2.1.1 Ponto flutuante de meia precisão

Uma extensão opcional para a arquitetura ARM fornece suporte de hardware para valores de meia precisão, atualmente suportando três formatos:

  • Formato de meia precisão do padrão IEEE754-2008

  • Formato alternativo para ARM, oferecendo alcance extra, mas sem infinito (NaN)

  • O formato de ponto flutuante Brain fornece uma faixa dinâmica semelhante ao formato de ponto flutuante de 32 bits, mas com menos precisão.

2.1.2 Vetores conteinerizados

        O conteúdo de um vetor em contêiner é opaco para a maioria dos padrões de chamada: os únicos aspectos que definem seu layout são o formato da memória (a forma como os tipos básicos são armazenados na memória) e o mapeamento entre diferentes registros no momento da chamada.

2.2 Ordem de bytes

Do ponto de vista do software, a memória é uma matriz e cada byte possui seu próprio endereço.

A ABI oferece suporte a duas visualizações de memória implementadas pelo hardware subjacente:

  • Visualização big endian (modo big endian)

  • Visualização Little Endian (modo Little Endian)

2.3 Tipos compostos

        Um tipo composto é uma coleção de um ou mais tipos de dados básicos processados ​​como uma única entidade durante uma chamada. Pode ser qualquer um dos seguintes:

  • Sequência, seus membros são organizados na memória em ordem (na verdade, é uma estrutura C)

  • Federação, cada membro tem o mesmo endereço

  • variedade

A definição é recursiva, ou seja, cada tipo pode conter tipos compostos (estruturas dentro de estruturas).

3 Padrões básicos de chamada de procedimento

        Esta parte define um conjunto de instruções padrão de chamada somente de registro de núcleo em nível de máquina, comum para ARM e Thumb. Deve ser usado em sistemas sem hardware de ponto flutuante ou quando for necessário um alto grau de interoperabilidade com código Thumb.

3.1 Registros de máquinas

        A arquitetura ARM define um conjunto de instruções básicas, bem como instruções adicionais para coprocessadores. O conjunto de instruções principais pode acessar os registradores principais e o coprocessador pode fornecer registradores para operações adicionais.

3.1.1 Registros principais

        Os conjuntos de instruções ARM e Thumb possuem 16 registradores de 32 bits, R0--R15, e um registrador de status (CPSR).A tabela a seguir mostra as funções de cada registrador.

        Os primeiros quatro registros r0-r3 (a1-a4) são usados ​​para passar valores de parâmetros para sub-rotinas e retornar valores de resultados de funções. Eles também podem ser usados ​​para salvar valores intermediários dentro de rotinas (mas geralmente apenas entre chamadas de sub-rotinas).

        O registro r12 (IP) pode ser usado pelo vinculador como um registro temporário entre a rotina e quaisquer sub-rotinas chamadas (consulte Uso de IP pelo vinculador para obter detalhes). Também pode ser usado dentro de uma rotina para salvar valores intermediários entre chamadas de sub-rotinas.

        Em algumas variantes, o registrador r11 (FP) pode ser usado como um ponteiro de quadro para vincular os registros de ativação de quadro em uma lista vinculada.

        A função do registrador r9 depende da plataforma. A plataforma virtual pode atribuir qualquer função a este registo e deve documentar esta utilização. Por exemplo, ele pode ser especificado como um endereço base estático (SB) em um modelo de dados sem posição ou como um registro de thread (TR) em um ambiente com armazenamento local de thread. O uso deste registrador pode ser necessário para manter seu valor persistente entre todas as chamadas. Plataformas virtuais que não precisam desse registro especial podem especificar r9 como um registro variável v6 salvo pelo receptor adicional.

        Normalmente, os registradores r4-r8, r10 e r11 (v1-v5, v7 e v8) são utilizados para armazenar os valores das variáveis ​​locais da rotina. Entre eles, apenas v1-v4 pode ser usado uniformemente por todo o conjunto de instruções Thumb, mas o AAPCS não exige que o código Thumb use apenas esses registros.

        A sub-rotina deve preservar o conteúdo dos registradores r4-r8, r10, r11 e SP (na variante PCS, r9 também é preservado quando r9 é especificado como v6).

        Em todas as variantes do padrão de chamada de procedimento, os registradores r12-r15 têm um papel especial. Entre essas funções, elas são denominadas IP, SP, LR e PC.

CPSR é um registro global com as seguintes propriedades:

  • Ao entrar ou retornar da interface pública, os bits N, Z, C, V e Q (bits 27-31) e os bits GE[3:0] (bits 16-19) são indefinidos. Os bits Q e GE[3:0] podem ser modificados somente quando executados em um processador que suporte esses recursos.

  • Na arquitetura Arm 6, o bit E (bit 8) pode ser usado para alterar temporariamente o endianness dos acessos de dados à memória em aplicativos que executam o modo little-endian ou no modo big-endian-8. O aplicativo deve ter o endianness especificado e a configuração do bit E deve corresponder ao endianness especificado do aplicativo ao entrar e sair de qualquer interface pública.

  • O bit T (bit 5) e o bit J (bit 24) são bits de status de execução. Somente instruções projetadas especificamente para modificar esses bits podem alterá-los.

  • Os bits A, I, F e M[4:0] (bits 0-7) são bits privilegiados e só podem ser modificados por aplicativos projetados para serem executados explicitamente em modo privilegiado.

  • Todos os outros bits são reservados e não devem ser modificados. Não está definido se esses bits são lidos como zero ou um, ou se permanecem inalterados nas interfaces públicas.

3.1.1.1 Tratamento de valores maiores que 32 bits

        Tipos básicos maiores que 32 bits podem ser passados ​​como argumentos para chamadas de função ou retornados como resultado de chamadas de função. Quando esses tipos estão em registros principais, aplicam-se as seguintes regras:

  • Os tipos de tamanho Dword são passados ​​em dois registros consecutivos (por exemplo, r0 e r1 ou r2 e r3). O conteúdo do registrador é como se o valor fosse carregado da representação da memória usando uma única instrução LDM.

  • Um vetor conteinerizado de 128 bits é passado em quatro registros consecutivos. O conteúdo do registrador é como se o valor tivesse sido carregado da memória usando uma única instrução LDM.

3.2 Processo, memória e pilha

        AAPCS se aplica a um único thread ou processo de execução (doravante denominado coletivamente como processo). Um processo tem um estado de programa definido pelos registradores subjacentes da máquina e pelo conteúdo da memória que ele pode acessar. A quantidade de memória que um processo é capaz de acessar sem causar falhas no tempo de execução pode mudar durante a execução do processo. A memória de um processo geralmente se enquadra em cinco categorias:

  • O código (o programa que está sendo executado) deve ser legível pelo processo, mas não precisa ser gravável por ele.

  • Leia apenas dados estáticos.

  • Dados estáticos graváveis.

  • pilha.

  • pilha.

        Os dados estáticos graváveis ​​podem ser divididos em dados inicializados, inicializados com zero e não inicializados. Com exceção da pilha, não há exigência de que cada classe de memória ocupe uma única região contígua de memória. Um processo deve sempre ter algum código e uma pilha, mas não precisa ter nenhuma outra categoria de memória.

        O heap é uma área de memória gerenciada pelo próprio processo (por exemplo, usando a função malloc do C). Normalmente usado para criar objetos de dados dinâmicos.

        Um programa em conformidade deve executar apenas instruções em uma região da memória designada como contendo código.

3.2.1 Pilha

        A pilha é uma área contígua de memória que pode ser usada para armazenar variáveis ​​locais e passar parâmetros adicionais para sub-rotinas quando o registro de parâmetros for insuficiente.

        A implementação da pilha é completamente descendente e o intervalo atual da pilha é salvo no registro SP (r13). A pilha geralmente possui um endereço base e um endereço limite, embora na prática a aplicação possa não ser capaz de determinar o valor numérico exato de qualquer um dos valores.

        A pilha pode ter um tamanho fixo ou pode ser expansível dinamicamente (ajustando o limite da pilha para baixo).

        As regras para manter uma pilha são divididas em duas partes: um conjunto de restrições que devem ser respeitadas em todos os momentos e uma restrição adicional que deve ser respeitada na interface pública.

3.2.1.1 Restrições gerais de pilha

As seguintes restrições básicas sempre precisam ser atendidas:

  • Limite de pilha ≤ SP ≤ endereço base da pilha. O ponteiro da pilha deve estar dentro do escopo da pilha.

  • SP mod 4 = 0. A pilha deve estar sempre alinhada aos limites das palavras.

  • Um processo só pode armazenar dados dentro de um intervalo fechado de toda a pilha, limitado por [SP, endereço base da pilha - 1] (onde SP é o valor do registrador r13).

3.2.1.2 Restrições de pilha de interfaces públicas

Na interface pública, a pilha também deve obedecer às seguintes restrições: SP mod 8 = 0. A pilha deve estar alinhada com palavras duplas.

3.2.1.3 Sondagem de pilha

        Para garantir a integridade da pilha, um processo pode emitir uma investigação de pilha imediatamente antes de alocar espaço adicional na pilha (passando de SP_old para SP_new). A investigação da pilha deve estar na área [SP_new, SP_old - 1] e pode ser uma operação de leitura ou gravação. O intervalo mínimo para sondagem de pilha é definido pela plataforma de destino, mas deve ser de pelo menos 4K bytes. Nenhum dado recuperável pode ser salvo abaixo da área de pilha atualmente alocada.

3.2.1.4 Ponteiro de quadro

        A plataforma pode exigir a construção de uma lista de quadros de pilha que descreva a hierarquia de chamadas atual no programa. Cada quadro deve ser vinculado ao quadro do chamador por meio de um registro de quadro usando dois valores de 32 bits na pilha. O registro do quadro mais interno (pertencente à chamada de rotina mais recente) deve ser apontado pelo registrador Frame Pointer (FP). A palavra com endereço mais baixo deve apontar para o registro do quadro anterior, e a palavra com endereço mais alto deve conter o valor passado para LR ao entrar na função atual. O final da cadeia de registro do quadro é indicado pelo endereço do quadro anterior sendo zero. A posição do registro do quadro no quadro da pilha não é especificada. O registro do ponteiro do quadro não pode ser atualizado até que o novo registro do quadro seja completamente construído.

3.3 Chamada de sub-rotina

        Ambos os conjuntos de instruções Arm e Thumb contêm uma instrução básica de chamada de sub-rotina BL para executar operações de ramificação e link. O efeito da execução de BL é transferir o próximo valor sequencial do contador de programa, o endereço de retorno, para o registrador de link (LR) e o endereço de destino para o contador de programa (PC). O bit 0 do registrador de link será definido como 1 se a instrução BL for executada no estado Thumb, ou como 0 se executada no estado Arm. O resultado é uma transferência de controle para o endereço de destino, passando o endereço de retorno em LR como um argumento adicional para a sub-rotina chamada.

        Quando o endereço de retorno for carregado de volta no PC (veja Interação), o controle retornará para a instrução seguindo o BL.

3.4 Retornar resultados

A maneira como uma função retorna um resultado depende do tipo do resultado. Para padrões básicos:

  1. Os tipos de ponto flutuante de meia precisão retornam os 16 bits menos significativos em r0.

  2. Tipos de dados primitivos menores que 4 bytes são estendidos por zero ou estendidos por sinal para uma palavra e retornados em r0.

  3. Tipos de dados primitivos do tamanho de palavras (por exemplo, int, float) são retornados em r0.

  4. Tipos de dados primitivos de tamanho Dword (por exemplo, vetores longlong, double e contêineres de 64 bits) são retornados em r0 e r1.

  5. Vetores em contêineres de 128 bits são retornados em r0-r3.

  6. Tipos compostos com até 4 bytes são retornados em r0. O formato é como armazenar o resultado em um endereço alinhado por palavra na memória e depois carregá-lo em r0 usando a instrução LDR. Quaisquer bits em r0 que estejam fora do intervalo do resultado terão um valor não especificado.

  7. Tipos compostos maiores que 4 bytes, ou cujo tamanho não pode ser determinado estaticamente pelo chamador e pelo receptor, serão armazenados na memória com seu endereço como um parâmetro extra passado ao chamar a função (Passagem de Parâmetros, PCS Básico, Regra A.4). A memória utilizada pelo resultado pode ser modificada a qualquer momento durante a chamada da função.

3.5 Passagem de parâmetros

        O padrão básico fornece mecanismos para passar parâmetros nos registros principais (r0-r3) e na pilha. Para sub-rotinas que requerem um pequeno número de parâmetros, apenas registros são utilizados, reduzindo bastante o custo de sua chamada. A passagem de parâmetros é definida como um modelo conceitual de dois níveis:

  1. Mapeamento de parâmetros de linguagem de origem para tipos de máquina.

  2. Organize o tipo de máquina para produzir a lista de parâmetros final.

        O mapeamento das linguagens de origem para os tipos de máquina é específico para cada linguagem e descrito em documentos separados (as ligações das linguagens C e C++ são descritas em Arm C and C++ Language Maps). O resultado é uma lista ordenada de argumentos a serem passados ​​para a sub-rotina.

        Na descrição a seguir, presume-se que existam muitos coprocessadores disponíveis para passagem e recebimento de parâmetros. Os registros do coprocessador são divididos em diferentes categorias. Um argumento pode ser candidato a no máximo uma classe de registro de coprocessador. Os parâmetros adequados para alocação a um registro de coprocessador são chamados de candidatos a registro de coprocessador (CPRC). No padrão base, não há parâmetros adequados para a classe de registro do coprocessador.

        Uma função variadica é sempre organizada de uma forma padrão básica.

        Para o chamador, presume-se que foi alocado espaço de pilha suficiente para acomodar os parâmetros empilhados antes da orquestração: na verdade, a quantidade de espaço de pilha necessária não é conhecida até que a orquestração de parâmetros seja concluída. O chamador pode modificar qualquer espaço de pilha usado para receber valores de parâmetros do chamador.

        Quando um parâmetro de tipo composto é atribuído (total ou parcialmente) a um registrador principal, ele se comporta como se o parâmetro tivesse sido armazenado em um endereço alinhado por palavra (4 bytes) na memória e então carregado em registradores contíguos usando o load multi apropriado. -registrar instrução.

4. Resumo

        Este artigo é de natureza de anotações. No artigo, o blogueiro selecionou apenas uma parte dos registros. No entanto, o inglês não é muito bom e o nível de compreensão e tradução provavelmente não é suficiente. Os alunos interessados ​​​​podem ler a versão original. O blogueiro faz o upload para seu armazém de nuvem de código, link da nuvem de código: https://gitee.com/zichuanning520/htq_library

Link para a versão mais recente:GitHub - ARM-software/abi-aa: Interface binária de aplicativo para a arquitetura Arm®

Acho que você gosta

Origin blog.csdn.net/zichuanning520/article/details/133972203
Recomendado
Clasificación