Control de excepciones de ARM64
Windows en ARM64 emplea el mismo mecanismo de control de excepciones estructurado tanto en las excepciones generadas por hardware asincrónicas como en las excepciones generadas por software sincrónicas. Los controladores de excepciones específicos de lenguaje se basan en el control de excepciones estructurado de Windows por medio de funciones del asistente de lenguaje. En este documento se describe el control de excepciones en Windows en ARM64. En este documento se describe el control de excepciones en Windows en ARM, así como los asistentes de lenguaje que usa el código generado mediante el ensamblador ARM de Microsoft y el compilador de MSVC.
Objetivos y motivación
Las convenciones de datos de desenredado de excepciones y esta descripción están previstas para lo siguiente:
Proporcionar una descripción suficiente para permitir el desenredado sin sondeos de código en todos los casos.
Analizar el código requiere que el código esté paginado. Esto evita el desenredado en algunas circunstancias en las que es útil (seguimiento, muestreo y depuración).
Analizar el código es complejo. El compilador debe tener cuidado de generar solo instrucciones que el responsable del desenredado pueda descodificar.
Si el desenredo no se puede describir por completo mediante códigos de desenredado, en algunos casos debe volver a la codificación de instrucciones. La codificación de instrucciones. aumenta la complejidad general e, idealmente, debería evitarse.
Admitir el desenredado en el prólogo y el epílogo medios.
- El desenredado se utiliza en Windows para más funciones que el control de excepciones. Es fundamental que el código se pueda desenredar con precisión incluso cuando se encuentra en medio de una secuencia de código de prólogo o epílogo.
Ocupar una cantidad mínima de espacio.
Los códigos de desenredado no deben agregarse para aumentar considerablemente el tamaño binario.
Dado que es probable que los códigos de desenredado estén bloqueados en memoria, una superficie pequeña garantiza una sobrecarga mínima para cada binario cargado.
Supuestos
Estas suposiciones se realizan en la descripción del control de excepciones:
Los prólogos y epílogos tienden a reflejarse entre sí. Al aprovechar esta característica común, se puede reducir considerablemente el tamaño de los metadatos necesarios para describir el desenredado. En el cuerpo de una función, no importa si las operaciones del prólogo se deshacen o si las operaciones del epílogo se llevan a cabo de forma avanzada. Ambas deberían generar idénticos resultados.
En general, las funciones tienden a ser relativamente pequeñas. Varias optimizaciones de espacio dependen de este hecho para lograr el empaquetado de los datos más eficaz.
No hay código de condición en los epílogos.
Registro de puntero de marco dedicado: si
sp
se guarda en otro registro (x29
) en el prólogo, ese registro permanece intacto en toda la función. Significa que el originalsp
se puede recuperar en cualquier momento.A menos
sp
que se guarde en otro registro, toda la manipulación del puntero de pila se produce estrictamente dentro del prólogo y el epílogo.El diseño del marco de pila se organiza tal como se describe en la sección siguiente.
Diseño del marco de pila de ARM64
En el caso de las funciones encadenadas de fotogramas, el fp
par y lr
se pueden guardar en cualquier posición del área de variables locales, en función de las consideraciones de optimización. El objetivo es maximizar el número de variables locales que se pueden alcanzar mediante una sola instrucción basada en el puntero de marco (x29
) o el puntero de pila (sp
). Sin embargo, para alloca
las funciones, debe estar encadenada y x29
debe apuntar a la parte inferior de la pila. Para permitir una mejor cobertura de modo de direccionamiento de pares de registros, las áreas de almacenamiento de registros no volátiles se colocan en la parte superior de la pila de área local. Estos son algunos ejemplos que ilustran algunas de las secuencias de prólogo más eficaces. Con el fin de mejorar la claridad y la ubicación de la memoria caché, el orden en que se almacenan los registros guardados por el destinatario en todos los registros canónicos es "en aumento". #framesz
a continuación se representa el tamaño de toda la pila (excepto alloca
el área). #localsz
y #outsz
denotan el tamaño del área local (incluida el área de almacenamiento del par <x29, lr>
) y el tamaño del parámetro de salida, respectivamente.
Encadenadas, #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)
Encadenadas, #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 area
Funciones hoja sin cadena (
lr
sin guardar)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 area
Se tiene acceso a todas las variables locales en función de
sp
.<x29,lr>
apunta al marco anterior. Para el tamaño <del marco = 512,sub sp, ...
se puede optimizar si el área guardada de los registros se mueve a la parte inferior de la pila. El inconveniente es que no es coherente con otros diseños anteriores. Además, los reg guardados forman parte del intervalo de pares-regs y del modo de direccionamiento de desplazamiento previo y posterior a la indexación.Funciones no hoja sin cadena (se guarda
lr
en el área guardada 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 area
O con registros Int guardados 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 area
Solo
x19
guardado: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
* La asignación del área de guardado reg no se plega en porque
stp
no se puede representar un reg-lrstp
indizado previamente con los códigos de desenredado.Se tiene acceso a todas las variables locales en función de
sp
.<x29>
apunta al marco anterior.Encadenadas, #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 pair
En comparación con el primer ejemplo de prólogo anterior, este ejemplo tiene una ventaja: todas las instrucciones para guardar registros están listas para ejecutarse después de una sola instrucción de asignación de la pila. Esto significa que no hay ninguna anti-dependencia de
sp
que impida el paralelismo en el nivel de instrucción.Encadenadas, tamaño de marco > 512 (opcional para funciones sin
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 area
Con fines de optimización,
x29
se puede colocar en cualquier posición del área local para proporcionar una mejor cobertura para el "par reg" y el modo de direccionamiento de desplazamiento pre-/post indexado. Se puede tener acceso a los punteros de marco siguientes en función desp
.Encadenadas, tamaño de marco>4 K, con o sin 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 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>
Información de control de excepciones de ARM64
Registros .pdata
Los registros .pdata
consisten en una matriz ordenada de elementos de longitud fija que describen cada función de manipulación de la pila en un binario PE. La frase "manipulación de pila" es importante: funciones de hoja que no requieren ningún almacenamiento local y no necesitan guardar o restaurar registros no volátiles, no requieren un registro .pdata
. Estos registros deben omitirse explícitamente para ahorrar espacio. Un desenredado de una de estas funciones puede obtener la dirección de retorno directamente desde lr
para subir al autor de la llamada.
Cada registro .pdata
para ARM64 tiene una longitud de 8 bytes. El formato general de cada registro coloca la dirección virtual relativa de 32 bits del inicio de la función en la primera palabra, seguida de una segunda palabra que contiene el puntero de un bloque .xdata
de longitud variable o una palabra empaquetada que describe una secuencia de desenredado de función canónica.
Los campos son los siguientes:
RVA de inicio de la función es la RVA de 32 bits del inicio de la función.
Flag (Marca) es un campo de 2 bits que indica cómo interpretar los 30 bits restantes de la segunda palabra
.pdata
. Si Flag (Marca) es 0, el resto de los bits forma una Exception Information RVA (RVA de información de la excepción), con los dos bits inferiores implícitamente en 0. Si Flag (Marca) no es cero, el resto de los bits forma una estructura de Packed Unwind Data (Datos de desenredado empaquetados).RVA de información de la excepción es la dirección de la estructura de información de excepción de longitud variable, que se almacena en la sección
.xdata
. Estos datos deben tener una alineación de 4 bytes.Packed Unwind Data (Datos de desenredado empaquetados) es una descripción comprimida de las operaciones necesarias para desenredar desde una función, siempre y cuando el formato sea canónico. En este caso, no se necesita un registro
.xdata
.
Registros .xdata
Cuando el formato de desenredo empaquetado no es suficiente para describir el desenredo de una función, se debe crear un registro .xdata
de longitud variable. La dirección de este registro se almacena en la segunda palabra del registro .pdata
. El formato de .xdata
es un conjunto de palabras empaquetado y de longitud variable:
Estos datos se dividen en cuatro secciones:
Encabezado de 1 o 2 palabras que describe el tamaño general de la estructura y proporciona datos clave de función. La segunda palabra solo está presente si los campos Epilog Count (Recuento de epílogos) y Code Words (Palabras de código) están establecidos en 0. El encabezado tiene estos campos de bits:
a. Function Length (Longitud de la función) es un campo de 18 bits. Indica la longitud total de la función en bytes, dividida entre 4. Si una función es mayor de 1M, hay que usar varios registros
.pdata
y.xdata
para describirla. Para obtener más información, vea la sección Funciones de gran tamaño.b. Vers es un campo de 2 bits. Describe la versión del resto de
.xdata
. Actualmente, solo se define la versión 0, por lo que no se permiten valores de 1 a 3.c. X es un campo de 1 bit. Indica la presencia (1) o ausencia (0) de datos de excepción.
d. E es un campo de 1 bit. Indica que la información que describe un único epílogo está empaquetada en el encabezado (1) en lugar de requerir más palabras de ámbito posteriormente (0).
e. Epilog Count (Recuento de epílogos) es un campo de 5 bits que tiene dos significados, en función del estado del bit E:
Si E es 0, especifica el recuento del número total de ámbitos de epílogos descritos en la sección 2. Si hay más de 31 ámbitos en la función, el campo Code Words (Palabras de código) se debe establecer en 0 para indicar que se necesita una palabra de extensión.
Si E es 1, este campo especifica el índice del primer código de desenredado que describe el único epílogo.
f. Code Words (Palabras de código) es un campo de 5 bits que especifica el número de palabras de 32 bits necesario para contener todos los códigos de desenredado en la sección 3. Si se requieren más de 31 palabras (es decir, 124 códigos de desenredado), este campo debe ser 0 para indicar que se requiere una palabra de extensión.
g. Extended Epilog Count (Recuento de epílogos ampliados) y Extended Code Words (Palabras de código ampliadas) son campos de 16 y 8 bits, respectivamente. Proporcionan más espacio para poder codificar un número inusualmente grande de epílogos o de palabras de código de desenredado. La palabra de extensión que contiene estos campos solo está presente si los campos Epilog Count (Recuento de epílogos) y Code Words (Palabras de código) de la primera palabra del encabezado son 0.
Si el recuento de epílogos no es cero, una lista de información sobre ámbitos de epílogo, empaquetado uno a una palabra, viene después del encabezado y el encabezado extendido opcional. Se almacenan en orden de aumento de desplazamiento inicial. Cada ámbito contiene los siguientes bits:
a. Epilog Start Offset (Desplazamiento inicial del epílogo) es un campo de 18 bits que tiene el desplazamiento del epílogo en bytes, dividido entre 4, con respecto al inicio de la función.
b. Res es un campo de 4 bits reservado para futuras expansiones. Su valor debe ser 0.
c. Epilog Start Index (Índice inicial del epílogo) es un campo de 10 bits, 2 bits más que Extended Code Words (Palabras de código ampliadas). Indica el índice de bytes del primer código de desenredado que describe este epílogo.
Tras la lista de ámbitos del epílogo viene una matriz de bytes que contiene códigos de desenredado, que se detallan en profundidad en una sección posterior. Esta matriz se rellena al final del límite de palabra completa más cercano. Los códigos de desenredado se escriben en esta matriz. Comienzan con el más cercano al cuerpo de la función y se desplazan hacia los bordes de esta. Los bytes de cada código de desenredado se almacenan en orden big-endian, de modo que se captura primero el byte más significativo, que identifica la operación y la longitud del resto del código.
Por último, después de los bytes del código de desenredado, si el bit X del encabezado se ha establecido en 1, aparece la información del controlador de excepciones. Consta de una única Exception Handler RVA (RVA del controlador de excepciones) que proporciona la dirección del propio controlador de excepciones. Va seguido inmediatamente de una cantidad de datos de longitud variable que requiere el controlador de excepciones.
El registro .xdata
está diseñado de forma que se pueden capturar los primeros 8 bytes y usarlos para calcular el tamaño completo del registro, menos la longitud de los datos de excepción de tamaño variable que le siguen. En el fragmento de código siguiente se calcula el tamaño del 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;
}
Aunque el prólogo y cada epílogo tienen su propio índice en los códigos de desenredado, comparten la tabla. Es completamente posible (y no es del todo inusual) que puedan compartir todos los mismos códigos. (Para obtener un ejemplo, vea el ejemplo 2 en la secciónejemplos).Los escritores de compiladores deben optimizar para este caso en particular. Dado que el índice más grande que se puede especificar es 255, lo cual limita el número total de códigos de desenredado posibles de una función en particular.
Códigos de desenredado
La matriz de códigos de desenredado es un conjunto de secuencias que describen exactamente cómo deshacer los efectos del prólogo. Se almacenan en el mismo orden en que se deben deshacer las operaciones. Los códigos de desenredado se pueden considerar como un conjunto pequeño de instrucciones, codificado como una cadena de bytes. Una vez completada la ejecución, la dirección de retorno a la función de llamada está en el lr
registro. Y todos los registros no volátiles se restauran a sus valores en el momento en el que se ha llamado a la función.
Si existiera la certeza de que solo pueden producirse excepciones dentro de un cuerpo de función, y nunca en un prólogo o en un epílogo, solo sería necesaria una única secuencia. Sin embargo, el modelo de desenredado de Windows requiere que el código se pueda desenredar desde un prólogo o epílogo parcialmente ejecutado. A fin de cumplir este requisito, los códigos de desenredado se han diseñado meticulosamente de forma que tienen una asignación sin ambigüedad de 1:1 para cada código de operación relevante en el prólogo y el epílogo. Este diseño tiene varias implicaciones:
Mediante el recuento del número de códigos de desenredado, es posible calcular la longitud del prólogo y el epílogo.
Mediante el recuento del número de instrucciones pasado el inicio de un ámbito de epílogo, es posible omitir el número equivalente de códigos de desenredado. Podemos ejecutar el resto de una secuencia para completar el desenredado parcialmente ejecutado que realiza el epílogo.
Mediante el recuento del número de instrucciones antes del final del prólogo, es posible omitir el número equivalente de códigos de desenredado. Podemos ejecutar el resto de la secuencia para deshacer solo las partes del prólogo que hayan finalizado su ejecución.
Los códigos de desenredado se codifican según la tabla siguiente. Todos los códigos de desenredado son un byte simple o doble, excepto el que asigna una pila enorme (alloc_l
). Hay 22 códigos de desenredado en total. Cada código de desenredado asigna exactamente una instrucción en el prólogo o epílogo para permitir el desenredado de los prólogos y epílogos parcialmente ejecutados.
Código de desenredado | Bits e interpretación |
---|---|
alloc_s |
000xxxxx: asignar una pila pequeña de tamaño < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzz: guardar <x19,x20> par en [sp-#Z*8]! , desplazamiento preindizado >= -248 |
save_fplr |
01zzzzzz: guarda el par <x29,lr> en [sp+#Z*8] , desplazamiento <= 504. |
save_fplr_x |
10zzzzzz: guardar <x29,lr> par en [sp-(#Z+1)*8]! , desplazamiento preindizado >= -512 |
alloc_m |
11000xxx'xxxxxxxx: asigne una pila grande con el tamaño < 32K (2^11 * 16). |
save_regp |
110010xx'xxzzzzzz: save x(19+#X) pair at [sp+#Z*8] , offset <= 504 |
save_regp_x |
110011xx'xxzzzzzzzz: guardar par x(19+#X) en [sp-(#Z+1)*8]! , desplazamiento preindizado >= -512 |
save_reg |
110100xx'xxzzzzzz: save reg x(19+#X) at [sp+#Z*8] , offset <= 504 |
save_reg_x |
1101010x'xxxzzzzz: save reg x(19+#X) at [sp-(#Z+1)*8]! , desplazamiento preindizado >= -256 |
save_lrpair |
1101011x'xxzzzzzz: guarda el par <x(19+2*#X),lr> en [sp+#Z*8] , desplazamiento <= 504 |
save_fregp |
1101100x'xxzzzzzz: guardar par d(8+#X) en [sp+#Z*8] , desplazamiento <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: guardar par d(8+#X) en [sp-(#Z+1)*8]! , desplazamiento preindizado >= -512 |
save_freg |
1101110x'xxzzzzzzzz: save reg d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_freg_x |
11011110'xxxzzzzz: guardar reg d(8+#X) en [sp-(#Z+1)*8]! , desplazamiento preindizado >= -256 |
alloc_l |
11100000'xxxxxxxx'xxxxxxxx'xxxxxxxx: asignar una pila grande de tamaño < 256 M (2^24 *16) |
set_fp |
11100001: configurar x29 con mov x29,sp |
add_fp |
11100010'xxxxxxxx: configurado x29 con add x29,sp,#x*8 |
nop |
11100011: no se requiere ninguna operación de desenredado. |
end |
11100100: final del código de desenredado. Implica ret en el epílogo. |
end_c |
11100101: final del código de desenredado en el ámbito encadenado actual. |
save_next |
11100110: guardar el siguiente par de registros Int o FP no volátiles. |
11100111: reservado. | |
11101xxx: reservado para los casos de pila personalizados siguientes generados solo para las rutinas ASM | |
11101000: pila personalizada para MSFT_OP_TRAP_FRAME |
|
11101001: pila personalizada para MSFT_OP_MACHINE_FRAME |
|
11101010: pila personalizada para MSFT_OP_CONTEXT |
|
11101011: pila personalizada para MSFT_OP_EC_CONTEXT |
|
11101100: pila personalizada para MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: reservado | |
11101110: reservado | |
11101111: reservado | |
11110xxx: reservado | |
11111000'yyy : reservado | |
11111001'yy'yy : reserved | |
11111010'yy'aaaa'y: reserved | |
11111011'yy'yy'yy'aaaa'y : reserved | |
pac_sign_lr |
11111100: firmar la dirección de retorno en lr con pacibsp |
11111101: reservado | |
11111110: reservado | |
11111111: reservado |
En las instrucciones en las que hay valores grandes que ocupan múltiples bytes, los bits más relevantes son los que están almacenados en primer lugar. Este diseño permite encontrar el tamaño total, en bytes, del código de desenredado con solo buscar el primer byte del código. Dado que cada código de desenredado se asigna exactamente a una instrucción en un prólogo o epílogo, se puede calcular el tamaño del prólogo o epílogo. Recorrer desde el principio de la secuencia hasta el final y usar una tabla de búsqueda, o un dispositivo similar, para averiguar la longitud del código de operación correspondiente.
No se permite el direccionamiento de desplazamiento posterior al índice en un prólogo. Todos los intervalos de desplazamiento (#Z) coinciden con la codificación de stp
/str
direccionamiento, excepto save_r19r20_x
, en el que 248 es suficiente para todas las áreas de ahorro (10 registros Int + 8 registros FP + 8 registros de entrada).
save_next
debe seguir un par de registros volátiles de tipo guardar para Int o FP: save_regp
, save_regp_x
, save_fregp
, save_fregp_x
, save_r19r20_x
u otro save_next
. Guarda el par de registros siguiente en la ranura siguiente de 16 bytes en orden "creciente". Un elemento save_next
hace referencia al primer par de registros FP cuando sigue el elemento save-next
que denota el último par de registros Int.
Dado que los tamaños de las instrucciones de retorno y salto normales son los mismos, no es necesario un código de desenredado separado end
en escenarios de llamada final.
end_c
está diseñado para administrar fragmentos de función no contiguos con fines de optimización. Que end_c
indica el final de los códigos de desenredado en el ámbito actual debe ir seguido de otra serie de códigos de desenredado que terminan con un valor real end
. Los códigos de desenredado entre end_c
y end
representan las operaciones de prólogo en la región primaria (un prólogo "fantasma"). En la sección siguiente se describen más detalles y ejemplos.
Datos de desenredado empaquetados
Se pueden usar datos de desenredado empaquetados en las funciones cuyos prólogos y epílogos sigan el formato canónico descrito a continuación. Elimina por completo la necesidad de un registro .xdata
y reduce notablemente el costo necesario para proporcionar datos de desenredado. Los prólogo y epílogos canónicos están diseñados para cumplir los requisitos comunes de una función simple: uno que no requiere un controlador de excepciones y que realiza sus operaciones de configuración y desmontaje en un orden estándar.
El formato de un registro .pdata
con datos de desenredado empaquetados tiene el aspecto siguiente:
Los campos son los siguientes:
- RVA de inicio de la función es la RVA de 32 bits del inicio de la función.
- La marca es un campo de 2 bits como se ha descrito anteriormente, con los significados siguientes:
- 00 = datos de desenredado empaquetados no utilizados; los bits restantes apuntan a un registro
.xdata
- 01 = se usan datos de desenredado empaquetados con un solo prólogo y epílogo al principio y al final del ámbito.
- 10 = se usan datos de desenredado empaquetados para el código sin ningún prólogo ni epílogo. Útil para describir segmentos de funciones independientes.
- 11 = reservado.
- 00 = datos de desenredado empaquetados no utilizados; los bits restantes apuntan a un registro
- Function Length (Longitud de la función) es un campo de 11 bits que proporciona la longitud de toda la función, en bytes, dividida entre 4. Si la función supera los 8 KB, se deberá usar en su lugar un registro
.xdata
completo. - Frame Size (Tamaño de marco) es un campo de 9 bits que indica el número de bytes de la pila asignado para esta función, dividido entre 16. Las funciones que asignan más de 8-16 KB de la pila deben usar un registro
.xdata
completo. Incluye el área de variables locales, el área de parámetros de salida, el área de Int y FP guardados por el destinatario y el área de parámetros de inicio. Excluye el área de asignación dinámica. - CR es una marca de 2 bits que indica si la función incluye instrucciones adicionales para configurar una cadena de fotogramas y un vínculo de retorno:
- 00 = función sin cadena,
<x29,lr>
el par no se guarda en la pila - 01 = función no encadenada;
<lr>
se guarda en la pila. - 10 = función encadenada con una
pacibsp
dirección de retorno firmada - 11 = función encadenada; se usa una instrucción de par de carga o almacenamiento en el prólogo o epílogo
<x29,lr>
.
- 00 = función sin cadena,
- H es una marca de 1 bit que indica si la función aloja los registros de parámetros de entero (x0-x7) almacenándolos en el inicio de la función. (0 = no aloja registros, 1 = aloja registros).
- RegI es un campo de 4 bits que indica el número de registros INT no volátiles (x19-x28) guardados en la ubicación de pila canónica.
- RegF es un campo de 3 bits que indica el número de registros FP no volátiles (d8-d15) guardados en la ubicación de pila canónica. (RegF = 0: no se guarda ningún registro FP; RegF>0: se guardan los registros RegF+1 FP). Los datos de desenredado empaquetados no se pueden usar para una función que solo guarde un registro FP.
Los prólogos canónicos que se encuentran en las categorías 1, 2 (sin área de parámetros de salida), 3 y 4 en la sección anterior se pueden representar con el formato de desenredado empaquetado. Los epílogos de las funciones canónicas siguen un formato similar, salvo que H no tiene ningún efecto, se omite la instrucción set_fp
y el orden de los pasos y las instrucciones de cada paso se invierten en el epílogo. El algoritmo para .xdata
empaquetados sigue estos pasos, que se detallan en la tabla siguiente:
Paso 0: Preproceso del tamaño de cada área.
Paso 1: Firmar la dirección de devolución.
Paso 2: Guardar registros guardados en int callee.
Paso 3: este paso es específico para el tipo 4 en las secciones iniciales. lr
se guarda al final del área Int.
Paso 4: Guardar registros guardados por destinatarios de FP.
Paso 5: Guardar argumentos de entrada en el área de parámetros principal.
Paso 6: Asigne la pila restante, incluido el área local, <x29,lr>
el par y el área de parámetros salientes. 6a corresponde al tipo canónico 1. 6b y 6c son para el tipo canónico 2. 6d y 6e son para el tipo 3 y el tipo 4.
Núm. de paso | Valores de marca | Núm. de instrucciones | Código de operación | Código de desenredado |
---|---|---|---|---|
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_x save_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)] |
nop nop nop nop |
6a | (CR == 10 || CR == 11) &&#locsz <= 512 |
2 | stp x29,lr,[sp,#-locsz]! mov x29,sp *** |
save_fplr_x set_fp |
6b | (CR == 10 || CR == 11) && 512 < #locsz <= 4080 |
3 | sub sp,sp,#locsz stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m save_fplr set_fp |
6c | (CR == 10 || CR == 11) &&#locsz > 4080 |
4 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m alloc_s /alloc_m save_fplr set_fp |
6d | (CR == 00 || CR == 01) &&#locsz <= 4080 |
1 | sub sp,sp,#locsz |
alloc_s /alloc_m |
6e | (CR == 00 || CR == 01) &&#locsz > 4080 |
2 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) |
alloc_m alloc_s /alloc_m |
* Si CR == 01 y RegI es un número impar, el paso 3 y el último save_reg
en el paso 2 se combinan en un save_regp
.
** Si RegI == CR == 0 y RegF != 0, el primer stp
para el punto flotante realiza el predecremento.
*** No existe ninguna instrucción correspondiente a mov x29,sp
en el epílogo. Los datos de desenredado empaquetados no se pueden usar si una función requiere la restauración de sp
desde x29
.
Desenredado de prólogos y epílogos parciales
En las situaciones de desenredado más comunes, la excepción o llamada se produce en el cuerpo de la función, que no tiene nada que ver con el prólogo y ninguno de los epílogos. En estas situaciones, el desenredado es sencillo: el desenredador simplemente ejecuta los códigos de la matriz de desenredado. Comienza en el índice 0 y continúa hasta que se detecta un end
código de operación.
Es más difícil desenredar correctamente en caso de que se produzca una excepción o una interrupción mientras se ejecuta un prólogo o un epílogo. En estas situaciones, el marco de pila solo se construye parcialmente. El problema es determinar exactamente lo que se ha hecho, para deshacerlo correctamente.
Por ejemplo, tome esta secuencia de prólogo y epílogo:
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
Junto a cada código de operación se encuentra el código de desenredado adecuado que describe esta operación. Se puede ver cómo la serie de códigos de desenredado del prólogo es una imagen exacta reflejada de los códigos de desenredado del epílogo, sin contar su instrucción final. Esto es una situación habitual y constituye el motivo por el cual siempre asumimos que los códigos de desenredado del prólogo se almacenan en orden inverso al orden de ejecución del prólogo.
Por lo tanto, para el prólogo y el epílogo, nos quedamos con un conjunto habitual de códigos de desenredado:
set_fp
, save_regp 0,240
, save_fregp,0,224
, , save_fplr_x_256
, end
El caso de epílogo es sencillo, ya que está en orden estándar. A partir del desplazamiento 0 en el epílogo (que comienza en el desplazamiento 0x100 en la función), esperamos ejecutar la secuencia de desenredado completa, ya que aún no se ha realizado ninguna limpieza. Si ya hemos avanzado una instrucción (en el desplazamiento 2 del epílogo), podemos desenredar correctamente omitiendo el primer código de desenredado. Se puede generalizar esta situación y suponer una asignación de 1:1 entre códigos de operación y códigos de desenredado. Después, para iniciar el desenredado de la instrucción n en el epílogo, deberíamos omitir los primeros n códigos de desenredado y comenzar a ejecutar desde allí.
Resulta que funciona una lógica similar para el prólogo, salvo que en orden inverso. Si comenzamos a desenredar desde el desplazamiento 0 en el prólogo, queremos que no se ejecute nada. Si desenredamos desde el desplazamiento 2, que es una avanzar una instrucción, querremos empezar a ejecutar la secuencia de desenredado un código de desenredado del final. (Recuerde que los códigos se almacenan en orden inverso). Y aquí también podemos generalizar: si empezamos a desenredar desde la instrucción n en el prólogo, deberíamos empezar a ejecutar n códigos de desenredado desde el final de la lista de códigos.
Los códigos de prólogo y epílogo no siempre coinciden exactamente, por lo que es posible que la matriz de desenredado tenga que contener varias secuencias de códigos. Use la lógica siguiente para determinar el desplazamiento por el que empezar a procesar códigos:
Si se desenreda desde el cuerpo de la función, comience a ejecutar códigos de desenredado en el índice 0 y continúe hasta alcanzar un
end
código de operación.Si se desenreda desde un epílogo, utilice el índice de inicio específico de dicho epílogo, suministrado con el ámbito del epílogo como un punto de inicio. Calcule el número de bytes del PC en cuestión desde el inicio del epílogo. Después avance por los códigos de desenredado, omitiendo los códigos de desenredado hasta haber pasado por todas las instrucciones que ya se han ejecutado. Luego ejecute a partir de ese punto.
Si el desenredado se encuentra en el prólogo, use el índice 0 como punto de partida. Calcule la longitud del código del prólogo desde la secuencia y, después, calcule el número de bytes del PC en cuestión desde el final del prólogo. Después avance por los códigos de desenredado, omitiendo los códigos de desenredado hasta haber pasado por todas las instrucciones que todavía no se hayan ejecutado. Luego ejecute a partir de ese punto.
Estas reglas significan que los códigos de desenredado del prólogo siempre deben ser los primeros en la matriz. También son los códigos que se usan para desenredar en el caso general de desenredado desde el cuerpo. Las secuencias de código específicas de los epílogos deben ir inmediatamente después.
Fragmentos de función
Con fines de optimización de código y otras razones, puede ser preferible dividir una función en fragmentos separados (también denominados regiones). Cuando se divide, cada fragmento resultante de la función necesita tener su propio registro .pdata
independiente (y, probablemente, también uno .xdata
).
Para cada fragmento secundario independiente que tenga su propio prólogo, se espera que no se realice ningún ajuste de la pila en su prólogo. Todo el espacio de la pila necesario para una región secundaria lo debe asignar previamente su región primaria (o región del host). Esta asignación previa mantiene la manipulación de punteros de pila estrictamente en el prólogo original de la función.
Un caso típico de fragmentos de función es "separación de código", donde el compilador puede mover una región del código fuera de su función host. Hay tres casos inusuales que podrían resultar de la separación de código.
Ejemplo
(región 1: inicio)
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ón 1: final)
(región 3: inicio)
...
(región 3: final)
(región 2: inicio)
... 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ón 2: final)
Solo prólogo (región 1: todos los epílogos se encuentran en regiones independientes):
Solo se debe describir el prólogo. Este prólogo no se puede representar en el formato
.pdata
compacto. En el caso completo de.xdata
, se puede representar estableciendo el valor Epilog Count (Recuento de epílogos) = 0. Vea la región 1 en el ejemplo anterior.Códigos de desenredado:
set_fp
,save_regp 0,240
,save_fplr_x_256
yend
.Solo epílogos (región 2: el prólogo está en la región del host):
Se supone que el control de tiempo salta a esta región, se han ejecutado todos los códigos de prólogo. El desenredado parcial puede producirse en los epílogos de la misma manera que en una función normal. Este tipo de región no se puede representar mediante el formato
.pdata
compacto. En un registro completo.xdata
, se puede codificar con un prólogo "fantasma", entre corchetes por unend_c
par de código yend
desenredado. El elementoend_c
inicial indica que el tamaño del prólogo es cero. El campo Epilog Start Index (Índice inicial del epílogo) de los puntos de epílogo únicos apunta aset_fp
.Códigos de desenredado para la región 2:
end_c
,set_fp
,save_regp 0,240
,save_fplr_x_256
yend
.Ningún registro ni epílogo (región 3: los prólogos y todos los epílogos están en otros fragmentos):
El formato
.pdata
compacto se puede aplicar a través de configurar el campo Flag (Marca) = 10. Con el registro.xdata
completo, Epilog Count (Recuento de epílogos) = 1. El código de desenredado es el mismo que el código de la región 2 anterior, pero el campo Epilog Start Index (Índice inicial del epílogo) también apunta aend_c
. El desenredado parcial nunca se producirá en esta región de código.
Otro caso más complicado de fragmentos de función es la "reducción hasta ajustar". El compilador puede optar por retrasar el guardado de algunos registros guardados por el destinatario hasta fuera del prólogo de entrada de función.
(región 1: inicio)
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ón 2: inicio)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(región 2: final)
... 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ón 1: final)
En el prólogo de la región 1, el espacio de pila está preasignado. Se puede ver que la región 2 tendrá el mismo código de desenredado incluso si se quita de su función de host.
Región 1: set_fp
, save_regp 0,240
, save_fplr_x_256
, . end
El índice de inicio de epílogo apunta a set_fp
como de costumbre.
Región 2: save_regp 2, 224
, end_c
, set_fp
, save_regp 0,240
, save_fplr_x_256
y end
. El campo Epilog Start Index (Índice inicial del epílogo) apunta al primer código de desenredado save_regp 2, 224
.
Funciones de gran tamaño
Se pueden usar fragmentos para describir funciones con un tamaño superior al límite de 1M impuesto por los campos de bits en el encabezado de .xdata
. Para describir una función muy grande como esta, debe dividirse en fragmentos menores que 1 M. Cada fragmento se debe ajustar de forma que no divida un epílogo en varias partes.
Solo el primer fragmento de la función contendrá un prólogo; todos los demás están marcados como carentes de prólogo. Según cuál sea el número de epílogos presentes, cada fragmento puede contener cero o más epílogos. Recuerde que cada ámbito de epílogo en un fragmento especifica su desplazamiento de inicio con respecto al inicio del fragmento, no al inicio de la función.
Si un fragmento no tiene ni prólogo ni epílogo, seguirá necesitando su propio registro .pdata
(y, probablemente, también un registro .xdata
) para describir cómo llevar a cabo el desenredo desde el cuerpo de la función.
Ejemplos
Ejemplo 1: Encadenado de fotogramas, formato compacto
|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]
Ejemplo 2: Encadenado de fotogramas, forma completa con prólogo y epílogo reflejado
|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
El campo Epilog Start Index (Índice inicial del epílogo) [0] apunta a la misma secuencia del código de desenredado del prólogo.
Ejemplo 3: Función sin cadena variadic
|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
El campo Epilog Start Index (Índice inicial del epílogo) [4] apunta al centro del código de desenredado del prólogo (reutiliza parcialmente la matriz de desenredado).
Consulte también
Información general sobre las convenciones ABI de ARM64
Control de excepciones de ARM