Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O Windows no ARM64 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 no ARM64. Ele ilustra os auxiliares de linguagem usados pelo código gerado pelo assembler Microsoft ARM e pelo compilador MSVC.
Objetivos e motivação
A exceção desenrolando convenções de dados, e esta descrição, destinam-se a:
Forneça descrição suficiente para permitir o desenrolamento sem sondagem de código em todos os casos.
A análise do código requer que este seja carregado na memória. Evita o desenrolar em algumas circunstâncias em que é útil (rastreamento, amostragem, depuração).
Analisar o código é complexo; O compilador deve ter cuidado para gerar apenas instruções que o desenrolador pode decodificar.
Se o desenrolamento não puder ser totalmente descrito usando códigos de desenrolamento, em alguns casos ele deve retornar à decodificação de instruções. A decodificação de instruções aumenta a complexidade geral e, idealmente, deve ser evitada.
Suporte ao desenrolar em meio-prólogo e meio-epílogo.
- O desenrolamento é usado no Windows para mais do que o tratamento de exceções. É fundamental que o código consiga desenrolar-se com precisão, mesmo quando está no meio de uma sequência de código de prolog ou epilog.
Ocupe uma quantidade mínima de espaço.
Os códigos de descompactação não devem ser agregados de forma a aumentar significativamente o tamanho binário.
Como é provável que os códigos de desenrolamento estejam bloqueados na memória, uma pequena dimensão garante uma sobrecarga mínima para cada binário carregado.
Suposições
Essas suposições são feitas na descrição de tratamento de exceções:
Prólogos e epílogos tendem a espelhar-se. Aproveitando essa característica comum, o tamanho dos metadados necessários para descrever o desenrolamento pode ser muito reduzido. Dentro do corpo da função, não importa se as operações do prólogo são desfeitas ou se as operações do epílogo são feitas de maneira progressiva. Ambos devem produzir resultados idênticos.
As funções tendem a ser, em geral, relativamente pequenas. Várias otimizações no espaço dependem deste facto para alcançar o empacotamento de dados mais eficiente.
Não há código condicional em epílogos.
Registro de ponteiro de quadro dedicado: Se o
spfor salvo em outro registro (x29) no prólogo, esse registro permanecerá intocado durante toda a função. Isso significa que osporiginal pode ser recuperado a qualquer momento.A menos que o
spseja salvo noutro registo, toda a manipulação do ponteiro da pilha ocorre estritamente no prólogo e no epílogo.O layout da estrutura de pilha é organizado conforme descrito na próxima seção.
Disposição da estrutura de pilha ARM64
Para funções encadeadas de quadros, o par fp e lr pode ser salvo em qualquer posição na área variável local, dependendo das considerações de otimização. O objetivo é maximizar o número de locais que podem ser alcançados por uma única instrução com base no ponteiro de quadro (x29) ou ponteiro de pilha (sp). No entanto, para as funções alloca, estas devem ser encadeadas e x29 deve apontar para o fundo da pilha. Para permitir uma melhor cobertura do modo de endereçamento de pares de registros, as áreas salvas de registro não voláteis são posicionadas na parte superior da pilha de área local. Aqui estão exemplos que ilustram várias das sequências prolog mais eficientes. Por uma questão de clareza e melhor localização de cache, a ordem de armazenamento de registros salvos por destinatários em todos os prólogos canônicos está em ordem de "crescimento".
#framesz abaixo representa o tamanho de toda a pilha (excluindo a área alloca).
#localsz e #outsz denotam o tamanho da área local (incluindo a área de salvamento para o par <x29, lr>) e o tamanho do parâmetro de saída, respectivamente.
Encadeado, #localsz <= 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] stp x29,lr,[sp,#-localsz]! // save <x29,lr> at bottom of local area mov x29,sp // x29 points to bottom of local sub sp,sp,#outsz // (optional for #outsz != 0)Acorrentado, #localsz > 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] sub sp,sp,#(localsz+outsz) // allocate remaining frame stp x29,lr,[sp,#outsz] // save <x29,lr> at bottom of local area add x29,sp,#outsz // setup x29 points to bottom of local areaFunções de folha não encadeadas (
lrnão salvas)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] str x23,[sp,#32] stp d8,d9,[sp,#40] // save FP regs (optional) stp d10,d11,[sp,#56] sub sp,sp,#(framesz-80) // allocate the remaining local areaTodos os locais são acessados com base em
sp.<x29,lr>aponta para o quadro anterior. Para o tamanho do quadro <= 512, osub sp, ...pode ser otimizado se a área salva dos registos for movida para a parte inferior da pilha. A desvantagem é que não é consistente com outros layouts acima. E os registos salvos fazem parte do intervalo para registos em pares e modo de endereçamento com deslocamento pré e pós-indexado.Funções não foliares e não encadeadas (salva
lrna área salva Int)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... stp x23,lr,[sp,#32] // save last Int reg and lr stp d8,d9,[sp,#48] // save FP reg-pair (optional) stp d10,d11,[sp,#64] // ... sub sp,sp,#(framesz-80) // allocate the remaining local areaOu, com registros Int salvos de número par,
stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... str lr,[sp,#32] // save lr stp d8,d9,[sp,#40] // save FP reg-pair (optional) stp d10,d11,[sp,#56] // ... sub sp,sp,#(framesz-80) // allocate the remaining local areaApenas
x19guardados:sub sp,sp,#16 // reg save area allocation* stp x19,lr,[sp] // save x19, lr sub sp,sp,#(framesz-16) // allocate the remaining local area* A alocação da área de salvaguarda reg não é integrada no
stpporque umstpreg-lr pré-indexado não pode ser representado com os códigos de desempacotamento.Todos os locais são acessados com base em
sp.<x29>aponta para o quadro anterior.Encadeado, #framesz <= 512, #outsz = 0
stp x29,lr,[sp,#-framesz]! // pre-indexed, save <x29,lr> mov x29,sp // x29 points to bottom of stack stp x19,x20,[sp,#(framesz-32)] // save INT pair stp d8,d9,[sp,#(framesz-16)] // save FP pairEm comparação com o primeiro exemplo de prolog acima, este exemplo tem uma vantagem: todas as instruções de salvamento de registro estão prontas para serem executadas após apenas uma instrução de alocação de pilha. Isso significa que não há antidependência de
spque impeça o paralelismo no nível de instrução.Encadeado, tamanho do quadro >: 512 (é opcional para funções sem
alloca)stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area sub sp,sp,#(framesz-80) // allocate the remaining local areaPara fins de otimização,
x29pode ser colocado em qualquer posição na área local para fornecer uma melhor cobertura para o modo de endereçamento de deslocamento "reg-pair" e pré/pós-indexado. Os locais abaixo dos ponteiros do quadro podem ser acessados com base emsp.Encadeado, tamanho do quadro > 4K, com ou sem aloca(),
stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area mov x15,#(framesz/16) bl __chkstk sub sp,sp,x15,lsl#4 // allocate remaining frame // end of prolog ... sub sp,sp,#alloca // more alloca() in body ... // beginning of epilog mov sp,x29 // sp points to top of local area ldp d10,d11,[sp,#64] ... ldp x29,lr,[sp],#80 // post-indexed, reload <x29,lr>
Informações de tratamento de exceções ARM64
.pdata registos
Os registros .pdata são uma matriz ordenada de itens de comprimento fixo que descrevem cada função de manipulação de pilha em um binário PE. A expressão "manipulação de pilha" é significativa: funções de folha que não exigem armazenamento local e não precisam salvar/restaurar registros não voláteis, não exigem um registro .pdata. Esses registros devem ser explicitamente omitidos para economizar espaço. Um descontrair de uma dessas funções pode obter o endereço de retorno diretamente do lr para passar para o chamador.
Cada registro .pdata para ARM64 tem 8 bytes de comprimento. O formato geral de cada registo coloca o RVA de 32 bits do início da função na primeira palavra, seguido por uma segunda palavra que contém um ponteiro para um bloco .xdata de comprimento variável ou uma palavra compactada que descreve uma sequência de desenrolamento de função canónica.
Os campos são os seguintes:
Function Start RVA é o RVA de 32 bits do início da função.
Flag é um campo de 2 bits que indica como interpretar os 30 bits restantes da segunda palavra
.pdata. Se Flag for igual a 0, os bits restantes formarão uma Informações de Exceção RVA (com os dois bits mais baixos implicitamente 0). Se o Flag for diferente de zero, os bits restantes formarão uma estrutura de Dados Empacotados de Desenrolamento .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 desenrolar de uma função, assumindo uma forma canônica. Neste caso, não é necessário o registo
.xdata.
.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:
Estes dados dividem-se em quatro secções:
Um cabeçalho de 1 ou 2 palavras que descreve o tamanho geral da estrutura e fornece dados de função chave. A segunda palavra só estará presente se os campos Epilog Count e Code Words estiverem definidos como 0. O cabeçalho tem estes campos de bits:
a) Comprimento de Função é um campo de 18 bits. Ele indica o comprimento total da função em bytes, dividido por 4. Se uma função for maior que 1M, vários registros de
.pdatae.xdatadevem ser usados para descrever a função. Para obter mais informações, consulte a seção Funções grandes.b) Vers é um campo de 2 bits. Descreve a versão dos restantes
.xdata. Atualmente, apenas a versão 0 é definida, portanto, valores de 1-3 não são permitidos.c. X é um campo de 1 bit. Indica a presença (1) ou ausência (0) de dados de exceção.
d. E é um campo de 1 bit. Ele indica que as informações que descrevem um único epílogo são compactadas no cabeçalho (1) em vez de exigir mais palavras de escopo posteriormente (0).
e. O campo Contagem de Epílogos é de 5 bits e tem dois significados, dependendo do estado do bit E.
Se e for igual a 0, especifica o número total de escopos de epílogo descritos na seção 2. Se existirem mais de 31 escopos na função, o campo Code Words deve ser definido como 0 para indicar que uma palavra de extensão é necessária.
Se e for igual a 1, este campo especifica o índice do primeiro código de desenrolar que descreve o único e apenas epílogo.
f. Code Words é um campo de 5 bits que especifica o número de palavras de 32 bits necessárias para conter todos os códigos de desempacotamento na seção 3. Se forem necessárias mais de 31 palavras (ou seja, 124 códigos de desenrolar), este campo deve ser 0 para indicar que uma palavra de extensão é necessária.
g. Extended Epilog Count e Extended Code Words são campos de 16 bits e 8 bits, respectivamente. Eles fornecem mais espaço para codificar um número excepcionalmente grande de epílogos, ou um número excepcionalmente grande de palavras de código de desenrolamento. A palavra de extensão que contém esses campos só estará presente se os campos Epilog Count e Code Words na primeira palavra do cabeçalho forem 0.
Se a contagem de epílogos não for zero, uma lista de informações sobre os escopos de epílogo, empacotada uma por palavra, vem depois do cabeçalho e do cabeçalho estendido opcional. Eles são armazenados em ordem crescente de deslocamento inicial. Cada escopo contém os seguintes bits:
a) Epilog Start Offset é um campo de 18 bits que indica o deslocamento, em bytes, do epílogo em relação ao início da função, dividido por 4.
b) Res é um campo de 4 bits reservado para expansão futura. Seu valor deve ser 0.
c. Epilog Start Index é um campo de 10 bits (2 bits a mais do que Extended Code Words). Ele indica o índice de byte do primeiro código de desenrolamento que descreve este epílogo.
Após a lista de escopos de epílogo, vem uma matriz de bytes que contém códigos de desenrolamento, descritos em detalhe numa seção posterior. Esta matriz é preenchida no final até ao mais próximo limite de palavra completa. Os códigos de desmontagem são gravados neste array. Eles começam com o mais próximo do corpo da função e se movem em direção às bordas da função. Os bytes para cada código de desenrolar são armazenados em ordem big-endian para que o byte mais significativo seja buscado primeiro, o que identifica a operação e o comprimento do restante do código.
Finalmente, após os bytes do código de desenrolamento, se o bit X no cabeçalho estiver definido para 1, segue-se a informação do manipulador de exceções. Ele consiste em um único Exception Handler RVA que fornece o endereço do próprio manipulador de exceções. Ele é seguido imediatamente por uma quantidade de dados de comprimento variável exigida pelo manipulador de exceções.
O registro .xdata foi projetado para que seja possível buscar os primeiros 8 bytes e usá-los para calcular o tamanho total do registro, menos o comprimento dos dados de exceção de tamanho variável a seguir. O trecho de código a seguir calcula o tamanho do registro:
ULONG ComputeXdataSize(PULONG Xdata)
{
ULONG Size;
ULONG EpilogScopes;
ULONG UnwindWords;
if ((Xdata[0] >> 22) != 0) {
Size = 4;
EpilogScopes = (Xdata[0] >> 22) & 0x1f;
UnwindWords = (Xdata[0] >> 27) & 0x1f;
} else {
Size = 8;
EpilogScopes = Xdata[1] & 0xffff;
UnwindWords = (Xdata[1] >> 16) & 0xff;
}
if (!(Xdata[0] & (1 << 21))) {
Size += 4 * EpilogScopes;
}
Size += 4 * UnwindWords;
if (Xdata[0] & (1 << 20)) {
Size += 4; // Exception handler RVA
}
return Size;
}
Embora o prólogo e cada epílogo tenham o seu próprio índice nos códigos de desenrolamento, a tabela é compartilhada entre eles. É totalmente possível (e não totalmente incomum) que todos eles possam compartilhar os mesmos códigos. (Para obter um exemplo, consulte o Exemplo 2 na seção Exemplos.) Os programadores de compiladores devem otimizar para este caso em particular. Isso deve-se ao facto de que o maior índice que pode ser especificado é 255, o que limita o número total de códigos de desenrolar para uma determinada função.
Desenrolar códigos
A matriz de códigos de desenrolamento é um pool de sequências que descrevem exatamente como desfazer os efeitos do prólogo. Eles são armazenados na mesma ordem em que as operações precisam ser desfeitas. Os códigos de desenrolar podem ser considerados como um pequeno conjunto de instruções, codificado como uma sequência de bytes. Quando a execução estiver concluída, o endereço de retorno para a função de chamada estará no registro lr. E, todos os registros não voláteis são restaurados para seus valores no momento em que a função foi chamada.
Se fosse garantido que as exceções ocorressem apenas dentro de um corpo de função, e nunca dentro de um prólogo ou de qualquer epílogo, então apenas uma única sequência seria necessária. No entanto, o modelo de desenrolamento do Windows exige que o código possa desenrolar-se no interior de um prólogo ou epílogo em execução parcial. Para atender a esse requisito, os códigos de desenrolar foram cuidadosamente projetados para que eles mapeem inequivocamente 1:1 para cada opcode relevante no prólogo e no epílogo. Este desenho tem várias implicações:
Ao contar o número de códigos de desenrolamento, é possível calcular o comprimento do prólogo e do epílogo.
Ao contar o número de instruções após o início de um escopo de epílogo, é possível ignorar o número equivalente de códigos de desenrolamento. Podemos executar o restante de uma sequência para completar o desenrolar parcialmente realizado pelo epílogo.
Ao contar o número de instruções até ao final do prólogo, é possível ignorar o número equivalente de códigos de desenrolamento. Podemos executar o resto da sequência para desfazer apenas as partes do prólogo que já foram executadas completamente.
Os códigos de desenrolar são codificados de acordo com a tabela abaixo. Todos os códigos de desenrolar são um byte único/duplo, exceto aquele que aloca uma enorme pilha (alloc_l). Existem 22 códigos de desanuviamento no total. Cada código de desenrolar mapeia exatamente uma instrução no prólogo/epílogo, para permitir o desenrolamento de prólogos e epílogos parcialmente executados.
| Código de desmontagem | Bits e interpretação |
|---|---|
alloc_s |
000xxxxx: alocar uma pilha pequena com tamanho < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzz: salve <x19,x20> par em [sp-#Z*8]!, deslocamento pré-indexado >= -248 |
save_fplr |
01zzzzzz: salvar par de <x29,lr> em [sp+#Z*8], deslocamento <= 504. |
save_fplr_x |
10zzzzzz: Guardar par de <x29,lr> em [sp-(#Z+1)*8]!, com deslocamento pré-indexado >= -512 |
alloc_m |
11000xxx'xxxxxxxx: alocar grande stack com tamanho < 32K (2^11 * 16). |
save_regp |
110010xx'xxzzzzzz: guardar o par x(19+#X) em [sp+#Z*8], deslocamento <= 504 |
save_regp_x |
110011xx'xxzzzzzz: guardar par x(19+#X) em [sp-(#Z+1)*8]!, >de deslocamento pré-indexado = -512 |
save_reg |
110100xx'xxzzzzzz: salvar o registro x(19+#X) em [sp+#Z*8], deslocamento <= 504 |
save_reg_x |
1101010x'xxxzzzzz: Guardar reg x(19+#X) em [sp-(#Z+1)*8]!, deslocamento pré-indexado >= -256 |
save_lrpair |
1101011x'xxzzzzzz: salvar par <x(19+2*#X),lr> em [sp+#Z*8], deslocamento <= 504 |
save_fregp |
1101100x'xxzzzzzz: salvar o par d(8+#X) em [sp+#Z*8], deslocamento <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: guardar par d(8+#X) em [sp-(#Z+1)*8]!, com deslocamento pré-indexado >= -512 |
save_freg |
1101110x'xxzzzzzz: guardar o registo d(8+#X) em [sp+#Z*8], com deslocamento <= 504 |
save_freg_x |
11011110'xxxzzzzz: guardar reg d(8+#X) em [sp-(#Z+1)*8]!, com deslocamento pré-indexado >= -256 |
alloc_z |
11011111'zzzzzzzzzz: alocar pilha com tamanho z * SVE-VL |
alloc_l |
11100000'xxxxxxxx'xxxxxxxx'xxxxxxxx: alocar pilha grande com tamanho < 256M (2^24 * 16) |
set_fp |
11100001: configurar x29 com mov x29,sp |
add_fp |
11100010'xxxxxxxx: Configurar x29 com add x29,sp,#x*8 |
nop |
11100011: Nenhuma operação de desanuviamento é necessária. |
end |
11100100: fim do código de desenrolamento. Implica ret em epílogo. |
end_c |
11100101: Fim do código de desenrolamento no escopo encadeado atual. |
save_next |
11100110: Salve o próximo par de registros. |
save_any_xreg |
11100111'0pxrrrrr'00oooooo: salvar registro(s)
|
save_any_dreg |
11100111'0pxrrrrr'01oooooo: salvar registro(s)
|
save_any_qreg |
11100111'0pxrrrrr'10oooooo: salvar registro(s)
|
save_zreg |
11100111'0oo0rrrr'11oooooo: salvar reg Z(#r+8) em [sp + #o * VL], (Z8 a Z23) |
save_preg |
11100111'0oo1rrrr'11oooooo: salvar reg P(#r) em [sp + #o * (VL / 8)], (P4 até P15; r valores [0, 3] são reservados) |
| 11100111'1yyyyyy': reservado | |
| 11101xxx: Reservado para casos de pilha personalizados abaixo gerados apenas para rotinas ASM | |
11101000: Pilha personalizada para MSFT_OP_TRAP_FRAME |
|
11101001: Pilha personalizada para MSFT_OP_MACHINE_FRAME |
|
11101010: Pilha personalizada para MSFT_OP_CONTEXT |
|
11101011: Pilha personalizada para MSFT_OP_EC_CONTEXT |
|
11101100: Pilha personalizada para MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
| 11101101: reservado | |
| 11101110: reservado | |
| 11101111: reservado | |
| 11110xxx: reservado | |
| 11111000'yyyyyy : reservado | |
| 11111001'yyyyyyyy'yyyyyyyy : reservado | |
| 11111010'yyyyyyyy'yyyyyyyy'yyyyyyyy : reservado | |
| 11111011'yyyyyyyy'yyyyyyyy'yyyyyyyy'yyyyyyyy : reservado | |
pac_sign_lr |
11111100: assine o endereço de retorno em lr com pacibsp |
| 11111101: reservado | |
| 11111110: reservado | |
| 11111111: reservado |
Em instruções com valores grandes cobrindo vários bytes, os bits mais significativos são armazenados primeiro. Este design torna possível encontrar o tamanho total em bytes do código de desempacotamento consultando apenas o primeiro byte do código. Como cada código de desenrolar é exatamente mapeado para uma instrução em um prólogo ou epílogo, você pode calcular o tamanho do prólogo ou epílogo. Percorra a sequência do início ao fim e use uma tabela de consulta ou dispositivo semelhante para determinar o comprimento do opcode correspondente.
O endereçamento de deslocamento pós-indexado não é permitido em um prólogo. Todos os intervalos de deslocamento (#Z) correspondem à codificação do endereçamento stp/str, exceto save_r19r20_x, em que 248 é suficiente para todas as áreas salvas (10 registros Int + 8 registros FP + 8 registros de entrada).
save_next deve seguir um save para um par de registros: save_regp, save_regp_x, save_fregp, save_fregp_x, save_r19r20_xou outro save_next. Ele também pode ser usado em conjunto com save_any_xreg, save_any_dreg ou save_any_qreg mas apenas quando p = 1. Ele salva o próximo par de registro em ordem numericamente crescente para o próximo espaço de pilha.
save_next não deve ser usado além do último registo do mesmo tipo.
Como os tamanhos das instruções regulares de retorno e salto são os mesmos, não há necessidade de um código de desenrolar end separado em cenários de chamada de cauda.
end_c é projetado para lidar com fragmentos de função não contíguos para fins de otimização. Um end_c que indique o fim dos códigos de desenrolar no âmbito atual deve ser seguido por outra série de códigos de desenrolar que terminem com uma endreal. Os códigos de desenrolar entre end_c e end representam as operações de prólogo na região pai (um "prólogo fantasma"). Mais detalhes e exemplos são descritos na seção abaixo.
Dados de desenrolamento 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 totalmente a necessidade de um registo .xdata e reduz significativamente o custo de fornecimento de dados de desmontagem. Os prólogos e epílogos canônicos são projetados para atender aos requisitos comuns de uma função simples: uma que não requer um manipulador de exceções e que faz suas operações de configuração e desmontagem em uma ordem padrão.
O formato de um registo de .pdata com dados de desenrolamento compactados é o seguinte:
Os campos são os seguintes:
- Function Start RVA é o RVA de 32 bits do início da função.
-
Flag é um campo de 2 bits, conforme descrito acima, com os seguintes significados:
- 00 = dados de desenrolamento embalados não utilizados; os bits restantes apontam para um registo
.xdata - 01 = dados de desenrolamento compactados usados com um único prólogo e epílogo no início e no final do escopo
- 10 = dados de desenrolamento compactados usados para código sem qualquer prólogo e epílogo. Útil para descrever segmentos de função separados
- 11 = reservado.
- 00 = dados de desenrolamento embalados não utilizados; os bits restantes apontam para um registo
-
Comprimento da Função é um campo de 11 bits que fornece o comprimento de toda a função em bytes, dividido por 4. Se a função for maior que 8k, um registro de
.xdatacompleto deve ser usado. - O Tamanho do Quadro é um campo de 9 bits que indica o número de bytes da pilha alocados para esta função, dividido por 16. As funções que alocam mais de (8k-16) bytes de pilha devem usar um registro de
.xdatacompleto. Inclui a área variável local, a área de parâmetros de saída, a área Int e FP salva pelo destinatário e a área de parâmetros domésticos. Exclui a área de alocação dinâmica. -
CR é um sinalizador de 2 bits que indica se a função inclui instruções extras para configurar uma cadeia de quadros e um link de retorno:
- 00 = função não encadeada, par de
<x29,lr>não é armazenado na pilha - 01 = função desencadeada,
<lr>é guardado na pilha - 10 = função encadeada com um endereço de retorno assinado
pacibsp - 11 = função encadeada, uma instrução de pares de armazenamento e carga é usada em prólogo/epílogo
<x29,lr>
- 00 = função não encadeada, par de
- H é um sinalizador de 1 bit que indica se a função abriga os registradores de parâmetros inteiros (x0-x7) armazenando-os no início da função. (0 = não regista a casa, 1 = regista a casa).
- RegI é um campo de 4 bits que indica o número de registradores INT não voláteis (x19-x28) salvos no local canônico da pilha.
- RegF é um campo de 3 bits que indica o número de registos FP não voláteis (d8-d15) guardados na localização canónica da pilha. (RegF=0: nenhum registro FP é salvo; RegF>0: os registos FP RegF+1 são guardados). Os dados de desenrolamento compactados não podem ser usados para funções que salvam apenas um registro FP.
Prologs canônicos que se enquadram nas categorias 1, 2 (sem área de parâmetro de saída), 3 e 4 na seção acima podem ser representados pelo formato de desenrolar compactado. Os epílogos para funções canônicas seguem uma forma semelhante, exceto H não tem efeito, a instrução set_fp é omitida e a ordem dos passos e as instruções em cada passo são invertidas no epílogo. O algoritmo para .xdata compactado segue estas etapas, detalhadas na tabela seguinte:
Passo 0: Pré-calcular o tamanho de cada área.
Passo 1: Assine o endereço de retorno.
Passo 2: Salve os registros salvos pelo destinatário Int.
Etapa 3: Esta etapa é específica para o tipo 4 nas seções iniciais.
lr é salvo no final da área Int.
Passo 4: Salve os registros FP salvos por destinatários.
Etapa 5: Salve os argumentos de entrada na área de parâmetros home.
Etapa 6: Aloque a pilha restante, incluindo área local, par de <x29,lr> e área de parâmetro de saída. 6a corresponde ao tipo canônico 1. 6b e 6c são para o tipo canônico 2. 6d e 6e são tanto para o tipo 3 como para o tipo 4.
| Passo # | Valores do sinalizador | N.º de instruções | Opcode | Código de desmontagem |
|---|---|---|---|---|
| 0 | #intsz = RegI * 8;if (CR==01) #intsz += 8; // lr#fpsz = RegF * 8;if(RegF) #fpsz += 8;#savsz=((#intsz+#fpsz+8*8*H)+0xf)&~0xf)#locsz = #famsz - #savsz |
|||
| 1 | CR == 10 | 1 | pacibsp |
pac_sign_lr |
| 2 | < 0 RegI<= 10 |
RegI / 2 + RegI % 2 |
stp x19,x20,[sp,#savsz]!stp x21,x22,[sp,#16]... |
save_regp_xsave_regp... |
| 3 | CR == 01* | 1 | str lr,[sp,#(intsz-8)]* |
save_reg |
| 4 | < 0 RegF<= 7 | (RegF + 1) / 2 + (RegF + 1) % 2) |
stp d8,d9,[sp,#intsz]**stp d10,d11,[sp,#(intsz+16)]...str d(8+RegF),[sp,#(intsz+fpsz-8)] |
save_fregp...save_freg |
| 5 | H == 1 | 4 | stp x0,x1,[sp,#(intsz+fpsz)]stp x2,x3,[sp,#(intsz+fpsz+16)]stp x4,x5,[sp,#(intsz+fpsz+32)]stp x6,x7,[sp,#(intsz+fpsz+48)] |
nopnopnopnop |
| 6º-A | (CR == 10 || CR == 11) &&#locsz
<= 512 |
2 | stp x29,lr,[sp,#-locsz]!mov x29,sp*** |
save_fplr_xset_fp |
| 6 ter | (CR == 10 || CR == 11) && 512 < #locsz<= 4080 |
3 | sub sp,sp,#locszstp x29,lr,[sp,0]add x29,sp,0 |
alloc_msave_fplrset_fp |
| 6 cêntimos | (CR == 10 || CR == 11) &&#locsz
> 4080 |
4 | sub sp,sp,4080sub sp,sp,#(locsz-4080)stp x29,lr,[sp,0]add x29,sp,0 |
alloc_malloc_s/alloc_msave_fplrset_fp |
| 6º D | (CR == 00 || CR == 01) &&#locsz
<= 4080 |
1 | sub sp,sp,#locsz |
alloc_s/alloc_m |
| 6º E | (CR == 00 || CR == 01) &&#locsz
> 4080 |
2 | sub sp,sp,4080sub sp,sp,#(locsz-4080) |
alloc_malloc_s/alloc_m |
* Se CR == 01 e RegI for um número ímpar, o passo 3 e o último save_reg no passo 2 são fundidos num save_regp.
Se RegI == CR == 0 e RegF != 0, então o primeiro stp para o ponto flutuante realiza o predecréscimo.
Nenhuma instrução correspondente a mov x29,sp está presente no epílogo. Os dados de desenrolamento compactados não podem ser usados se uma função necessitar da restauração de sp de x29.
Desdobramento de prólogos e epílogos parciais
Nas situações de desempacotamento mais comuns, a exceção ou invocação ocorre no corpo da função, longe do prólogo e de todos os epílogos. Nessas situações, o desenrolar é simples: o desenrolador simplesmente executa os códigos na matriz de desenrolamento. Começa no índice 0 e continua até que um opcode end seja detetado.
É mais difícil realizar o deslocamento corretamente no caso em que ocorre uma exceção ou interrupção durante a execução de um prólogo ou epílogo. Nessas situações, o quadro de pilha é apenas parcialmente construído. O problema é determinar exatamente o que foi feito, desfazê-lo corretamente.
Por exemplo, tome esta sequência de prólogos e epílogos:
0000: stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store)
0004: stp d8,d9,[sp,#224] // save_fregp 0, 224
0008: stp x19,x20,[sp,#240] // save_regp 0, 240
000c: mov x29,sp // set_fp
...
0100: mov sp,x29 // set_fp
0104: ldp x19,x20,[sp,#240] // save_regp 0, 240
0108: ldp d8,d9,[sp,224] // save_fregp 0, 224
010c: ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load)
0110: ret lr // end
Ao lado de cada opcode está o código de desenrolamento apropriado que descreve esta operação. Você pode ver como a série de códigos de desenrolar para o prólogo é uma imagem espelhada exata dos códigos de desenrolar para o epílogo (sem contar a instrução final do epílogo). É uma situação comum: é por isso que sempre assumimos que os códigos de desenrolamento para o prólogo são armazenados em ordem inversa à ordem de execução do prólogo.
Assim, tanto para o prólogo quanto para o epílogo, ficamos com um conjunto comum de códigos de desenrolamento:
set_fp, save_regp 0,240, save_fregp,0,224, save_fplr_x_256, end
O caso do epilog é simples, uma vez que está em ordem normal. Começando no deslocamento 0 dentro do epílogo (que começa no deslocamento 0x100 na função), esperamos que a sequência completa de desenrolamento seja executada, pois ainda não foi feita qualquer limpeza. Se encontrarmos uma instrução em (no deslocamento 2 no epílogo), podemos relaxar com sucesso ignorando o primeiro código de desenrolamento. Podemos generalizar essa situação e assumir um mapeamento 1:1 entre opcodes e unwind codes. Então, para começar a desenrolar a partir da instrução n no epílogo, devemos saltar os primeiros códigos de desenrolamento n e começar a execução a partir daí.
Acontece que uma lógica semelhante funciona para o prólogo, exceto no sentido inverso. Se começarmos a desenrolar a partir do deslocamento 0 no prólogo, não queremos executar nada. Se quisermos desenrolar a partir do deslocamento 2, que é uma instrução, então queremos começar a executar a sequência de desenrolar um código de desenrolar antes do final. (Lembre-se, os códigos são armazenados em ordem inversa.) E aqui também, podemos generalizar: se começarmos a desenrolar a partir de instruções n no prólogo, devemos começar a executar os códigos de desenrolamento n a partir do final da lista de códigos.
Os códigos Prolog e epilog nem sempre correspondem exatamente, e é por isso que a matriz unwind pode precisar conter várias sequências de códigos. Para determinar o deslocamento a partir de onde começar a processar códigos, use a seguinte lógica:
Se desenrolar de dentro do corpo da função, comece a executar códigos de desenrolamento no índice 0 e continue até atingir um opcode
end.Se estiver a desenrolar a partir de dentro de um epílogo, use o índice inicial específico do epílogo fornecido com o escopo do epílogo como ponto de partida. Calcule quantos bytes o PC em questão tem desde o início do epílogo. Em seguida, avance pelos códigos de desenrolar, ignorando os códigos de desenrolar até que todas as instruções já executadas sejam contabilizadas. Em seguida, execute a partir desse ponto.
Se desenrolar a partir do prólogo, use o índice 0 como ponto de partida. Calcule o comprimento do código prolog a partir da sequência e, em seguida, calcule quantos bytes o PC em questão está a partir do final do prolog. Em seguida, avance pelos códigos de desenrolamento, ignorando-os até que todas as instruções ainda não executadas sejam contabilizadas. Em seguida, execute a partir desse ponto.
Essas regras indicam que os códigos de desenrolar para o prólogo devem ser sempre os primeiros na matriz. E, também, são os códigos usados para desenfazer no caso geral de desenfazer dentro do corpo. Quaisquer sequências de código específicas do epílogo devem seguir-se imediatamente a seguir.
Fragmentos de função
Para fins de otimização de código e outros motivos, pode ser preferível dividir uma função em fragmentos separados (também chamados de regiões ). Quando dividido, cada fragmento de função resultante requer um registro separado .pdata (e possivelmente .xdata).
Para cada fragmento secundário separado que tem o seu próprio prólogo, espera-se que não seja feito nenhum ajuste de pilha no seu prólogo. Todo o espaço de pilha necessário para uma região secundária deve ser pré-alocado pela sua região pai (também conhecida como região hospedeira). Esta pré-alocação mantém o ajuste do ponteiro da pilha estritamente no prólogo da função original.
Um caso típico de fragmentos de função é a "separação de código", onde o compilador pode mover uma região de código para fora de sua função host. Há três casos incomuns que podem resultar da separação de código.
Exemplo
(região 1: início)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...(região 1: fim)
(região 3: início)
...(região 3: fim)
(região 2: início)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end(região 2: fim)
Apenas Prolog (região 1: todos os epílogos estão em regiões separadas):
Apenas o prólogo deve ser descrito. Este prólogo não pode ser representado no formato de
.pdatacompacto. No caso de.xdatacompleto, ele pode ser representado definindo Epilog Count = 0. Ver região 1 no exemplo acima.Desenrolar códigos:
set_fp,save_regp 0,240,save_fplr_x_256,end.Apenas epílogos (região 2: o prólogo está na região anfitriã)
Supõe-se que, no momento em que o controle salta para esta região, todos os códigos de prolog tenham sido executados. O desenrolar parcial pode acontecer em epílogos da mesma forma que numa função normal. Este tipo de região não pode ser representado por um
.pdatacompacto. Em um registo de.xdatacompleto, ele pode ser codificado com um prólogo "fantasma", entre um par de códigos de desapilhamentoend_ceend. Oend_cindica que o tamanho do prólogo é zero. O índice de início do epílogo do único epílogo aponta paraset_fp.Desenrolar código para a região 2:
end_c,set_fp,save_regp 0,240,save_fplr_x_256,end.Sem prólogos ou epílogos (região 3: prólogos e todos os epílogos estão em outros fragmentos):
O formato
.pdatacompacto pode ser aplicado através da definição de Flag = 10. Com registro de.xdatacompleto, Contagem de Epilog = 1. O código Unwind é o mesmo que o código da região 2 acima, mas o Epilog Start Index também aponta paraend_c. O desanuviamento parcial nunca acontecerá nesta região do código.
Outro caso mais complicado de fragmentos de função é o "shrink wrapping". O compilador pode optar por adiar o salvamento de alguns registos guardados pelo callee até estar fora do prólogo da entrada da função.
(região 1: início)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...(região 2: início)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224(região 2: fim)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end(região 1: fim)
No prólogo da região 1, o espaço da pilha é pré-alocado. Você pode ver que a região 2 terá o mesmo código de desenrolamento, mesmo que tenha sido movida para fora de sua função de host.
Região 1: set_fp, save_regp 0,240, save_fplr_x_256, end. O Epilog Start Index aponta para set_fp como de costume.
Região 2: save_regp 2, 224, end_c, set_fp, save_regp 0,240, save_fplr_x_256, end. O Epilog Start Index aponta para o primeiro código de desenrolamento save_regp 2, 224.
Grandes funções
Os fragmentos podem ser usados para descrever funções maiores do que o limite de 1M imposto pelos campos de bits no cabeçalho .xdata. Para descrever uma função excepcionalmente grande como esta, ela precisa ser dividida em fragmentos menores que 1M. Cada fragmento deve ser ajustado para que não divida um epílogo em vários pedaços.
Apenas o primeiro fragmento da função conterá um prólogo; todos os outros fragmentos são marcados como não tendo prólogo. Dependendo do número de epílogos presentes, cada fragmento pode conter zero ou mais epílogos. Lembre-se de 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 registro .pdata (e possivelmente .xdata) para descrever como desenrolar de dentro do corpo da função.
Exemplos
Exemplo 1: Quadros em cadeia e em forma compacta
|Foo| PROC
|$LN19|
str x19,[sp,#-0x10]! // save_reg_x
sub sp,sp,#0x810 // alloc_m
stp fp,lr,[sp] // save_fplr
mov fp,sp // set_fp
// end of prolog
...
|$pdata$Foo|
DCD imagerel |$LN19|
DCD 0x416101ed
;Flags[SingleProEpi] functionLength[492] RegF[0] RegI[1] H[0] frameChainReturn[Chained] frameSize[2080]
Exemplo 2: Encadeamento de frames, completo com espelho Prolog & Epilog
|Bar| PROC
|$LN19|
stp x19,x20,[sp,#-0x10]! // save_regp_x
stp fp,lr,[sp,#-0x90]! // save_fplr_x
mov fp,sp // set_fp
// end of prolog
...
// begin of epilog, a mirror sequence of Prolog
mov sp,fp
ldp fp,lr,[sp],#0x90
ldp x19,x20,[sp],#0x10
ret lr
|$pdata$Bar|
DCD imagerel |$LN19|
DCD imagerel |$unwind$cse2|
|$unwind$Bar|
DCD 0x1040003d
DCD 0x1000038
DCD 0xe42291e1
DCD 0xe42291e1
;Code Words[2], Epilog Count[1], E[0], X[0], Function Length[6660]
;Epilog Start Index[0], Epilog Start Offset[56]
;set_fp
;save_fplr_x
;save_r19r20_x
;end
O Epilog Start Index [0] aponta para a mesma sequência do código de desenrolamento do Prolog.
Exemplo 3: Função não encadeada variádica
|Delegate| PROC
|$LN4|
sub sp,sp,#0x50
stp x19,lr,[sp]
stp x0,x1,[sp,#0x10] // save incoming register to home area
stp x2,x3,[sp,#0x20] // ...
stp x4,x5,[sp,#0x30]
stp x6,x7,[sp,#0x40] // end of prolog
...
ldp x19,lr,[sp] // beginning of epilog
add sp,sp,#0x50
ret lr
AREA |.pdata|, PDATA
|$pdata$Delegate|
DCD imagerel |$LN4|
DCD imagerel |$unwind$Delegate|
AREA |.xdata|, DATA
|$unwind$Delegate|
DCD 0x18400012
DCD 0x200000f
DCD 0xe3e3e3e3
DCD 0xe40500d6
DCD 0xe40500d6
;Code Words[3], Epilog Count[1], E[0], X[0], Function Length[18]
;Epilog Start Index[4], Epilog Start Offset[15]
;nop // nop for saving in home area
;nop // ditto
;nop // ditto
;nop // ditto
;save_lrpair
;alloc_s
;end
O Epilog Start Index [4] aponta para o meio do código de desenrolar Prolog (reutilizar parcialmente a matriz de desenrolamento).
Ver também
Visão geral das convenções ARM64 ABI
Tratamento de exceções ARM