De excepciones, del depurador, de Windows y de Visual Studio

Esto leyendo la quinta edición de Windows Internals, que cubre Windows Vista y Windows Server 2008 y es la última versión disponible del libro. Allá por los años del Windows 95 y del NT 4.0, un poco antes, un poco después, estaba yo muy interesado en las tripillas de los sistemas operativos, y aparte de empacharme con algún que otro mamotreto genérico sobre el tema, le di caña a los equivalentes, como Windows 95, al descubierto, o el Windows NT de Helen Custer, así como otros de similar temática. Vamos, que prácticamente me leí todo lo que pude conseguir sobre las interioridades de esos sistemas operativos.

Pero desde hace un tiempo me veo en la tesitura de que, conversando aquí y allí, me he dado cuenta de que me he quedado bastante obsoleto en el tema, con cosas que no sólo han sido mejoradas, sino que ahora funcionan de forma completamente diferente, así que me he puesto las pilas y le estoy dando un buen repaso al libro…

Bueno, pues como iba diciendo, el capítulo 3 del citado no es que sea nada del otro mundo. Bueno, sí, lo cierto es que como no sepas de programación de hardware y hayas hecho algo, te vas a quedar poco menos que a dos velas con muchas de las cosas que aparecen, ya que tienen que ver con la gestión de interrupciones, tanto de software como de hardware, así como las excepciones y demás intríngulis que todo sistema operativo ha de cumplir.

Personalmente yo he hecho un par de proyectos basados en un microprocesador, aunque generalmente, cuando lo hago, suelo trastear con microcontroladores más o menos potentes, y la verdad es que muchas de las cosas que aparecen ahí explicadas, con la evidente diferencia entre un sistema operativo como es Windows y lo que un mindundi como yo haya podido hacer, me las he encontrado en mis desarrollos. Tener en cuenta las prioridades y los niveles de las interrupciones, programarlos y cambiarlos en tiempo real, instalar y quitar vectores de interrupción, etc.

Por lo menos eso es de lo que trata la parte del capítulo 3 que he leído, que ciertamente me parece demasiado detallista en cosas que, la verdad, a no ser que vayas a meterte de lleno en la creación de drivers primarios para Windows, de poco te sirve. Y si sabes de sistemas operativos, lo único llamativo es que te enteras de que Windows no es mucho más diferente que otros sistemas.

Volviendo al tema (lo que me mola irme por las ramas), sobre la página ciento y pico (je je, me quedan todavía más de mil cien por leer, ¡qué emoción!) se pone a hablar del control de las excepciones del sistema operativo, y cómo son controladas en conjunción con el software de aplicación (ese que tu y yo hacemos) y el propio sistema. Y entonces te dice que si no tienes claro cómo funciona a nivel de programador, que mires los capítulos finales de Windows via C/C++. Y como resulta que tengo ese libro en la lista de pendientes (hace unos años leí y estudié la misma versión pero mucho más antigua, y encima en castellano), pues dejé uno y me puse con el otro, máxime recordando que fueron precisamente esos capítulos los que no leí en la edición anterior.

El sistema de excepciones de Windows es muy potente y versátil, y todos deberíamos saber lo básico sobre él. Pero no, no voy a entrar en detalles sobre el mismo, simplemente os diré que funciona muy parecido a las excepciones de un lenguaje de programación, con la salvedad de que es el sistema operativo el que las lanza y las gestiona si no lo has hecho tu.

En Windows Vista y siguientes existe una aplicación que se llama WerFault.exe y que seguro que habéis visto en ejecución. De hecho, cada vez que os peta un programa, es esa aplicación la que toma el control y saca la barra de progreso moviéndose y te permite unas u otras acciones una vez que ha hecho sus deberes.

Es decir, cuando una aplicación genera una excepción, o el sistema operativo dispara una causada por la aplicación (por ejemplo, una división entre cero o una escritura fuera de rango), se trastea con la pila de tu programa y se busca un controlador de excepción adecuado. Ojo, no estamos hablando de bloques try/catch, sino de bloques __try/__except… aunque a veces el control termina también en un catch, sobre todo si usamos C++ y hemos habilitado la compatibilidad de control de excepciones del sistema operativo (y esto no viene explicado así en el libro, que asume la compatibildad). En .NET no sé qué ocurrirá, pero no creo que haya mucha diferencia.

Si nuestro programa tiene un manejador para esa excepción, se ejecutará, y dependiendo de lo que devuelva, se reintentará la instrucción (de código máquina, así que ojo) que generó la excepción, se continuará como si nada hubiera pasado (y en este caso podríamos liarla todavía más gorda), o se seguirá buscando otro nuevo manejador. Es decir, independientemente de lo que quiera que hagamos en nuestro controlador, podemos decirle al sistema operativo que reintente, que ignore o que busque otro controlador más.

Una vez que se han recorrido todos los controladores adecuados, además de dos más extra que podemos instalar mediante AddVectoredExceptionHandler() y SetUnhandledExceptionFilter(), y siempre que se haya llegado al final de toda la cadena de excepciones (que es algo más larga y compleja de lo que aquí he expuesto) sin haber sido detenida, Windows lanzará el citado programa, y le pasará un manejador al proceso que está fallando, aparte de detener por completo la ejecución de todos sus hilos (en XP sólo se detenía el que fallaba) mediante unas APIs ALPC no documentadas.

Este programa, en base a algunas claves del registro, hará una cosa u otra. Como ha recibido los manejadores en modo heredado, tendrá control sobre el programa que está fallando. Aparte de añadir el evento correspondiente, podrá enviar el informe a Microsoft, o añadirlo a la lista de pendientes. Incluso existe la posibilidad de que ni siquiera aparezca, haciendo todas sus tareas de forma oculta, o incluso que no haga ninguna. Podemos incluso filtrar el comportamiento según qué aplicación haya fallado.

Después de que ha hecho sus tareas internas, si existe algún depurador registrado y si la configuración (o lo que hayamos elegido) lo permite, abrirá el programa vsjitdebugger.exe que mostrará los depuradores registrados. De nuevo este programa recibe los manejadores de forma heredada, y mientras, nuestra aplicación está completamente muerta, esperando. Una vez que hemos elegido un depurador, éste será ejecutado pasando, otra vez, la información necesaria para que sea capaz de buscar y localizar la aplicación que ha fallado.

Es entonces cuando ésta se abrirá dentro del depurador, y si están disponibles los símbolos y demás, podremos ver qué ha fallado y por qué.

Si os fijáis, el tema no es sencillo ni de lejos. En primer lugar, el programa fallante no puede hacer nada porque podría estar en un estado indeterminado, con la memoria o la pila completamente corruptas, así que es el sistema operativo el que debe tomar el control y lanzar una nueva aplicación que se encargue de todo el paripé, que a su vez lanzará un depurador que no es tal, sino un concentrador de depuradores, que al final sí, lanzará el depurador elegido, que tomará bajo su ala a nuestro programa petado.

¿Os dais cuenta lo complejo que es algo que asumimos de forma inconsciente como trivial? Pues no todo termina ahí, sino que es ahora el depurador el que debe meter las garras dentro de la aplicación defectuosa, insertándose en su espacio de direcciones de alguna manera.

Para finalizar, comentaros que esto no es más que un resumen de los capítulos 23, 24 y 25 de Windows via C/C++, quinta edición.

Un comentario sobre “De excepciones, del depurador, de Windows y de Visual Studio”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *