c++ - reales - El programa solo falla como versión de lanzamiento: ¿cómo depurar?
manual android studio avanzado (26)
Ntdll.dll con depurador adjunto
Una pequeña diferencia entre lanzar un programa desde el IDE o WinDbg en lugar de ejecutarlo desde la línea de comandos / escritorio es que cuando se inicia con un depurador conectado (es decir, IDE o WinDbg) ntdll.dll usa una implementación de montón diferente que realiza una pequeña validación en la asignación / liberación de memoria.
Puede leer información relevante en un punto de corte de usuario inesperado en ntdll.dll . Una herramienta que podría ayudarlo a identificar el problema es PageHeap.exe .
Análisis de bloqueos
No escribiste lo que es el "crash" que estás experimentando. Una vez que el programa falla y le ofrece enviar la información del error a Microsoft, debe poder hacer clic en la información técnica y verificar al menos el código de excepción, y con un poco de esfuerzo incluso puede realizar un análisis post-mortem (consulte Heisenbug : Programa WinApi se bloquea en algunas computadoras) para obtener instrucciones)
Aquí tengo el tipo de problema de "Schroedinger''s Cat": mi programa (en realidad, el banco de pruebas para mi programa, pero un programa) está fallando, pero solo cuando está incorporado en modo de lanzamiento, y solo cuando se lanza desde la línea de comando . A través de la depuración de cavernícolas (es decir, desagradables mensajes printf () por todos lados), he determinado el método de prueba donde el código se cuelga, aunque lamentablemente el bloqueo real parece ocurrir en algún destructor, ya que los últimos mensajes de rastreo que veo están en otros destructores que se ejecutan limpiamente.
Cuando intento ejecutar este programa dentro de Visual Studio, no se cuelga. Lo mismo ocurre cuando se inicia desde WinDbg.exe. El bloqueo solo se produce al iniciar desde la línea de comando. Esto está sucediendo en Windows Vista, por cierto, y desafortunadamente no tengo acceso a una máquina XP ahora mismo para probar.
Sería realmente agradable si pudiera hacer que Windows imprima un seguimiento de la pila, o algo distinto a simplemente terminar el programa como si hubiera salido limpiamente. ¿Alguien tiene algún consejo sobre cómo podría obtener información más significativa aquí y espero arreglar este error?
Editar: El problema fue causado por una matriz fuera de límites, que describo más en esta publicación . ¡Gracias a todos por su ayuda para encontrar este problema!
A veces esto sucede porque has envuelto una operación importante dentro de la macro "afirmar". Como sabrá, "afirmar" evalúa expresiones solo en modo de depuración.
Algo similar me pasó una vez con GCC. Resultó ser una optimización demasiado agresiva que solo se habilitó al crear la versión final y no durante el proceso de desarrollo.
Bueno, a decir verdad, fue mi culpa, no la de gcc, ya que no me di cuenta de que mi código se basaba en el hecho de que esa optimización particular no se hubiera realizado.
Me llevó mucho tiempo rastrearlo y solo llegué porque lo pregunté en un grupo de noticias y alguien me hizo pensar en ello. Por lo tanto, permítame devolverle el favor por si acaso esto le sucede a usted también.
Aunque haya creado su exe como versión uno, aún puede generar archivos PDB (base de datos del programa) que le permitirán apilar el seguimiento y realizar una cantidad limitada de inspección variable. En su configuración de compilación hay una opción para crear los archivos PDB. Enciéndela y vuelve a vincular. Luego intente ejecutar desde el IDE primero para ver si obtiene el bloqueo. Si es así, entonces genial, está todo listo para mirar las cosas. De lo contrario, cuando se ejecuta desde la línea de comando, puede hacer una de estas dos cosas:
- Ejecute EXE y, antes del bloqueo, haga un Adjuntar al proceso (menú Herramientas en Visual Studio).
- Después del bloqueo, seleccione la opción para iniciar el depurador.
Cuando se le pida que apunte a los archivos PDB, navegue para encontrarlos. Si los PDB se pusieron en la misma carpeta de salida que su EXE o DLL, probablemente se recogerán automáticamente.
Los PDB proporcionan un enlace a la fuente con suficiente información de símbolos para que sea posible ver los rastros de pila, variables, etc. Puede inspeccionar los valores como normales, pero tenga en cuenta que puede obtener lecturas falsas ya que el pase de optimización puede significar solo cosas aparecer en los registros, o las cosas suceden en un orden diferente al esperado.
NB: Estoy asumiendo un entorno de Windows / Visual Studio aquí.
Bloqueos como este casi siempre son causados porque un IDE usualmente establecerá el contenido de la variable no inicializada en ceros, nulo o algún otro valor ''sensible'', mientras que cuando se ejecuta de forma nativa obtendrá cualquier basura aleatoria que el sistema capte.
Por lo tanto, su error es casi seguro que está utilizando algo así como que está utilizando un puntero antes de que se haya inicializado correctamente y que se salga con la suya en el IDE porque no apunta a ningún lugar peligroso, o el valor lo maneja su comprobación de errores, pero en el modo de lanzamiento hace algo desagradable.
Con respecto a sus problemas para obtener información de diagnóstico, ¿ha intentado utilizar adplus.vbs como alternativa a WinDbg.exe? Para adjuntar a un proceso en ejecución, use
adplus.vbs -crash -p <process_id>
O para iniciar la aplicación en caso de que el bloqueo ocurra rápidamente:
adplus.vbs -crash -sc your_app.exe
Se puede encontrar información completa sobre adplus.vbs en: http://support.microsoft.com/kb/286350
Cosas a tener en cuenta:
Sobrepasos de matriz: el depurador de Visual Studio inserta relleno que puede detener los bloqueos.
Condiciones de carrera: ¿tiene varios hilos implicados? Si es así, una condición de carrera solo se muestra cuando una aplicación se ejecuta directamente.
Vinculación: es la compilación de lanzamiento que se realiza en las bibliotecas correctas.
Cosas para intentar:
Minidump: realmente fácil de usar (simplemente búscalo en msdn) te dará un volcado completo para cada hilo. Simplemente carga la salida en Visual Studio y es como si estuviera depurando en el momento del bloqueo.
Cuando he encontrado problemas como este antes, generalmente se debe a la inicialización de la variable. En el modo de depuración, las variables y los punteros se inicializan a cero automáticamente, pero en el modo de lanzamiento no lo hacen. Por lo tanto, si tienes un código como este
int* p;
....
if (p == 0) { // do stuff }
En el modo de depuración, el código en if no se ejecuta, pero en el modo de publicación p contiene un valor indefinido, que es poco probable que sea 0, por lo que el código se ejecuta a menudo y provoca un bloqueo.
Verificaría su código para las variables no inicializadas. Esto también se puede aplicar a los contenidos de las matrices.
Después de muchas horas de depuración, finalmente encontré que la causa del problema, que en realidad fue causada por un desbordamiento del búfer, causaba una diferencia de un solo byte:
char *end = static_cast<char*>(attr->data) + attr->dataSize;
Este es un error de fencepost (error de uno a uno) y fue corregido por:
char *end = static_cast<char*>(attr->data) + attr->dataSize - 1;
Lo extraño fue que puse varias llamadas a _CrtCheckMemory () alrededor de varias partes de mi código, y siempre devolvían 1. Pude encontrar el origen del problema colocando "return false;" llamadas en el caso de prueba, y luego finalmente determinando a través de prueba y error dónde estaba el error.
Gracias a todos por sus comentarios. ¡Hoy aprendí mucho sobre windbg.exe! :)
En el 100% de los casos que he visto u oído hablar, donde un programa C o C ++ funciona bien en el depurador pero falla cuando se ejecuta fuera, la causa ha sido escribir más allá del final de una matriz local de funciones. (El depurador pone más en la pila, por lo que es menos probable que sobrescriba algo importante).
Encontré este artículo útil para su escenario. ISTR las opciones del compilador estaban un poco desactualizadas. Mire a su alrededor las opciones de proyecto de Visual Studio para ver cómo generar archivos pdb para su compilación de lanzamiento, etc.
Es sospechoso que ocurra fuera del depurador y no dentro; ejecutar en el depurador normalmente no cambia el comportamiento de la aplicación. Verificaría las diferencias de entorno entre la consola y el IDE. Además, obviamente, compile la versión sin optimizaciones y con información de depuración, y vea si eso afecta el comportamiento. Finalmente, consulte las herramientas de depuración post-mortem que otras personas han sugerido aquí, generalmente puede obtener alguna pista de ellas.
Estoy de acuerdo con Rolf. Debido a que la reproducibilidad es tan importante, no debe tener un modo sin errores. Todas sus compilaciones deben ser depurables. Tener dos objetivos para depurar más que duplica su carga de depuración. Solo envíe la versión del "modo de depuración", a menos que sea inutilizable. En ese caso, hágalo utilizable.
Haga que su programa genere un mini volcado cuando se produce la excepción, luego ábralo en un depurador (por ejemplo, en WinDbg). Las funciones clave a tener en cuenta: MiniDumpWriteDump, SetUnhandledExceptionFilter
Hasta el momento, ninguna respuesta ha intentado ofrecer una descripción general sobre las técnicas disponibles para depurar aplicaciones de lanzamiento:
Las versiones Release y Debug se comportan de manera diferente por muchas razones. Aquí hay una excelente descripción general. Cada una de estas diferencias puede causar un error en la compilación Release que no existe en la compilación Debug.
La presencia de un depurador también puede cambiar el comportamiento de un programa , tanto para la versión como para la depuración. Vea esta respuesta. En resumen, al menos el depurador de Visual Studio utiliza el Depósito de depuración automáticamente cuando se conecta a un programa. Puede desactivar el almacenamiento de depuración utilizando la variable de entorno _NO_DEBUG_HEAP. Puede especificar esto en las propiedades de su computadora o en la Configuración del proyecto en Visual Studio. Eso podría hacer que el bloqueo sea reproducible con el depurador conectado.
Si la solución anterior no funciona, debe detectar la excepción no controlada y adjuntar un depurador post mortem a la instancia donde se produce el bloqueo. Puede usar, por ejemplo, WinDbg para esto, detalles sobre los depuradores postmortem disponibles y su instalación en MSDN.
Puede mejorar su código de manejo de excepciones y si se trata de una aplicación de producción, debe:
a. Instale un controlador de terminación personalizado usando
std::set_terminate
Si desea depurar este problema localmente, puede ejecutar un bucle infinito dentro del manejador de terminación y enviar texto a la consola para notificarle que se ha llamado a
std::terminate
. A continuación, conecte el depurador y verifique la pila de llamadas. O imprime el seguimiento de la pila como se describe en esta respuesta.En una aplicación de producción, es posible que desee enviar un informe de errores a su país de origen, idealmente junto con un pequeño volcado de memoria que le permita analizar el problema como se describe aquí.
segundo. Utilice el mecanismo estructurado de manejo de excepciones de Microsoft que le permite detectar excepciones de hardware y software. Ver MSDN . Puede guardar partes de su código usando SEH y usar el mismo enfoque que en a) para solucionar el problema. SEH proporciona más información acerca de la excepción que ocurrió que podría usar al enviar un informe de error desde una aplicación de producción.
He visto muchas respuestas correctas. Sin embargo, no hay ninguno que me haya ayudado. En mi caso, hubo un uso incorrecto de las instrucciones SSE con la memoria desalineada . Eche un vistazo a su biblioteca de matemáticas (si usa una) e intente deshabilitar la compatibilidad con SIMD, vuelva a compilar y reproduzca el bloqueo.
Ejemplo:
Un proyecto incluye mathfu y usa las clases con el vector STL: std :: vector <mathfu :: vec2> . Tal uso probablemente causará un bloqueo en el momento de la construcción del elemento mathfu :: vec2 ya que el asignador predeterminado STL no garantiza la alineación requerida de 16 bytes. En este caso para probar la idea, se puede definir #define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT 1
antes de cada inclusión del mathfu , recompilar en la configuración de Release y verificar nuevamente.
Las configuraciones Debug y RelWithDebInfo funcionaron bien para mi proyecto, pero no para la Versión uno. El motivo de este comportamiento probablemente se deba a que el depurador procesa las solicitudes de asignación / desasignación y realiza cierta contabilidad de la memoria para verificar y verificar los accesos a la memoria.
Experimenté la situación en los entornos de Visual Studio 2015 y 2017.
Intente usar _CrtCheckMemory () para ver en qué estado se encuentra la memoria asignada. Si todo va bien, _CrtCheckMemory devuelve TRUE , sino FALSE .
Las compilaciones de versiones de depuración pueden ser un problema debido a las optimizaciones que cambian el orden en que las líneas de su código parecen ejecutarse. ¡Realmente puede ser confuso!
Una técnica para al menos reducir el problema es utilizar MessageBox () para mostrar declaraciones rápidas que indiquen a qué parte del programa ha llegado su código ("Starting Foo ()", "Starting Foo2 ()"); comience a ponerlos en la parte superior de las funciones en el área de su código que sospecha (¿qué estaba haciendo en el momento en que se estrelló?). Cuando pueda distinguir qué función cambiar, los cuadros de mensaje a bloques de código o incluso líneas individuales dentro de esa función hasta que se reduzca a unas pocas líneas. Luego puede comenzar a imprimir el valor de las variables para ver en qué estado se encuentran en el punto de falla.
Para tener un volcado de emergencia que pueda analizar:
- Genera archivos pdb para tu código.
- Usted rebase para tener su exe y dlls cargados en la misma dirección.
- Habilitar depurador post mortem como Dr. Watson
- Verifique la dirección de fallas de colisión usando una herramienta como el buscador de choques .
También debería consultar las herramientas en Debugging tools for windows . Puede controlar la aplicación y ver todas las excepciones de oportunidad que existían antes de su excepción de segunda oportunidad.
Espero eso ayude...
Puede configurar WinDbg como su depurador post mortem. Esto iniciará el depurador y lo adjuntará al proceso cuando se produzca el bloqueo. Para instalar WinDbg para la depuración post mortem, use la opción / I (tenga en cuenta que está en mayúscula ):
windbg /I
Más detalles here .
En cuanto a la causa, es muy probable que sea una variable unitaria como sugieren las otras respuestas.
Puede ejecutar su software con Global Flags habilitado (consulte Herramientas de depuración para Windows). Muy a menudo ayudará a resolver el problema.
Según mi experiencia, la mayoría son problemas de corrupción de memoria.
Por ejemplo :
char a[8];
memset(&a[0], 0, 16);
: /*use array a doing some thing */
es muy posible ser normal en el modo de depuración cuando uno ejecuta el código.
Pero en el lanzamiento, eso podría / podría ser un colapso.
Para mí, revolver donde la memoria está fuera de límite es demasiado difícil.
Use algunas herramientas como Visual Leak Detector (windows) o valgrind (linux) son más sabias elección.
Tuve este error y vs se bloqueó incluso cuando intentaba! Limpiar! mi proyecto. Así que eliminé los archivos obj de forma manual desde el directorio de Release, y después de eso me fue muy bien.
Una buena forma de depurar un error como este es habilitar optimizaciones para su compilación de depuración.
Vista SP1 en realidad tiene un generador de volcado de emergencia realmente bueno integrado en el sistema. Lamentablemente, no está activado por defecto.
Vea este artículo: http://msdn.microsoft.com/en-us/library/bb787181(VS.85).aspx
El beneficio de este enfoque es que no se necesita instalar ningún software adicional en el sistema afectado. Agarre y rasgúelo, bebé!
Una vez tuve un problema cuando la aplicación se comportó de manera similar a la tuya. Resultó ser un desagradable desbordamiento de búfer en sprintf. Naturalmente, funcionó cuando se ejecutó con un depurador adjunto. Lo que hice fue instalar un filtro de excepción no controlado ( SetUnhandledExceptionFilter ) en el que simplemente SetUnhandledExceptionFilter infinitamente (usando WaitForSingleObject en un manipulador falso con un valor de tiempo de espera de INFINITE).
Entonces podrías hacer algo como:
long __stdcall MyFilter(EXCEPTION_POINTERS *) { HANDLE hEvt=::CreateEventW(0,1,0,0); if(hEvt) { if(WAIT_FAILED==::WaitForSingleObject(hEvt, INFINITE)) { //log failure } } } // somewhere in your wmain/WinMain: SetUnhandledExceptionFilter(MyFilter);
Luego adjunté el depurador después de que el error se había manifestado (el programa gui dejó de responder).
Entonces puedes tomar un vertedero y trabajar con él más tarde:
.dump / ma path_to_dump_file
O depurarlo de inmediato. La manera más simple es rastrear dónde el contexto del procesador ha sido guardado por la maquinaria de manejo de excepciones de tiempo de ejecución:
sd esp Rango 1003f
El comando buscará el espacio de dirección de la pila para los registros CONTEXT proporcionados la longitud de la búsqueda. Usualmente uso algo como ''l? 10000'' . Tenga en cuenta que no utilice números que sean demasiado grandes ya que el registro que busca generalmente está cerca del marco de filtro de excepciones sin manos. 1003f es la combinación de indicadores (creo que corresponde a CONTEXT_FULL) utilizado para capturar el estado del procesador. Su búsqueda se vería similar a esto:
0: 000> sd esp l1000 1003f
0012c160 0001003f 00000000 00000000 00000000? ...............
Una vez que obtenga los resultados, use la dirección en el comando cxr:
.cxr 0012c160
Esto lo llevará a este nuevo CONTEXTO, exactamente en el momento del bloqueo (obtendrá exactamente el seguimiento de la pila en el momento en que se bloqueó su aplicación). Además, use:
.exr -1
para averiguar exactamente qué excepción había ocurrido.
Espero eso ayude.