Eligiendo qué hacer cuando pete nuestra aplicación

En la entrada anterior expliqué qué ocurre cuando una aplicación peta y se genera una excepción no controlada (o controlada pero relanzada), y en esta voy a explicar cómo podemos, desde nuestro propio programa, configurar el modo en que eso ocurre. Pero antes una introducción.

En algunos lados he dicho que las excepciones son caras. Con eso no me refiero a que cuesten dinero, sino a que es un tipo de característica que resulta muy complicada de procesar y que a veces requiere de bastante tiempo de proceso. Cuando se dispara una interrupción en una aplicación, sobre todo si ocurre en el lado del kernel, éste tiene que entrar y salir varias veces entre su modo y el de usuario, operaciones que no son baratas (de nuevo no en dinero, aunque lo mismo en su servidor en producción sí) ya que significan saltar entre los anillos 0 y 3 del procesador. Aparte de eso, se debe recorrer la pila (a ambos lados de los anillos) para ir buscando el controlador adecuado, ejecutarlo y en la mayoría de casos continuar con la búsqueda.

Cuando ponemos un bloque try/catch, o __try/__except ó __try/__finally, el compilador deja “marcas” en la pila, “marcas” que serán leídas por el controlador de excepciones. Dependiendo de cómo lo hayamos hecho, y el número de bloques y de controladores, el sistema tendrá que salvar y restaurar el estado de la pila y de la aplicación para ir ejecutando los diferentes bloques de captura, y a veces incluso se necesita de cierta “inteligencia” para que la ejecución sea la correcta. El tema queda bastante claro una vez que se han leído los capítulos de los dos libros que mencioné, con una excepción: que esos recorridos requieren tiempo, sobre todo si hay saltos entre anillo 0 y 3, por lo que en general el uso de excepciones está para eso: para ser usadas cuando realmente se produzca una, y no alegremente como una mera característica de nuestro lenguaje.

Dicho esto, considero que el ejemplo que Jeffrey Ritcher pone al final del capítulo 25 no es muy correcto, no porque esté mal, sino porque realizar gestión de memoria mediante excepciones me parece que es matar moscas a cañonazos… aparte del desperdicio de la misma. Ritcher crea una hoja de cálculo de un tamaño predefinido pero sin asignar ningún tipo de memoria. Luego, ante cada requerimiento de que una celda sea llenada con algo, intenta escribir en la posición. Si ya hay memoria asignada todo irá bien, pero si no, se disparará una excepción que asignará un bloque a dicha celda. La barbaridad está en que cada asignación, aunque sea de un solo byte, asignará 4KB (que es el tamaño de página de RAM), y en que el gasto de la excepción y su control es enormemente más caro que simplemente comprobar si la memoria está asignada o no. A no ser que se me escape algo, como ejemplo sobre el funcionamiento de las excepciones, vale, pero como ejemplo de algoritmo genérico, no.

De todos modos, el sistema de excepciones es my potente y versátil, y con la combinación del registro, podemos configurar a base de bien el comportamiento de las no controladas. Windows Internals lista 25 claves para cambiar el comportamiento, alguna de ellas tan radicales como hacer que ni siquiera se ejecute WerFault.exe y nuestra aplicación desaparezca de forma silenciosa.

Pero como diría el personaje de los dibujos animados, no se vayan todavía, que hay más. Aparte de poder instalar y controlar ciertas funciones de control de excepciones, podemos configurar el modo con que nuestra aplicación se comunica con el gestor de las mismas. Es decir, mediante ciertas llamadas a funciones de Win32, podemos decirle al sistema WER cómo debe comportarse, qué ficheros adjuntar, etc.

La mayoría de las funciones que voy a citar aquí de pasada, se encuentran dentro de kernel32.dll y para usarlas debemos incluir werapi.h.

  • WerSetFlags()/WerGetFlags(). Cambiamos o leemos si queremos que se incluya el heap (montículo) en el informe, si se suspenderán todos los hilos o sólo el que falló, si se añadirá el informe a la lista de errores, y si se enviará o no a Microsoft.
  • WerAddExcludedApplication()/WerRemoveExcludedApplication(). Añadir o eliminar una aplicación a la lista de aplicaciones excluidas de la generación de informes.
  • WerRegisterFile()/WerUnregisterFile(). Añadir o quitar el fichero indicado al informe.
  • WerRegisterMemoryBlock()/WerUnregisterMemoryBlock(). Añadir/Qutar un volcado de un área de la memoria de nuestro programa.

También podemos crear un informe desde nuestro programa cuando queramos. Los pasos a seguir son:

  1. Llamar a WerReportCreate(), que inicia el informe.
  2. Llamar tantas veces a WerReportSetParameter() como parámetros queramos cambiar.
  3. Llamar tantas veces a WerReportAddDump() como bloques de memoria queramos incluir.
  4. Llamar tantas veces a WerReportAddFile() como ficheros del tipo que sean queramos incluir.
  5. Llamar a WerReportSetUIString() para añadir opciones a la pantalla de WerFault.exe.
  6. Llamar a WerReportSubmit(), que dependiendo de la configuración para el programa y/o genéricas que se tengan, enviará o no, preguntará o no, etc. qué hacer con el informe.
  7. Llamar a WerReportCloseHandle() para terminar con el tema.

Como podéis ver, la cosa es muy potente, y puede ser enormemente útil en determinadas situaciones. Imaginemos que nuestra aplicación esté petando en casa del cliente y no haya modo de ver qué puñetas pasa. La generación de informes a demanda podría ser la solución. Lo que no he visto es cómo recibir e interpretar esos informes.

También podemos usar estas funciones cuando sea nuestra aplicación la que esté controlando la excepción, pero debemos ser cuidadosos ya que ésta podría estar tan estropeada que el uso de todo esto volviera a genera nuevas excepciones que terminaran en el mismo código, que a su vez generara otras que…

Se supone que Windows es lo suficientemente inteligente como para detener la cadena, pero como yo ya he visto más de una vez petar de forma continua a una aplicación en Windows Vista hasta que la he matado desde el administrador de tareas, debemos ser cuidadosos con su uso.

3 comentarios sobre “Eligiendo qué hacer cuando pete nuestra aplicación”

  1. Espero que sigas con éstos resúmenes. Aunque ya sabes que no es precisamente mi especialidad, lo estoy leyendo como si fuera una novela, coño! que hasta me esta picando la curiosidad! 🙂

  2. Rafa, no conozco el ejemplo que mencionas de Richter, pero esa descripción del crecimiento progresivo en términos de 4 KB basado en excepciones me recuerda al modo en que se va extendiendo la pila (stack) de un thread en modo usuario.

  3. Ritcher se basa en eso mismo para crear su aplicación. En la pila, cuando se intenta escribir una posición más allá, se dispara la excepción correspondiente y se le dan 4K más.

    En la pila no es problema, porque no se desperdicia memoria, hasta que no se llene no se le dan 4K más, pero con memoria normal en un programa estás desperdiciándola.

    Supongo que esto lo sabrás: cuando asignas con new o malloc, no estás llamando a Win32, estás haciendo que el runtime recorra su heap y busque un hueco libre, y si no lo hay entonces pedirá al sistema 4K (o más) y te dará tu parte correspondiente, dejando el resto para la siguiente asignación, porque si se pidiera directamente a Win32, este sólo puede darte un mínimo de 4KB y no veas el desperdicio…

    Todo esto da para una nueva entrada… Si te animas te enlazo.

Responder a anonymous Cancelar respuesta

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