Partilhar via


Tratamento de Exceções do ARM

O Windows no ARM usa o mesmo mecanismo estruturado de tratamento de exceções para exceções assíncronas geradas por hardware e exceções síncronas geradas por software. Os manipuladores de exceção específicos do idioma são criados com base no tratamento de exceções estruturadas do Windows usando funções auxiliares de linguagem. Este documento descreve o tratamento de exceções no Windows em ARM e os auxiliares de linguagem gerados pelo Microsoft ARM assembler e pelo compilador MSVC.

Tratamento de Exceções do ARM

O Windows em ARM usa códigos de desenrolamento para controlar o desenrolamento da pilha durante o tratamento de exceções estruturadas (SEH). Os códigos de desenrolar são uma sequência de bytes armazenados na .xdata seção da imagem executável. Estes códigos descrevem o funcionamento do prólogo de função e do código do epílogo de uma forma abstrata. O manipulador os usa para desfazer os efeitos do prólogo da função quando ele se desenrola para o quadro de pilha do chamador.

O ARM EABI (embedded application binary interface) especifica um modelo para desenrolamento de exceção que usa códigos de desenrolamento. O modelo não é suficiente para o processo de desenmaraçamento do SEH no Windows. Ele deve lidar com casos assíncronos em que o processador está no meio do prólogo ou epílogo de uma função. O Windows também separa o controle de desenrolamento em desenrolamento no nível de função e desenrolamento de escopo específico do idioma, que é unificado no ARM EABI. Por esses motivos, o Windows no ARM especifica mais detalhes para os dados e o procedimento de desenrolamento.

Suposições

As imagens executáveis para Windows no ARM usam o formato PE (Portable Executable). Para obter mais informações, consulte Formato PE. As informações de tratamento de exceções são armazenadas nas seções .pdata e .xdata da imagem.

O mecanismo de tratamento de exceções faz certas suposições sobre o código que segue o ABI para Windows no ARM:

  • Quando uma exceção ocorre dentro do corpo de uma função, o manipulador pode desfazer as operações do prólogo, ou fazer as operações do epílogo de maneira avançada. Ambos devem produzir resultados idênticos.

  • Prólogos e epílogos tendem a espelhar-se mutuamente. Esse recurso pode ser usado para reduzir o tamanho dos metadados necessários para descrever o desenrolamento.

  • As funções tendem a ser relativamente pequenas. Várias otimizações dependem dessa observação para o empacotamento eficiente de dados.

  • Se uma condição é colocada em um epílogo, ela se aplica igualmente a cada instrução no epílogo.

  • Se o prólogo salvar o ponteiro de pilha (SP) em outro registo, esse registo deve permanecer inalterado durante toda a função, para que o SP original possa ser recuperado a qualquer momento.

  • A menos que o SP seja salvo em outro registro, toda manipulação dele deve ocorrer estritamente dentro do prólogo e do epílogo.

  • Para desenrolar qualquer quadro de pilha, estas operações são necessárias:

    • Ajuste r13 (SP) em incrementos de 4 bytes.

    • Pop um ou mais registros inteiros.

    • Pop um ou mais registros VFP (ponto flutuante virtual).

    • Copie um valor de registro arbitrário para r13 (SP).

    • Carregue a controladora de armazenamento da pilha usando uma pequena operação pós-decréscimo.

    • Analise um dos poucos tipos de quadros bem definidos.

.pdata Registos

Os .pdata registros em uma imagem em formato PE são uma matriz ordenada de itens de comprimento fixo que descrevem cada função de manipulação de pilha. As funções folha (funções que não chamam outras funções) não exigem .pdata registros quando não manipulam a pilha. (Ou seja, eles não exigem nenhum armazenamento local e não precisam salvar ou restaurar registros não voláteis.) Os registros para essas funções podem ser omitidos da .pdata seção para economizar espaço. Uma operação de desenrolar de uma dessas funções pode simplesmente copiar o endereço de retorno do Link Register (LR) para o contador de programas (PC) para ir até o chamador.

Cada .pdata registro para ARM tem 8 bytes de comprimento. O formato geral de um registo coloca o endereço virtual relativo (RVA) do início da função na primeira palavra de 32 bits, seguido de uma segunda palavra que contém um ponteiro para um bloco de comprimento variável .xdata ou uma palavra compactada que descreve uma sequência de desenrolamento de função canónica, conforme mostrado nesta tabela:

Deslocamento de palavras Bits (unidade de informação digital) Propósito
0 0-31 Function Start RVA é o RVA de 32 bits do início da função. Se a função contiver código Thumb, o bit baixo deste endereço deve ser ajustado.
1 0-1 Flag é um campo de 2 bits que indica como interpretar os 30 bits restantes da segunda .pdata palavra. Se Flag for 0, então os bits restantes formam um RVA de Informações de Exceção (com os dois bits baixos implicitamente 0). Se Flag for diferente de zero, os bits restantes formam uma estrutura de dados de desenrolar compactados .
1 2-31 Informações de exceção RVA ou dados de desenrolamento compactados.

Exception Information RVA é o endereço da estrutura de informações de exceção de comprimento variável, armazenada na seção .xdata. Os dados devem estar alinhados em 4 bytes.

Packed Unwind Data é uma descrição compactada das operações necessárias para desempacotar de uma função, pressupondo uma forma canônica. Neste caso, não é necessário o registo .xdata.

Dados de descompactação compactados

Para funções cujos prólogos e epílogos seguem a forma canônica descrita abaixo, dados de desenrolamento compactados podem ser usados. Ele elimina a necessidade de um .xdata registo e reduz significativamente o espaço necessário para fornecer os dados de desempacotamento. Os prólogos e epílogos canônicos são projetados para atender aos requisitos comuns de uma função simples que não requer um manipulador de exceções e executa suas operações de configuração e desmontagem em uma ordem padrão.

Esta tabela mostra o formato de um .pdata registo que tem dados de desenrolamento compactados.

Deslocamento de palavras Bits Propósito
0 0-31 Function Start RVA é o RVA de 32 bits do início da função. Se a função contiver código Thumb, o bit baixo deste endereço deve ser ajustado.
1 0-1 Flag é um campo de 2 bits que tem estes significados:

- 00 = dados de desenrolamento compactados não utilizados; os bits restantes apontam para o registo .xdata.
- 01 = dados de desenrolamento compactados.
- 10 = dados de desenrolamento comprimidos em que se presume que a função não tem prólogo. Isso é útil para descrever fragmentos de função que são descontíguos com o início da função.
- 11 = reservado.
1 2-12 Function Length é um campo de 11 bits que fornece o comprimento de toda a função em bytes divididos por 2. Se a função for maior que 4K bytes, um registro completo .xdata deve ser usado.
1 13-14 Ret é um campo de 2 bits que indica como a função retorna:

- 00 = retorno via pop {pc} (o bit da L bandeira deve ser definido como 1 neste caso).
- 01 = retorno usando uma ramificação de 16 bits.
- 10 = retorno usando uma ramificação de 32 bits.
- 11 = nenhum epílogo. Isto é útil para descrever um fragmento de função descontígua que pode conter apenas um prólogo, mas cujo epílogo está em outro lugar.
1 15 H é um sinalizador de 1 bit que indica se a função armazena os registos dos parâmetros inteiros (r0-r3) empurrando-os no início da função e desaloca os 16 bytes de pilha antes de retornar. (0 = não regista casas, 1 = registos de casas.)
1 16-18 Reg é um campo de 3 bits que indica o índice do último registro não volátil salvo. Se o R bit for 0, então apenas registos inteiros estão a ser guardados e presumem-se que estão no intervalo de r4-rN, onde N é igual a 4 + Reg. Se o R bit for 1, então apenas registos de ponto flutuante estão a ser guardados, e presume-se que estejam no intervalo de d8-dN, onde N é igual a 8 + Reg. A combinação especial de R = 1 e Reg = 7 indica que nenhum registro é salvo.
1 19 R é um sinalizador de 1 bit que indica se os registradores não voláteis salvos são registros inteiros (0) ou registros de ponto flutuante (1). Se R estiver definido como 1 e o Reg campo estiver definido como 7, nenhum registro não volátil foi enviado.
1 20 L é um sinalizador de 1 bit que indica se a função salva/restaura LR, juntamente com outros registros indicados pelo Reg campo. (0 = não salva/restaura, 1 = salva/restaura.)
1 21 C é um sinalizador de 1 bit que indica se a função inclui instruções adicionais para configurar uma cadeia de frames para percorrer rapidamente a pilha (1) ou não (0). Se esse bit for definido, r11 será implicitamente adicionado à lista de registros inteiros não voláteis salvos. (Consulte as restrições abaixo se o C sinalizador for usado.)
1 22-31 Stack Adjust é um campo de 10 bits que indica o número de bytes de pilha alocados para esta função, dividido por 4. No entanto, apenas valores entre 0x000-0x3F3 podem ser codificados diretamente. As funções que alocam mais de 4044 bytes de pilha devem usar um registro completo .xdata . Se o Stack Adjust campo for 0x3F4 ou maior, então os 4 bits baixos têm um significado especial:

- Bits 0-1 indicam o número de palavras de ajustamento da pilha (1-4) menos 1.
- O bit 2 é definido como 1 se o prólogo integrou este ajuste na sua operação de empurrar.
- O bit 3 é definido como 1 se o epílogo combinou esse ajuste em sua operação pop.

Devido a possíveis redundâncias nas codificações acima, estas restrições aplicam-se:

  • Se o C sinalizador estiver definido como 1:

    • O L flag também deve ser definido como 1, porque a ligação de frames requer tanto r11 quanto LR.

    • R11 não deve ser incluído no conjunto de registos descrito por Reg. Ou seja, se r4-r11 são empurrados, Reg deve descrever apenas r4-r10, porque a C bandeira implica r11.

  • Se o Ret campo estiver definido como 0, o L sinalizador deve ser definido como 1.

A violação dessas restrições causa uma sequência sem suporte.

Para fins da discussão abaixo, duas pseudo-bandeiras derivam de Stack Adjust:

  • PF ou "prólogo dobrado" indica que Stack Adjust é 0x3F4 ou maior e o bit 2 está definido.

  • EF ou "dobragem do epílogo" indica que Stack Adjust é 0x3F4 ou maior e o bit 3 está ativado.

Os prólogos para funções canônicas podem ter até 5 instruções (observe que 3a e 3b são mutuamente exclusivos):

Instrução Opcode é assumido presente se: Tamanho Opcode Códigos de desenrolamento
1 H==1 16 push {r0-r3} 04
2 C==1 ou L==1 ou R==0 ou PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3 bis C==1 e (R==1 e PF==0) 16 mov r11,sp FB
3 ter C==1 e (R==0 ou PF==1) 32 add r11,sp,#xx FC
4 R==1 e Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 e PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

A instrução 1 estará sempre presente se o H bit estiver definido como 1.

Para configurar o encadeamento de quadros, a instrução 3a ou 3b estará presente se estiver definido o bit C. É um 16-bit mov se nenhum registro além de r11 e LR são empurrados, caso contrário, é um 32-bit add.

Se for especificado um ajuste sem dobragem, a instrução 5 será o ajuste explícito da pilha.

As instruções 2 e 4 são definidas com base na necessidade ou não de um push. Esta tabela resume quais registros são salvos com base nos Ccampos , L, Re PF . Em todos os casos, N é igual a Reg + 4, E é igual a Reg + 8, e S é igual a (~Stack Adjust) & 3.

C L R PF Registros inteiros enviados por push Registos VFP empurrados
0 0 0 0 R4 - R*N* nenhuma
0 0 0 1 r*S* - r*N* nenhuma
0 0 1 0 nenhuma d8 - d*E*
0 0 1 1 r*S* - r3 d8 - d*E*
0 1 0 0 r4 - r*N*, LR nenhuma
0 1 0 1 r*S* - r*N*, LR nenhuma
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (codificação inválida) (codificação inválida)
1 0 0 1 (codificação inválida) (codificação inválida)
1 0 1 0 (codificação inválida) (codificação inválida)
1 0 1 1 (codificação inválida) (codificação inválida)
1 1 0 0 r4 - r*N*, r11, LR nenhuma
1 1 0 1 r*S* - r*N*, r11, LR nenhuma
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

Os epílogos para funções canônicas seguem uma forma semelhante, mas em sentido inverso e com algumas opções adicionais. O epílogo pode ter até 5 instruções, e sua forma é estritamente ditada pela forma do prólogo.

Instrução Opcode é assumido presente se: Tamanho Opcode
6 Stack Adjust!=0 e EF==0 16/32 add sp,sp,#xx
7 R==1 e Reg!=7 32 vpop {d8-dE}
8 C==1 ou (L==1 e (H==0 ou Ret !=0)) ou R==0 ou EF==1 16/32 pop {registers}
9º-A H==1 e (L==0 ou Ret!=0) 16 add sp,sp,#0x10
9 ter H==1 e L==1 e Ret==0 32 ldr pc,[sp],#0x14
10º-A Ret==1 16 bx reg
10 ter Ret== 2 32 b address

A instrução 6 é o ajuste explícito da pilha quando é especificado um ajuste não dobrado. Porque PF é independente de EF, é possível ter a instrução 5 presente sem a instrução 6, ou vice-versa.

As instruções 7 e 8 usam a mesma lógica do prólogo para determinar quais registos são restaurados da pilha, mas com estas três mudanças: primeiro, EF é usado no lugar de PF; segundo, se Ret = 0 e H = 0, então LR é substituído por PC na lista de registos e o epílogo termina imediatamente; terceiro, se Ret = 0 e H = 1, então LR é omitido da lista de registos e removido pela instrução 9b.

Se H estiver definido, então a instrução 9a ou 9b está presente. A instrução 9a é usada quando Ret é diferente de zero, o que também implica a presença de 10a ou 10b. Se L=1, então LR foi exibido como parte da instrução 8. A instrução 9b é usada quando L é 1 e Ret é zero, para indicar um término antecipado do epílogo. Além disso, serve para retornar e ajustar a pilha ao mesmo tempo.

Se o epílogo ainda não terminou, então a instrução 10a ou 10b está presente, para indicar uma ramificação de 16 bits ou 32 bits, com base no valor de Ret.

.xdata Registos

Quando o formato de desenrolar compactado é insuficiente para descrever o desenrolamento de uma função, deve ser criado um registo .xdata de comprimento variável. O endereço deste registo é armazenado na segunda palavra do registo .pdata. O formato do .xdata é um conjunto compactado de palavras de comprimento variável com quatro secções.

  1. Um cabeçalho de 1 ou 2 palavras que descreve o tamanho geral da .xdata estrutura e fornece dados de função chave. A segunda palavra só estará presente se os campos Contagem de Epílogos e Palavras de Código estiverem ambos definidos como 0. Os campos estão divididos nesta tabela:

    Palavra Bits Propósito
    0 0-17 Function Length é um campo de 18 bits que indica o comprimento total da função em bytes, dividido por 2. Se uma função for maior que 512 KB, vários .pdata e .xdata registros devem ser usados para descrever a função. Para obter detalhes, consulte a seção Grandes funções neste documento.
    0 18-19 Vers é um campo de 2 bits que descreve a versão do restante.xdata. Apenas a versão 0 está atualmente definida; valores de 1-3 são reservados.
    0 20 X é um campo de 1 bit que indica a presença (1) ou ausência (0) de dados de exceção.
    0 21 E é um campo de 1 bit que indica que as informações que descrevem um único epílogo são compactadas no cabeçalho (1) em vez de exigir palavras de escopo adicionais posteriormente (0).
    0 22 F é um campo de 1 bit que indica que este registo descreve um fragmento de função (1) ou uma função completa (0). Um fragmento implica que não há prólogo e que todo o processamento do prólogo deve ser ignorado.
    0 23-27 Contagem de Epílogo é um campo de 5 bits que tem dois significados, dependendo do estado do E bit:

    - Se E é 0, este campo é uma contagem do número total de âmbitos de epílogo descritos na secção 2. Se existirem mais de 31 escopos na função, esse campo e o campo Palavras de código devem ser definidos como 0 para indicar que uma palavra de extensão é necessária.
    - Se E for 1, este campo especifica o índice do primeiro código de desenrolar que descreve o único epílogo.
    0 28-31 Code Words é um campo de 4 bits que especifica o número de palavras de 32 bits necessárias para conter todos os códigos de desenrolar mencionados na seção 4. Se forem necessárias mais de 15 palavras para mais de 63 bytes de código de desenrolamento, este campo e o campo Contagem de epílogos devem ser definidos como 0 para indicar que uma palavra de extensão é necessária.
    1 0-15 A Contagem de Epílogos Estendida é um campo de 16 bits que fornece mais espaço para codificar um número excepcionalmente grande de epílogos. A palavra de extensão que contém este campo só estará presente se os campos Contagem de Epílogo e Palavras de Código na primeira palavra do cabeçalho estiverem ambos definidos como 0.
    1 16-23 Extended Code Words é um campo de 8 bits que fornece mais espaço para codificar um número anormalmente grande de palavras de código de desenrolamento. A palavra de extensão que contém este campo só estará presente se os campos Contagem de Epílogo e Palavras de Código na primeira palavra do cabeçalho estiverem ambos definidos como 0.
    1 24-31 Reservado
  2. Depois dos dados de exceção (se o bit E no cabeçalho foi definido como 0) há uma lista de informações sobre escopos de epílogo, que são empacotados um por palavra e armazenados pela ordem crescente do deslocamento inicial. Cada escopo contém estes campos:

    Bits Propósito
    0-17 O Deslocamento de Início do Epílogo é um campo de 18 bits que descreve o deslocamento do epílogo, em bytes divididos por 2, em relação ao início da função.
    18-19 Res é um campo de 2 bits reservado para expansão futura. Seu valor deve ser 0.
    20-23 Condition é um campo de 4 bits que dá a condição sob a qual o epílogo é executado. Para epílogos incondicionais, deve ser definido como 0xE, que indica "sempre". (Um epílogo deve ser inteiramente condicional ou inteiramente incondicional, e no modo Thumb-2, o epílogo começa com a primeira instrução após o opcode IT.)
    24-31 Epilogue Start Index é um campo de 8 bits que indica o índice de bytes do primeiro código de desenrolar que descreve este epílogo.
  3. Após a lista de âmbitos de epílogo, vem uma matriz de bytes que contêm códigos de desenrolamento, que são descritos em detalhes na seção Códigos de Desenrolamento neste artigo. Esta matriz é preenchida no final até ao mais próximo limite de palavra completa. Os bytes são armazenados em ordem little-endian para que possam ser buscados diretamente no modo little-endian.

  4. Se o campo X no cabeçalho for 1, os bytes de código de desenrolar serão seguidos pelas informações do manipulador de exceções. Isso consiste em um RVA do manipulador de exceções que contém o endereço do manipulador de exceções, seguido imediatamente pela quantidade (de comprimento variável) de dados exigida pelo manipulador de exceções.

O .xdata registro é projetado para que seja possível buscar os primeiros 8 bytes e calcular o tamanho total do registro, sem incluir o comprimento dos dados de exceção de tamanho variável que se seguem. Este trecho de código calcula o tamanho do registro:

ULONG ComputeXdataSize(PULONG Xdata)
{
    ULONG Size;
    ULONG EpilogueScopes;
    ULONG UnwindWords;

    if ((Xdata[0] >> 23) != 0) {
        Size = 4;
        EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
        UnwindWords = (Xdata[0] >> 28) & 0x0f;
    } else {
        Size = 8;
        EpilogueScopes = Xdata[1] & 0xffff;
        UnwindWords = (Xdata[1] >> 16) & 0xff;
    }

    if (!(Xdata[0] & (1 << 21))) {
        Size += 4 * EpilogueScopes;
    }

    Size += 4 * UnwindWords;

    if (Xdata[0] & (1 << 20)) {
        Size += 4;  // Exception handler RVA
    }

    return Size;
}

Embora o prólogo e cada epílogo tenham um índice para os códigos de desanuviamento, a tabela é compartilhada entre eles. Não é incomum que todos eles possam compartilhar os mesmos códigos de desenrolamento. Recomendamos que os desenvolvedores de compiladores otimizem para este caso, porque o maior índice que pode ser especificado é 255, e isso limita o número total de códigos de desenrolamento possíveis para uma função específica.

Códigos de desenrolamento

A matriz de códigos de desenrolamento é um conjunto de sequências de instruções que descrevem exatamente como desfazer os efeitos do prólogo, na ordem em que as operações devem ser desfeitas. Os códigos de desempacotamento são um mini conjunto de instruções, codificados como uma cadeia de bytes. Quando a execução é concluída, o endereço de retorno para a função de chamada está no registro LR, e todos os registradores não voláteis são restaurados para seus valores no momento em que a função foi chamada.

Se as exceções fossem garantidas para ocorrer apenas dentro de um corpo de função, e nunca dentro de um prólogo ou epílogo, então apenas uma sequência de desanuviamento seria necessária. No entanto, o modelo de desenrolamento do Windows requer a capacidade de se desenrolar de dentro de um prólogo ou epílogo parcialmente executado. Para acomodar este requisito, os códigos de desenrolar foram cuidadosamente projetados para ter um mapeamento um-para-um inequívoco para cada opcode relevante no prólogo e no epílogo. Isto tem várias implicações:

  • É possível calcular o comprimento do prólogo e do epílogo contando o número de códigos de desenrolamento. Isso é possível mesmo com instruções Thumb-2 de comprimento variável porque há mapeamentos distintos para opcodes de 16 bits e 32 bits.

  • Ao contar o número de instruções após o início de um escopo de epílogo, é possível pular o número equivalente de códigos de desenrolar e executar o resto de uma sequência para completar o desenrolar parcialmente executado que o epílogo estava executando.

  • Ao contar o número de instruções antes do final do prólogo, é possível saltar o número equivalente de códigos de desempacotamento e executar o resto da sequência para desfazer apenas as partes do prólogo que já concluíram a execução.

A tabela a seguir mostra o mapeamento de códigos de desempacotamento para opcodes. Os códigos mais comuns são apenas um byte, enquanto os menos comuns requerem dois, três ou até quatro bytes. Cada código é armazenado do byte mais significativo para o byte menos significativo. A estrutura dos códigos de desenrolamento difere da codificação descrita no ARM EABI, porque estes códigos de desenrolamento são projetados para ter um mapeamento um para um com os opcodes no prólogo e epílogo para permitir o desenrolamento de prólogos e epílogos parcialmente executados.

Byte 1 Byte 2 Byte 3 Byte 4 Opsize Explicação
00-7F 16 add sp,sp,#X

onde X é (código & 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

onde LR é removido se Code & 0x2000 e r0-r12 são removidos se o bit correspondente estiver definido em Code & 0x1FFF
C0-CF 16 mov sp,rX

onde X é Código & 0x0F
D0-D7 16 pop {r4-rX,lr}

onde X é (Código & 0x03) + 4 e LR é exibido se Código & 0x04
D8-DF 32 pop {r4-rX,lr}

onde X é (Código & 0x03) + 8 e LR é exibido se Código & 0x04
E0-E7 32 vpop {d8-dX}

onde X é (Código & 0x07) + 8
E8-EB 00-FF 32 addw sp,sp,#X

onde X é (Código & 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

onde LR é desempilhado se Code & 0x0100 e r0-r7 são desempilhados se o bit correspondente for definido em Code & 0x00FF
EE 00-0F 16 Específico da Microsoft
EE 10-FF 16 Disponível
EF 00-0F 32 ldr lr,[sp],#X

onde X é (Código & 0x000F) * 4
EF 10-FF 32 Disponível
F0-F4 - Disponível
F5 00-FF 32 vpop {dS-dE}

onde S é (Código & 0x00F0) >> 4 e E é Código & 0x000F
F6 00-FF 32 vpop {dS-dE}

onde S é ((Código & 0x00F0) >> 4) + 16 e E é (Código & 0x000F) + 16
F7 00-FF 00-FF 16 add sp,sp,#X

onde X é (Código & 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

onde X é (Código & 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

onde X é (Código & 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

onde X é (Código & 0x00FFFFFF) * 4
FB 16 NOP (16 bits)
FC 32 NOP (32 bits)
DF 16 fim + nop de 16 bits no epílogo
FE 32 fim + nop de 32 bits no epílogo
FF - fim

Isso mostra o intervalo de valores hexadecimais para cada byte num código de desenrolamento Código, juntamente com o tamanho do opcode Opsize e a interpretação correspondente da instrução original. As células vazias indicam códigos de desenrolamento mais curtos. Nas instruções que têm valores grandes cobrindo vários bytes, os bits mais significativos são armazenados primeiro. O campo Opsize mostra o tamanho implícito do opcode associado a cada operação Thumb-2. As entradas duplicadas aparentes na tabela com codificações diferentes são usadas para distinguir entre diferentes tamanhos de opcode.

Os códigos de descompactação são projetados de modo que o primeiro byte do código indique tanto o tamanho total em bytes do código quanto o tamanho do opcode correspondente no fluxo de instruções. Para calcular o tamanho do prólogo ou epílogo, percorra os códigos de desenrolamento do início da sequência até o final e use uma tabela de pesquisa ou método semelhante para determinar quanto tempo é o opcode correspondente.

Os códigos de desenrolamento 0xFD e 0xFE são equivalentes ao código final padrão 0xFF, mas consideram um nop opcode extra no caso do epílogo, seja em 16 bits ou 32 bits. Para prólogos, códigos 0xFD, 0xFE e 0xFF são exatamente equivalentes. Isso explica os finais comuns de epílogo bx lr ou b <tailcall-target>, que não têm uma instrução de prólogo equivalente. Isto aumenta a probabilidade de que as sequências de desenrolamento possam ser compartilhadas entre o prólogo e os epílogos.

Em muitos casos, deve ser possível usar o mesmo conjunto de códigos de desenrolar para o prólogo e todos os epílogos. No entanto, para lidar com o desenrolar de prólogos e epílogos parcialmente executados, você pode ter que ter várias sequências de código de desenrolamento que variam em ordem ou comportamento. É por isso que cada epílogo tem o seu próprio índice na matriz de desempilhamento para mostrar por onde começar a executar.

Desanuviamento de prólogos e epílogos parciais

O caso de desenrolar mais comum é quando a exceção ocorre no corpo da função, longe do prólogo e dos epílogos. Nesse caso, o desenrolador executa os códigos na matriz de desenrolar começando no índice 0 e continua até que um opcode final seja detetado.

Quando uma exceção ocorre enquanto um prólogo ou epílogo está em execução, o quadro da pilha é apenas parcialmente construído, e o desenrolador deve determinar exatamente o que foi feito para desfazê-lo corretamente.

Por exemplo, considere esta sequência de prólogo e epílogo:

0000:   push  {r0-r3}         ; 0x04
0002:   push  {r4-r9, lr}     ; 0xdd
0006:   mov   r7, sp          ; 0xc7
...
0140:   mov   sp, r7          ; 0xc7
0142:   pop   {r4-r9, lr}     ; 0xdd
0146:   add   sp, sp, #16     ; 0x04
0148:   bx    lr

Ao lado de cada opcode está o código de desenrolar apropriado para descrever esta operação. A sequência de códigos de descompactação para o prólogo é uma imagem espelhada dos códigos de descompactação para o epílogo, excluindo a instrução final. Este caso é comum, e é a razão pela qual os códigos de desenrolamento para o prólogo são sempre assumidos como sendo armazenados na ordem inversa da ordem de execução do prólogo. Isso nos dá um conjunto comum de códigos de desenrolamento:

0xc7, 0xdd, 0x04, 0xfd

O código 0xFD é um código especial para o final da sequência, o que significa que o epílogo é uma instrução de 16 bits mais longa do que o prólogo. Isto permite um maior compartilhamento dos códigos de desenrolamento.

No exemplo, se ocorrer uma exceção enquanto o corpo da função entre o prólogo e o epílogo está em execução, o desenrolamento começa com o caso do epílogo, no offset 0 do código do epílogo. Isso corresponde ao deslocamento 0x140 no exemplo. O desenrolador executa a sequência de desenrolamento completo, porque nenhuma limpeza foi feita. Se, em vez disso, a exceção ocorrer uma instrução após o início do código do epílogo, o desenrolador pode desenrolar com êxito ignorando o primeiro código de desenrolamento. Dado um mapeamento um-para-um entre opcodes e códigos de desempacotamento, ao desempacotar a partir da instrução n no epílogo, o mecanismo de desempacotamento deve pular os primeiros n códigos de desempacotamento.

Lógica semelhante funciona ao contrário para o prólogo. Caso se inicie o desenrolar a partir do deslocamento 0 no prólogo, nada precisa ser executado. Se desenrolar a partir de uma instrução, a sequência de desenrolar deve iniciar um código de desenrolar a partir do final, porque os códigos de desenrolamento do prólogo são armazenados na ordem inversa. No caso geral, se desenrolar a partir da instrução n no prólogo, o desenrolamento deve começar a ser executado em n desenrolar códigos a partir do final da lista de códigos.

Os códigos de desenrolamento do prólogo e do epílogo nem sempre correspondem exatamente. Nesse caso, a matriz de código de desenrolamento pode ter que conter várias sequências de códigos. Para determinar o deslocamento para iniciar o processamento de códigos, use esta lógica:

  1. Se estiver a desenrolar a partir do corpo da função, comece a executar códigos de desenrolamento no índice 0 e continue até que um código de operação final seja alcançado.

  2. Se estiver a desenrolar-se a partir de um epílogo, use o índice inicial específico do epílogo fornecido pelo âmbito do epílogo. Calcule quantos bytes o PC tem desde o início do epílogo. Avance pelos códigos de desenrolar até que todas as instruções já executadas sejam consideradas. Iniciar a sequência de desenrolamento a partir desse ponto.

  3. Se estiver a desenrolar a partir do interior do prólogo, comece no índice 0 nos códigos de desenrolamento. Calcule o comprimento do código do prólogo a partir da sequência e, em seguida, calcule quantos bytes o PC tem a partir do final do prólogo. Salte para a frente através dos códigos de desenrolar até que todas as instruções não executadas sejam contabilizadas. Iniciar a sequência de desenrolamento a partir desse ponto.

Os códigos de desenrolamento para o prólogo devem ser sempre os primeiros na matriz. eles também são os códigos usados para relaxar no caso geral de desenrolar de dentro do corpo. Quaisquer sequências de código específicas do epílogo devem seguir-se imediatamente após a sequência de código do prólogo.

Fragmentos de função

Para otimização de código, pode ser útil dividir uma função em partes descontíguas. Quando isso é feito, cada fragmento de função requer seu próprio registro separado .pdata—e, possivelmente, um registro separado .xdata.

Supondo que o prólogo da função está no início da função e não pode ser dividido, há quatro casos de fragmento de função:

  • Apenas prólogo; todos os epílogos em outros fragmentos.

  • Prólogo e um ou mais epílogos; mais epílogos em outros fragmentos.

  • Sem prólogo ou epílogos; prólogo e um ou mais epílogos em outros fragmentos.

  • Apenas epílogos; prólogo e possivelmente mais epílogos em outros fragmentos.

No primeiro caso, apenas o prólogo deve ser descrito. Isto pode ser feito na forma compacta .pdata ao descrever o prólogo normalmente e especificando um Ret valor de 3 para indicar nenhum epílogo. Na forma completa .xdata, isto pode ser feito ao fornecer os códigos de desenrolamento do prólogo no índice 0, como de costume, e especificar uma contagem de epílogo de 0.

O segundo caso é como uma função normal. Se houver apenas um epílogo no fragmento, e ele estiver no final do fragmento, então um registro compacto .pdata pode ser usado. Caso contrário, deve ser utilizado um registo completo .xdata . Tenha em mente que os deslocamentos especificados para o início do epílogo são relativos ao início do fragmento, não ao início original da função.

O terceiro e quarto casos são variantes do primeiro e segundo casos, respectivamente, exceto que não contêm um prólogo. Nessas situações, supõe-se que há código antes do início do epílogo e ele é considerado parte do corpo da função, que normalmente seria desenrolado desfazendo os efeitos do prólogo. Esses casos devem, portanto, ser codificados com um pseudo-prólogo, que descreve como fazer o desenrolamento a partir do interior do corpo, mas que é considerado com comprimento 0 ao determinar se deve ser realizado um desenrolar parcial no início do fragmento. Alternativamente, este pseudo-prólogo pode ser descrito utilizando os mesmos códigos de desenrolamento que o epílogo, pois presumivelmente realizam operações equivalentes.

No terceiro e quarto casos, a presença de um pseudo-prólogo é especificada definindo o Flag campo do registro compacto .pdata como 2 ou definindo a bandeira F no .xdata cabeçalho como 1. Em ambos os casos, a verificação de um desenrolar parcial do prólogo é ignorada, e todos os desenrolamentos sem epílogo são considerados completos.

Grandes funções

Os fragmentos podem ser usados para descrever funções maiores do que o limite de 512 KB imposto pelos campos de bits no .xdata cabeçalho. Para descrever uma função maior, basta dividi-la em fragmentos menores que 512 KB. Cada fragmento deve ser ajustado para não dividir um epílogo em vários pedaços.

Apenas o primeiro fragmento da função contém um prólogo. Todos os outros fragmentos são marcados como não tendo prólogo. Dependendo do número de epílogos, cada fragmento pode conter zero ou mais epílogos. Tenha em mente que cada escopo de epílogo em um fragmento especifica seu deslocamento inicial em relação ao início do fragmento, não ao início da função.

Se um fragmento não tem prólogo nem epílogo, ele ainda requer seu próprio .pdata- e possivelmente .xdata- registro para descrever como relaxar de dentro do corpo da função.

Empacotamento termoencolhível

Um caso especial mais complexo de fragmentos de função é chamado shrink-wrapping. É uma técnica para adiar a salvaguarda de registos desde o início da execução da função para um ponto posterior na função. Otimiza para casos simples que não exigem guardar registadores. Este caso tem duas partes: há uma região externa que aloca o espaço da pilha, mas salva um conjunto mínimo de registros, e uma região interna que salva e restaura outros registros.

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatiles
    sub    sp, sp, #0x100    ; A: allocate all stack space up front
    ...                      ; A:
    add    r0, sp, #0xE4     ; A: prepare to do the inner save
    stm    r0, {r5-r11}      ; A: save remaining non-volatiles
    ...                      ; B:
    add    r0, sp, #0xE4     ; B: prepare to do the inner restore
    ldm    r0, {r5-r11}      ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C:

Normalmente, espera-se que as funções empacotadas por encolhimento pré-aloquem o espaço para os registros extras salvos no prólogo regular e, em seguida, salvem os registros usando str ou stm em vez de push. Essa ação mantém toda a manipulação do ponteiro de pilha no prólogo original da função.

A função shrink-wrapped de exemplo deve ser dividida em três regiões, que são marcadas como A, B e C nos comentários. A primeira A região cobre o início da função até ao final dos armazenamentos não voláteis adicionais. Um .pdata ou .xdata registro deve ser construído para descrever este fragmento como tendo um prólogo e sem epílogos.

A região do meio B recebe o seu próprio .pdata ou .xdata registro que descreve um fragmento que não tem prólogo nem epílogo. No entanto, os códigos de desenrolar para esta região ainda devem estar presentes porque é considerado como um corpo de função. Os códigos devem descrever um prólogo composto que represente tanto os registos originais guardados no prólogo da região A como os registos suplementares guardados antes de entrarem na região B, como se tivessem sido produzidos por uma sequência de operações.

O registo guardado por região B não pode ser considerado como um "prólogo interior" porque o prólogo composto descrito para a região B deve descrever tanto o prólogo da região A como os registos adicionais guardados. Se o fragmento B tivesse um prólogo, os códigos de desenrolar também indicariam o tamanho desse prólogo, e não há forma de descrever o prólogo composto de um modo que corresponda diretamente aos opcodes que se limitam a salvar os registros adicionais.

Os registos extra guardados devem ser considerados parte da região A, porque até que estejam concluídos, o prólogo composto não descreve com precisão o estado da pilha.

A última C região recebe o seu próprio .pdata ou .xdata registro, descrevendo um fragmento que não tem prólogo, mas tem um epílogo.

Uma abordagem alternativa também pode funcionar se a manipulação da pilha feita antes de entrar na região B puder ser reduzida a uma instrução:

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatile registers
    sub    sp, sp, #0xE0     ; A: allocate minimal stack space up front
    ...                      ; A:
    push   {r4-r9}           ; A: save remaining non-volatiles
    ...                      ; B:
    pop    {r4-r9}           ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C: restore non-volatile registers

A principal perceção é que, em cada limite de instrução, a pilha é totalmente consistente com os códigos de desenrolar para a região. Se ocorrer um desanuviamento antes do impulso interno neste exemplo, ele é considerado parte da região A. Apenas o prólogo da região A é desenrolado. Se o descontrair ocorrer após o empurrão interno, é considerado parte da região B, que não tem prólogo. No entanto, tem códigos de desenrolar que descrevem tanto o impulso interno quanto o prólogo original da região A. Lógica semelhante vale para o pop interno.

Otimizações de codificação

A riqueza dos códigos de desenrolamento e a capacidade de fazer uso de formas compactas e expandidas de dados oferecem muitas oportunidades para otimizar a codificação para reduzir ainda mais o espaço. Com o uso agressivo dessas técnicas, a sobrecarga líquida da descrição de funções e fragmentos através de códigos de desenrolamento pode ser minimizada.

A ideia de otimização mais importante: não confunda limites de prólogo e epílogo para fins de desempacotamento com limites lógicos de prólogo e epílogo do ponto de vista do compilador. Os limites de desenrolamento podem ser reduzidos e tornados mais apertados para melhorar a eficiência. Por exemplo, um prólogo pode conter código após a configuração da pilha para realizar verificações. Uma vez concluída toda a manipulação da pilha, não há necessidade de codificar outras operações, podendo qualquer coisa além disso ser removida do prólogo de desenrolamento.

Esta mesma regra aplica-se ao comprimento da função. Se houver dados (como um pool literal) que seguem um epílogo em uma função, eles não devem ser incluídos como parte do comprimento da função. Ao reduzir a função apenas para o código que faz parte da função, as chances são muito maiores de que o epílogo esteja no final e um registro compacto .pdata possa ser usado.

Em um prólogo, uma vez que o ponteiro da pilha é salvo em outro registo, normalmente não há necessidade de gravar mais códigos de operação. Para desenrolar a função, a primeira coisa que é feita é recuperar o SP do registro salvo. Outras operações não têm qualquer efeito sobre o desenrolar.

Epílogos de instrução única não precisam ser codificados, seja como escopos ou como códigos de desenrolamento. Se ocorrer um desempacotamento antes que essa instrução seja executada, então é seguro assumir que é de dentro do corpo da função. Apenas executar os códigos de desenrolamento do prólogo é suficiente. Quando o desenrolar ocorre após a execução de uma única instrução, então, por definição, ele ocorre em outra região.

Os epílogos de múltiplas instruções não precisam codificar a primeira instrução do epílogo, pela mesma razão do ponto anterior: se o desenrolar ocorre antes que a instrução seja executada, um desenrolar completo do prólogo é suficiente. Se o desenrolar ocorrer após essa instrução, apenas as operações posteriores devem ser consideradas.

A reutilização de código 'unwind' deve ser agressiva. O índice que cada escopo de epílogo especifica aponta para um ponto de partida arbitrário na matriz de códigos de desenrolamento. Não precisa apontar para o início de uma sequência anterior; pode apontar para o meio. A melhor abordagem é gerar a sequência de código de desenrolamento. Em seguida, pesquise uma coincidência exata de bytes no pool de sequências já codificado. Use qualquer combinação perfeita como ponto de partida para a reutilização.

Depois que os epílogos de instrução única são ignorados, se não houver epílogos restantes, considere usar uma forma compacta .pdata , que se torna muito mais provável na ausência de um epílogo.

Exemplos

Nesses exemplos, a base da imagem está em 0x00400000.

Exemplo 1: Função Folha, Sem Locais

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x000535F8 (= 0x004535F8-0x00400000)
  • Palavra 1

    • Flag = 1, indicando formatos canónicos de prólogo e epílogo

    • Function Length = 0x31 (= 0x62/2)

    • Ret = 1, indicando um retorno de ramificação de 16 bits

    • H = 0, indicando que os parâmetros não foram inicializados

    • R = 0 e Reg = 1, indicando push/pop de r4-r5

    • L = 0, indicando que não há LR salvar/restaurar

    • C = 0, indicando que não há encadeamento de frames

    • Stack Adjust = 0, indicando que não há ajuste de pilha

Exemplo 2: Função aninhada com alocação local

Prologue:
  004533AC: B5F0      push        {r4-r7, lr}
  004533AE: B083      sub         sp, sp, #0xC
Epilogue:
  00453412: B003      add         sp, sp, #0xC
  00453414: BDF0      pop         {r4-r7, pc}

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x000533AC (= 0x004533AC -0x00400000)
  • Palavra 1

    • Flag = 1, indicando formatos canónicos de prólogo e epílogo

    • Function Length = 0x35 (= 0x6A/2)

    • Ret = 0, indicando um retorno pop {pc}

    • H = 0, indicando que os parâmetros não foram inicializados

    • R = 0 e Reg = 3, indicando push/pop de r4-r7

    • L = 1, indicando que o LR foi salvo/restaurado

    • C = 0, indicando que não há encadeamento de frames

    • Stack Adjust = 3 (= 0x0C/4)

Exemplo 3: Função variádica aninhada

Prologue:
  00453988: B40F      push        {r0-r3}
  0045398A: B570      push        {r4-r6, lr}
Epilogue:
  004539D4: E8BD 4070 pop         {r4-r6}
  004539D8: F85D FB14 ldr         pc, [sp], #0x14

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x00053988 (= 0x00453988-0x00400000)
  • Palavra 1

    • Flag = 1, indicando formatos canónicos de prólogo e epílogo

    • Function Length = 0x2A (= 0x54/2)

    • Ret = 0, indicando um retorno no estilo pop {pc} (neste caso, um ldr pc,[sp],#0x14 retorno)

    • H = 1, indicando que os parâmetros foram calibrados

    • R = 0 e Reg = 2, indicando push/pop de r4-r6

    • L = 1, indicando que o LR foi salvo/restaurado

    • C = 0, indicando que não há encadeamento de frames

    • Stack Adjust = 0, indicando que não há ajuste de pilha

Exemplo 4: Função com epílogos múltiplos

Prologue:
  004592F4: E92D 47F0 stmdb       sp!, {r4-r10, lr}
  004592F8: B086      sub         sp, sp, #0x18
Epilogues:
  00459316: B006      add         sp, sp, #0x18
  00459318: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  0045943E: B006      add         sp, sp, #0x18
  00459440: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  004595D4: B006      add         sp, sp, #0x18
  004595D6: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459606: B006      add         sp, sp, #0x18
  00459608: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459636: F028 FF0F bl          KeBugCheckEx     ; end of function

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x000592F4 (= 0x004592F4-0x00400000)
  • Palavra 1

    • Flag = 0, indicando que o registo .xdata está presente (necessário para múltiplos epílogos)

    • .xdata Endereço - 0x00400000

.xdata (variável, 6 palavras):

  • Palavra 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, indicando a primeira versão do.xdata

    • X = 0, indicando que não há dados de exceção

    • E = 0, indicando uma lista de âmbitos de epílogo

    • F = 0, indicando uma descrição completa da função, incluindo prólogo

    • Epilogue Count = 0x04, indicando os 4 âmbitos totais do epílogo

    • Code Words = 0x01, indicando uma palavra de 32 bits de códigos de exceção

  • Palavras 1-4, descrevendo 4 âmbitos de epílogo em 4 locais. Cada escopo tem um conjunto comum de códigos de desenrolamento, compartilhados com o prólogo, no deslocamento 0x00, e é incondicional, especificando a condição 0x0E (sempre).

  • Desenrolar códigos, a partir do Word 5: (partilhado entre prólogo/epílogo)

    • Código de desenrolar 0 = 0x06: sp += (6 << 2)

    • Desenrolar o código 1 = 0xDE: pop {r4-r10, lr}

    • Desativar código 2 = 0xFF: término

Exemplo 5: Função com pilha dinâmica e epílogo interno

Prologue:
  00485A20: B40F      push        {r0-r3}
  00485A22: E92D 41F0 stmdb       sp!, {r4-r8, lr}
  00485A26: 466E      mov         r6, sp
  00485A28: 0934      lsrs        r4, r6, #4
  00485A2A: 0124      lsls        r4, r4, #4
  00485A2C: 46A5      mov         sp, r4
  00485A2E: F2AD 2D90 subw        sp, sp, #0x290
Epilogue:
  00485BAC: 46B5      mov         sp, r6
  00485BAE: E8BD 41F0 ldm         sp!, {r4-r8, lr}
  00485BB2: B004      add         sp, sp, #0x10
  00485BB4: 4770      bx          lr
  ...
  00485E2A: F7FF BE7D b           #0x485B28    ; end of function

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x00085A20 (= 0x00485A20-0x00400000)
  • Palavra 1

    • Flag = 0, indicando .xdata registro presente (necessário para epílogos múltiplos)

    • .xdata Endereço - 0x00400000

.xdata (variável, 3 palavras):

  • Palavra 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, indicando a primeira versão do.xdata

    • X = 0, indicando que não há dados de exceção

    • E = 0, indicando uma lista de âmbitos de epílogo

    • F = 0, indicando uma descrição completa da função, incluindo prólogo

    • Epilogue Count = 0x001, indicando o âmbito total do epílogo 1

    • Code Words = 0x01, indicando uma palavra de 32 bits de códigos de exceção

  • Palavra 1: Escopo do epílogo no offset de 0xC6 (= 0x18C/2), iniciando o índice de código de desenrolar em 0x00 e com uma condição de 0x0E (sempre)

  • Descompactar códigos, começando no Word 2: (partilhado entre prólogo/epílogo)

    • Desenrolar o código 0 = 0xC6: sp = r6

    • Desenrolar o código 1 = 0xDC: pop {r4-r8, lr}

    • Código de desenrolar 2 = 0x04: sp += (4 << 2)

    • Código de desenrolamento 3 = 0xFD: fim, equivale a uma instrução de 16 bits no epílogo

Exemplo 6: Função com gestor de exceções

Prologue:
  00488C1C: 0059 A7ED dc.w  0x0059A7ED
  00488C20: 005A 8ED0 dc.w  0x005A8ED0
FunctionStart:
  00488C24: B590      push        {r4, r7, lr}
  00488C26: B085      sub         sp, sp, #0x14
  00488C28: 466F      mov         r7, sp
Epilogue:
  00488C6C: 46BD      mov         sp, r7
  00488C6E: B005      add         sp, sp, #0x14
  00488C70: BD90      pop         {r4, r7, pc}

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x00088C24 (= 0x00488C24-0x00400000)
  • Palavra 1

    • Flag = 0, indicando .xdata registro presente (necessário para epílogos múltiplos)

    • .xdata Endereço - 0x00400000

.xdata (variável, 5 palavras):

  • Palavra 0

    • Function Length =0x000027 (= 0x00004E/2)

    • Vers = 0, indicando a primeira versão do.xdata

    • X = 1, indicando os dados de exceção presentes

    • E = 1, indicando um único epílogo

    • F = 0, indicando uma descrição completa da função, incluindo prólogo

    • Epilogue Count = 0x00, indicando que os códigos de desaceleração do epílogo começam no endereço 0x00

    • Code Words = 0x02, indicando duas palavras de 32 bits de códigos de desenrolamento

  • Desenrole códigos, começando no Word 1:

    • Código de unwind 0 = 0xC7: sp = r7

    • Código de desenrolar 1 = 0x05: sp += (5 << 2)

    • Código de desalinhamento 2 = 0xED/0x90: pop {r4, r7, lr}

    • Desenrolar o código 4 = 0xFF: fim

  • O Word 3 especifica um manipulador de exceção = 0x0019A7ED (= 0x0059A7ED - 0x00400000)

  • As palavras 4 e posteriores são dados de exceção embutidos

Exemplo 7: Funclet

Function:
  00488C72: B500      push        {lr}
  00488C74: B081      sub         sp, sp, #4
  00488C76: 3F20      subs        r7, #0x20
  00488C78: F117 0308 adds        r3, r7, #8
  00488C7C: 1D3A      adds        r2, r7, #4
  00488C7E: 1C39      adds        r1, r7, #0
  00488C80: F7FF FFAC bl          target
  00488C84: B001      add         sp, sp, #4
  00488C86: BD00      pop         {pc}

.pdata (fixo, 2 palavras):

  • Palavra 0

    • Function Start RVA = 0x00088C72 (= 0x00488C72-0x00400000)
  • Palavra 1

    • Flag = 1, indicando formatos canónicos de prólogo e epílogo

    • Function Length = 0x0B (= 0x16/2)

    • Ret = 0, indicando um retorno pop {pc}

    • H = 0, indicando que os parâmetros não foram inicializados

    • R = 0 e Reg = 7, indicando que nenhum registo foi guardado/restaurado

    • L = 1, indicando que o LR foi salvo/restaurado

    • C = 0, indicando que não há encadeamento de frames

    • Stack Adjust = 1, indicando um ajuste de pilha de 1 × 4 bytes

Ver também

Visão geral das convenções ARM ABI
Problemas comuns de migração do Visual C++ ARM