2009-12-26 14 views
6

Actualmente estoy trabajando para agregar excepciones y manejo de excepciones a mi aplicación OSS. Las excepciones han sido la idea general desde el principio, pero quería encontrar un buen marco de excepción y honestamente, comprender las convenciones de manejo de excepciones C++ y las expresiones idiomáticas un poco mejor antes de comenzar a usarlas. Tengo mucha experiencia con C# /. Net, Python y otros lenguajes que usan excepciones. No soy ajeno a la idea (pero lejos de ser un maestro).Mostrar información de depuración de excepciones a los usuarios

En C# y Python, cuando se produce una excepción no controlada, el usuario obtiene un buen seguimiento de la pila y, en general, una gran cantidad de muy útil información de depuración inestimable. Si está trabajando en una aplicación OSS, que los usuarios peguen esa información en los informes de problemas es ... bueno, digamos que me resulta difícil vivir sin eso. Para este proyecto de C++, obtengo "La aplicación se colgó", o de usuarios más informados, "Hice X, Y y Z, y luego se bloqueó". ¡Pero también quiero esa información de depuración!

Ya he (y con gran dificultad) he hecho las paces con el hecho de que nunca veré una forma multiplataforma y de compilación cruzada de obtener un seguimiento de la pila de excepción C++, pero sé que puedo obtener el nombre de la función y otra información relevante.

Y ahora quiero eso para mis excepciones no controladas. Estoy usando boost::exception, y tienen esta muy buena diagnostic_information thingamajig que puede imprimir el nombre de la función (sin marcar), el archivo, la línea y lo más importante, otra información específica de la excepción que el programador agregó a esa excepción.

Naturalmente, manejaré excepciones dentro del código cada vez que pueda, pero no soy tan ingenuo como para pensar que no dejaré pasar a un par (involuntariamente, por supuesto).

Lo que quiero hacer es ajustar mi punto de entrada principal dentro de un bloque try con un catch que crea un diálogo especial que informa al usuario que ha ocurrido un error en la aplicación, con información más detallada presentada cuando el usuario hace clic "Más" o "Información de depuración" o lo que sea. Esto contendría la cadena de diagnostic_information. Podría entonces instruir a los usuarios a pegar esta información en informes de problemas.

Pero una primera impresión molesta me dice que si se envuelve todo en un bloque try es una muy mala idea. ¿Es lo que voy a hacer estúpido? Si es (e incluso si no lo es), ¿cuál es la mejor manera de lograr lo que quiero?

Respuesta

3

Envolver todo el código en un bloque es un try/catch-ok. No ralentizará la ejecución de nada dentro de él, por ejemplo. De hecho, todos mis programas tienen (código similar a) este marco:

int execute(int pArgc, char *pArgv[]) 
{ 
    // do stuff 
} 

int main(int pArgc, char *pArgv[]) 
{ 
    // maybe setup some debug stuff, 
    // like splitting cerr to log.txt 

    try 
    { 
     return execute(pArgc, pArgv); 
    } 
    catch (const std::exception& e) 
    { 
     std::cerr << "Unhandled exception:\n" << e.what() << std::endl; 
     // or other methods of displaying an error 

     return EXIT_FAILURE; 
    } 
    catch (...) 
    { 
     std::cerr << "Unknown exception!" << std::endl; 

     return EXIT_FAILURE; 
    } 
} 
+0

+1, pero ¿es correcto llamar a algo no derivado de 'std :: exception' una excepción? ;-) (y no, no tengo un nombre mejor para int arrojado o algo igualmente afilado). –

+0

¿Te refieres al 'catch (...)'? Solo lo tengo completo, si algo entra realmente en él, me sorprendería y buscaría quién está lanzando cosas al azar. – GManNickG

+0

GMan, sí, veo tu intención, es solo que me pregunto si alguien dice "excepción desconocida" o "algo desconocido arrojado en nuestro camino" en este caso ;-) una cuestión de terminología. –

1

No, no es estúpido. Es una muy buena idea, y no cuesta prácticamente nada en tiempo de ejecución hasta que llegue a una excepción no controlada, por supuesto.

Tenga en cuenta que ya hay un controlador de excepciones envolver el hilo, proporcionada por el sistema operativo (y otro por el tiempo de ejecución de C-creo). Es posible que deba pasar ciertas excepciones a estos controladores para obtener un comportamiento correcto. En algunas arquitecturas, el acceso a datos mal alineados es manejado por un manejador de excepciones. por lo que es posible que desee un caso especial EXCEPTION_DATATYPE_MISALIGNMENT y dejarlo pasar al manejador de excepciones de nivel superior.

Incluyo los registros, la versión de la aplicación y el número de compilación, el tipo de excepción y un volcado de pila en hex anotado con nombres de módulo y desplazamientos para valores hexadecimales que podrían ser direcciones al código. Asegúrese de incluir el número de versión y el número de compilación/fecha de su exe.

También puede usar VirtualQuery para convertir valores de pila en "ModuleName + Offset" con bastante facilidad. Y eso, combinado con un archivo .MAP a menudo le dirá exactamente dónde se estrelló.

Descubrí que podía entrenar beta testers para enviar mi texto con bastante facilidad, pero en los primeros días lo que obtuve fue una imagen del diálogo de error en lugar del texto. Creo que es porque muchos usuarios no saben que puedes hacer clic derecho en cualquier control de edición para obtener un menú con "Seleccionar todo" y "Copiar". Si fuera a hacerlo nuevamente, agregaría un botón que copió ese texto en el portapapeles para que pueda pegarse fácilmente en un correo electrónico.

Aún mejor si quiere tomarse la molestia de tener un botón de "enviar informe de errores", pero solo darles a los usuarios una manera de obtener el texto en sus propios correos electrónicos hace que llegue la mayor parte del camino, y no lo hace plantee banderas rojas sobre "¿qué información estoy compartiendo con ellos?"

+0

¿Qué es esa VirtualQuery que mencionas? Nunca lo escuché, pero podría ser interesante para nuestra plataforma SPARC. –

+0

es un Win32 Api. No sé el equivalente para sistemas Unix, lo siento. –

31

Poner un bloque try/catch en main() está bien, no causa ningún problema. El programa está muerto en una excepción no controlada de todos modos. Sin embargo, no va a ser nada útil en su búsqueda para obtener la pista de pila más importante. Esa información es gonzo cuando el bloque catch atrapa la excepción.

La captura de una excepción en C++ tampoco será de mucha ayuda. Las probabilidades de que el programa muera en una excepción derivada de std :: exception son bastante escasas. Aunque podría suceder. Mucho más probable en una aplicación C/C++ es la muerte debido a excepciones de hardware, AccessViolation es numero uno. Atraparlos requiere las palabras clave __try y __except en su método main(). De nuevo, hay muy poco contexto disponible, básicamente solo tienes un código de excepción. Un AV también le dice qué ubicación exacta de la memoria causó la excepción.

Esto no es solo un problema multiplataforma por cierto, no se puede obtener un buen seguimiento de pila en ninguna plataforma. No hay una manera confiable de recorrer la pila, hay demasiadas optimizaciones (como la omisión de framepointer) que hacen que este sea un viaje peligroso. Es la forma C/C++: hazlo lo más rápido posible, no dejes ninguna pista de lo que sucedió cuando explota.

Lo que debe hacer es solucionar este tipo de problemas de la manera C/C++. Necesitas crear un minivolcado. Es más o menos análogo al "volcado del núcleo" de la antigüedad, una instantánea de la imagen del proceso en el momento en que ocurre la excepción. En aquel entonces, en realidad obtuviste un vertedero completo del núcleo. Ha habido progreso, hoy en día es "mini", algo necesario porque un volcado de núcleo completo tomaría cerca de 2 gigabytes. Realmente funciona bastante bien para diagnosticar el estado del programa.

En Windows, que comienza llamando a SetUnhandledExceptionFilter(), proporciona un puntero a la función de devolución de llamada a una función que se ejecutará cuando su programa fallezca en una excepción no controlada. Cualquier excepción, C++ y SEH. Su próximo recurso es dbghelp.dll, disponible en la descarga de herramientas de depuración para Windows. Tiene un punto de entrada llamado MiniDumpWriteDump(), crea un minivolcado.

Una vez que obtiene el archivo creado por MiniDumpWriteDump(), está bastante dorado. Puede cargar el archivo .dmp en Visual Studio, casi como si fuera un proyecto. Presione F5 y VS muele durante un rato tratando de cargar archivos .pdb para las DLL cargadas en el proceso. Deberá configurar el servidor de símbolos, que es muy importante para obtener buenos trazados de pila. Si todo funciona, obtendrá una "ruptura de depuración" en el lugar exacto donde se produce la excepción" Con un seguimiento de pila

cosas que hay que hacer para que esto funcione sin problemas:..

  • Use un servidor de compilación para crear los archivos binarios. Necesita insertar los símbolos de depuración (.archivos pdb) a un servidor de símbolos para que estén disponibles cuando depure el minivolcado.
  • Configure el depurador para que pueda encontrar los símbolos de depuración para todos los módulos. Puede obtener los símbolos de depuración para Windows de Microsoft, los símbolos para su código deben provenir del servidor de símbolos mencionado anteriormente.
  • Escribe el código para atrapar la excepción no controlada y crea el minivolcado. Mencioné SetUnhandledExceptionFilter() pero el código que crea el minivolcado no debería estar en el programa que se colgó. Las probabilidades de que pueda escribir el minivolcado con éxito son bastante escasas, el estado del programa no está determinado. Lo mejor es ejecutar un proceso de "protección" que vigile un Mutex con nombre. Su filtro de excepción puede establecer el mutex, el guardia puede crear el minivolcado.
  • Crea una forma para que el minivolcado sea transferido de la máquina del cliente a la tuya. Usamos el servicio S3 de Amazon para eso, terabytes a un precio razonable.
  • Conecta el manejador de minivolcado a tu base de datos de depuración. Usamos Jira, tiene un servicio web que nos permite verificar el cubo contra una base de datos de bloqueos anteriores con la misma "firma". Cuando es único o no tiene suficientes visitas, le pedimos al código del administrador de bloqueos que cargue el minivolcado en Amazon y cree la entrada de la base de datos de errores.

Bueno, eso es lo que hice para la empresa para la que trabajo. Funcionó muy bien, redujo la frecuencia del cubo de choque de miles a docenas. Mensaje personal a los creadores del componente de código abierto ffdshow: te odio con pasión. ¡Pero ya no estás bloqueando nuestra aplicación! Insectores

+0

Eso fue malo para el equipo de ffdshow, están haciendo un trabajo muy aceptable sin pago y tienen muchos usuarios agradecidos (incluido yo mismo). De lo contrario, acuerde, +1 –

+0

¿Cómo generó una "firma" para el bloqueo? –

0

De hecho, boost :: diagnostic_information ha sido diseñado específicamente para ser utilizado en un bloque de captura "global", para mostrar información sobre excepciones que no deberían haberlo alcanzado. Sin embargo, tenga en cuenta que la cadena devuelta por boost :: diagnostic_information NO es fácil de usar.

Cuestiones relacionadas