¿Por qué Windows de 64 bits no puede desenrollar las excepciones de usuario-kernel-usuario?
64bit windows64 (2)
Una muy buena pregunta.
Puedo dar una pista de por qué "propagar" una excepción a través del límite kernel-usuario es algo problemático.
Cita de su pregunta:
¿Por qué no puede Windows de 64 bits desenrollar la pila durante una excepción, si la pila cruza el límite del kernel , cuando Windows de 32 bits puede?
La razón es muy simple: no existe tal cosa como "la pila cruza el límite del kernel". Llamar a una función de modo kernel no es en modo alguno comparable a una llamada de función estándar. No tiene nada que ver con la pila de llamadas en realidad. Como probablemente sepa, la memoria en modo kernel es simplemente inaccesible desde el modo de usuario.
La invocación de una función de kernel-mode (también conocida como syscall ) se implementa activando una interrupción de software (o un mecanismo similar). Un código de modo de usuario pone algunos valores en los registros (que identifican el servicio de modo kernel necesario) e invoca una instrucción de CPU (como sysenter
) que transfiere la CPU al modo kernel y pasa el control al sistema operativo.
Luego hay un código en modo kernel que maneja el syscall solicitado. Se ejecuta en una pila de kernel por separado (que no tiene nada que ver con la pila de modo de usuario). Después de que se manejó la solicitud, el control vuelve al código de modo de usuario. Dependiendo de la llamada de sistema específica, la dirección de retorno del modo de usuario puede ser la que invocó la transacción en modo kernel, así como también puede ser una dirección diferente.
Algunas veces llama a una función de modo núcleo que "en el medio" debe invocar una llamada en modo usuario. Puede parecer una pila de llamadas que consiste en un código de usuario-núcleo-usuario, pero es solo una emulación . En tal caso, el código kernel-mode transfiere el control a un código de modo de usuario que envuelve su función de modo de usuario. Este código envoltorio llama a su función, e inmediatamente después de su retorno desencadena una transacción en modo kernel.
Ahora, si el código del modo de usuario "invocado desde el modo kernel" genera una excepción, esto es lo que debería suceder:
- El código del modo de usuario del contenedor maneja la excepción SEH (es decir, detiene su propagación, pero aún no realiza el desenrollado de la pila).
- Pasa el control al kernel-mode (OS), como en un caso de flujo de programa normal.
- El código del modo Kenrel responde de manera adecuada. Termina el servicio solicitado. Según si hubo una excepción de modo de usuario, el procesamiento puede ser diferente.
- Al regresar al modo usuario, el código kernel-mode puede especificar si hubo una excepción anidada. En caso de una excepción, la pila no se restaura a su estado original (ya que aún no se ha desenrollado).
- El código de modo de usuario comprueba si hubo tal excepción. Si lo fue, la pila de llamadas se falsifica para incluir la llamada anidada en modo usuario y la excepción se propaga.
Entonces esa excepción que cruza el límite kernel-usuario es una emulación . No hay tal cosa de forma nativa.
¿Por qué no puede Windows de 64 bits desenrollar la pila durante una excepción, si la pila cruza el límite del kernel, cuando Windows de 32 bits puede?
El contexto de toda esta pregunta proviene de:
Fondo
En Windows de 32 bits, si lanzo una excepción en mi código de modo de usuario , que se llamó desde el código del modo kernel , se llamó desde mi código de modo de usuario , por ejemplo:
User mode Kernel Mode
------------------ -------------------
CreateWindow(...); ------> NtCreateWindow(...)
|
WindowProc <---------------------+
el Manejo estructurado de excepciones (SEH) en Windows puede desenrollar la pila, volviendo a pasar por el modo kernel, volver a mi código de usuario, donde puedo manejar la excepción y veo un seguimiento de pila válido.
Pero no en Windows de 64 bits
Las ediciones de 64 bits de Windows no pueden hacer esto:
Por razones complicadas, no podemos propagar la excepción en sistemas operativos de 64 bits (amd64 e IA64). Este ha sido el caso desde la primera versión de 64 bits de Server 2003. En x86, este no es el caso: la excepción se propaga a través del límite del núcleo y terminaría haciendo retroceder los marcos
Y como en este caso no hay forma de seguir un rastro de pila confiable, tuvimos que tomar una decisión: dejar ver la excepción no absurda u ocultarla por completo:
Los arquitectos kernel en ese momento decidieron adoptar el enfoque conservador de AppCompat: ocultar la excepción y esperar lo mejor.
A continuación, el artículo habla de cómo se comportaron así todos los sistemas operativos Windows de 64 bits:
- Windows XP de 64 bits
- Windows Server 2003 de 64 bits
- Windows Vista de 64 bits
- Windows Server 2008 de 64 bits
Pero a partir de Windows 7 (y Windows Server 2008), los arquitectos cambiaron de opinión, más o menos. Para aplicaciones de solo 64 bits (no aplicaciones de 32 bits), dejarían (por defecto) de suprimir estas excepciones de usuarios de kernel de usuario. Entonces, por defecto, en:
- Windows 7 de 64 bits
- Windows Server 2008
todas las aplicaciones de 64 bits verán estas excepciones, donde nunca solían verlas.
En Windows 7, cuando una aplicación nativa x64 falla de esta manera, se notifica al Asistente de compatibilidad de programas . Si la aplicación no tiene un manifiesto de Windows 7 , mostramos un cuadro de diálogo que le informa que PCA ha aplicado un ajuste de compatibilidad de aplicaciones . ¿Qué significa esto? Esto significa que la próxima vez que ejecute su aplicación, Windows emulará el comportamiento de Server 2003 y hará que la excepción desaparezca. Tenga en cuenta que PCA no existe en Server 2008 R2, por lo que este consejo no se aplica.
Entonces la pregunta
La pregunta es: ¿por qué Windows de 64 bits no puede desenrollar una pila a través de una transición de núcleo, mientras que las ediciones de 32 bits de Windows sí?
La única pista es:
Por razones complicadas, no podemos propagar la excepción en sistemas operativos de 64 bits (amd64 e IA64).
La pista es que es complicado .
Es posible que no entienda la explicación, ya que no soy un desarrollador de sistemas operativos, pero me gustaría tener una oportunidad de saber por qué.
Actualización: revisión para detener la supresión de aplicaciones de 32 bits
Microsoft ha lanzado una revisión que permite que las aplicaciones de 32 bits tampoco tengan las excepciones suprimidas:
KB976038: las excepciones que se lanzan desde una aplicación que se ejecuta en una versión de Windows de 64 bits se ignoran
- Una excepción que se lanza en una rutina de devolución de llamada se ejecuta en el modo de usuario.
En este escenario, esta excepción no causa la falla de la aplicación. En cambio, la aplicación entra en un estado inconsistente. Entonces, la aplicación arroja una excepción diferente y se bloquea.
Una función de devolución de llamada del modo de usuario es típicamente una función definida por la aplicación que es llamada por un componente de modo kernel. Ejemplos de funciones de devolución de llamada del modo de usuario son procedimientos de Windows y procedimientos de enlace. Windows llama a estas funciones para procesar mensajes de Windows o procesar eventos de enlace de Windows.
A continuación, la revisión le permite evitar que Windows come las excepciones a nivel mundial:
HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options DisableUserModeCallbackFilter: DWORD = 1
o por aplicación:
HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/Notepad.exe DisableUserModeCallbackFilter: DWORD = 1
El comportamiento también se documentó en XP y Server 2003 en KB973460:
Una pista
Encontré otra pista cuando investigo usando xperf para capturar trazas de pila en Windows de 64 bits:
Apilar caminando en Xperf
Deshabilitar el paginado ejecutivo
Para que el seguimiento funcione en Windows de 64 bits, debe establecer la clave de registro DisablePagingExecutive . Esto le dice al sistema operativo que no pague los controladores del modo kernel y el código del sistema en el disco, que es un requisito previo para obtener pilas de llamadas de 64 bits utilizando xperf, ya que la pila de 64 bits depende de los metadatos en las imágenes ejecutables y, en algunas situaciones, El código xperf stack walk no tiene permitido tocar las páginas paginadas . Ejecutar el siguiente comando desde un símbolo del sistema elevado configurará esta clave de registro para usted.
REG ADD "HKLM/System/CurrentControlSet/Control/Session Manager/Memory Management" -v DisablePagingExecutive -d 0x1 -t REG_DWORD -f
Después de configurar esta clave de registro, deberá reiniciar su sistema para poder grabar las pilas de llamadas. Tener este indicador configurado significa que el núcleo de Windows bloquea más páginas en la RAM, por lo que probablemente consuma alrededor de 10 MB de memoria física adicional.
Esto da la impresión de que en Windows de 64 bits (y solo en Windows de 64 bits), no puede recorrer pilas de kernel porque puede haber páginas en el disco.
Soy el desarrollador que escribió este Hotfix hace muchísimo tiempo, así como la publicación del blog. La razón principal es que el archivo de registro completo no siempre se captura cuando realiza la transición al espacio del núcleo, por razones de rendimiento.
Si realiza una llamada de sistema normal, la Interfaz binaria de aplicaciones (ABI) x64 solo requiere que preserve los registros no volátiles (similar a hacer una llamada de función normal). Sin embargo, desenrollar correctamente la excepción requiere que tenga todos los registros, por lo que no es posible. Básicamente, esta fue una elección entre perf en un escenario crítico (es decir, un escenario que potencialmente ocurre miles de veces por segundo) frente al 100% manejando correctamente un escenario patológico (un crash).