c++ - ¿Cómo depurar los errores de corrupción del montón?
windows debugging (14)
¿Qué tipo de cosas pueden causar estos errores?
Hacer cosas malas con la memoria, por ejemplo, escribir después del final de un búfer, o escribir en un búfer después de haber sido liberado de vuelta al montón.
¿Cómo los depuro?
Use un instrumento que agregue la comprobación automatizada de límites a su ejecutable: es decir, valgrind en Unix, o una herramienta como BoundsChecker (Wikipedia sugiere también Purify and Insure ++) en Windows.
Tenga en cuenta que esto ralentizará su aplicación, por lo que pueden no ser utilizables si la suya es una aplicación en tiempo real.
Otra posible herramienta / herramienta de depuración podría ser HeapAgent de MicroQuill.
Estoy depurando una aplicación C ++ multi-threaded (nativa) en Visual Studio 2008. En ocasiones aparentemente aleatorias, aparece el error "Windows ha desencadenado un punto de interrupción ..." con una nota que podría deberse a una corrupción en el montón. Estos errores no siempre bloquean la aplicación de inmediato, aunque es probable que falle poco después.
El gran problema con estos errores es que aparecen solo después de que la corrupción ha tenido lugar, lo que los hace muy difíciles de rastrear y depurar, especialmente en una aplicación multiproceso.
¿Qué tipo de cosas pueden causar estos errores?
¿Cómo los depuro?
Consejos, herramientas, métodos, iluminaciones ... son bienvenidos.
¿Qué tipo de funciones de asignación estás usando? Hace poco golpeé un error similar con las funciones de asignación de estilo Heap *.
Resultó que estaba creando erróneamente el montón con la opción HEAP_NO_SERIALIZE
. Esto esencialmente hace que las funciones de Heap se ejecuten sin seguridad de hilos. Es una mejora en el rendimiento si se usa correctamente, pero nunca se debe usar si está usando HeapAlloc en un programa de subprocesos múltiples [1]. Solo menciono esto porque su publicación menciona que tiene una aplicación de subprocesos múltiples. Si está utilizando HEAP_NO_SERIALIZE en cualquier lugar, elimínelo y es probable que solucione su problema.
[1] Hay ciertas situaciones en las que esto es legal, pero requiere que serialice las llamadas a Heap * y generalmente no es el caso para los programas de subprocesos múltiples.
Además de buscar herramientas, considere buscar un posible culpable. ¿Hay algún componente que esté utilizando, quizás no escrito por usted, que puede no haber sido diseñado y probado para ejecutarse en un entorno multiproceso? O simplemente uno que no conoce se ha ejecutado en ese entorno.
La última vez que me pasó, era un paquete nativo que se había utilizado con éxito en trabajos por lotes durante años. Pero era la primera vez en esta empresa que se usaba desde un servicio web .NET (que es multiproceso). Eso fue todo - ellos mintieron sobre que el código era seguro para hilos.
Es posible que también desee comprobar si está enlazando con la biblioteca dinámica o estática de C en tiempo de ejecución. Si sus archivos DLL se vinculan con la biblioteca de tiempo de ejecución C estática, los archivos DLL tienen montones separados.
Por lo tanto, si tuviera que crear un objeto en una DLL e intentar liberarlo en otra DLL, obtendría el mismo mensaje que está viendo arriba. Se hace referencia a este problema en otra pregunta de desbordamiento de pila, liberando la memoria asignada en una DLL diferente .
La mejor herramienta que encontré útil y que funcionó siempre es la revisión del código (con buenos revisores de código).
Aparte de la revisión del código, primero probaría Page Heap . Page Heap tarda unos segundos en configurarse y, con suerte, puede identificar su problema.
Si no tiene suerte con Page Heap, descargue Herramientas de depuración para Windows de Microsoft y aprenda a usar WinDbg. Lamento no poder brindarle ayuda más específica, pero la depuración de la corrupción del montón múltiple es más un arte que una ciencia. Busca "corrupción de montones de WinDbg" en Google y deberías encontrar muchos artículos sobre el tema.
Me gustaría agregar mi experiencia. En los últimos días, resolví una instancia de este error en mi aplicación. En mi caso particular, los errores en el código fueron:
- Eliminar elementos de una colección STL mientras se itera sobre ella (creo que hay indicadores de depuración en Visual Studio para detectar estos elementos, lo atrapé durante la revisión del código)
- Este es más complejo, lo dividiré en pasos:
- Desde un hilo nativo de C ++, devuelva la llamada al código administrado
- En terrenos gestionados, llame a
Control.Invoke
y elimine un objeto gestionado que envuelve el objeto nativo al que pertenece la devolución de llamada. - Dado que el objeto todavía está vivo dentro del hilo nativo (permanecerá bloqueado en la llamada de devolución de llamada hasta que finalice
Control.Invoke
). Debo aclarar que utilizoboost::thread
, por lo que utilizo una función de miembro como función de subproceso. - Solución : utilice
Control.BeginInvoke
(mi GUI está hecha con Winforms) para que el hilo nativo pueda finalizar antes de que se destruya el objeto (el propósito de la devolución de llamada es precisamente notificar que el hilo terminó y el objeto puede ser destruido).
Puede detectar muchos problemas de corrupción de montón al habilitar Page Heap para su aplicación. Para hacer esto, necesita usar gflags.exe que viene como parte de Debugging Tools For Windows
Ejecute Gflags.exe y en las opciones del archivo de imagen para su ejecutable, marque la opción "Habilitar el montón de páginas".
Ahora reinicie su exe y conéctelo a un depurador. Con Page Heap habilitado, la aplicación irrumpirá en el depurador siempre que se produzca cualquier daño en el montón.
Puede usar las macros VC CRT Heap-Check para _CrtSetDbgFlag : _CRTDBG_CHECK_ALWAYS_DF o _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .
Si estos errores ocurren al azar, hay una alta probabilidad de que se encuentre con razas de datos. Por favor, compruebe: ¿modifica los punteros de memoria compartida de diferentes subprocesos? Intel Thread Checker puede ayudar a detectar estos problemas en el programa multiproceso.
Tuve un problema similar, y apareció de forma aleatoria. Quizás algo estaba dañado en los archivos de compilación, pero terminé solucionándolo limpiando el proyecto primero y luego reconstruyendo.
Entonces, además de las otras respuestas dadas:
¿Qué tipo de cosas pueden causar estos errores? Algo corrupto en el archivo de compilación.
¿Cómo los depuro? Limpiando el proyecto y reconstruyendo Si se soluciona, probablemente este sea el problema.
Un artículo muy relevante es Depuración de la corrupción de Heap con Application Verifier y Debugdiag .
Un consejo rápido que recibí de Detectar el acceso a la memoria liberada es este:
Si desea localizar el error rápidamente, sin verificar cada instrucción que accede al bloque de memoria, puede configurar el puntero de memoria a un valor no válido después de liberar el bloque:
#ifdef _DEBUG // detect the access to freed memory #undef free #define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666; #endif
Application Verifier combinado con Debugging Tools para Windows es una configuración increíble. Puede obtener ambos como parte del Kit de controladores de Windows o el SDK de Windows más ligero . (Se enteró sobre Application Verifier al investigar una pregunta anterior sobre un problema de corrupción de montón ). He usado BoundsChecker y Insure ++ (mencionado en otras respuestas) también en el pasado, aunque me sorprendió cuánta funcionalidad había en Application Verifier.
Vale la pena mencionar Electric Fence (también conocido como "efence"), dmalloc , valgrind , etc., pero la mayoría de estos son mucho más fáciles de ejecutar bajo * nix que Windows. Valgrind es ridículamente flexible: he depurado software de servidor grande con muchos problemas de montón usándolo.
Cuando todo lo demás falla, puede proporcionar su propio operador global new / delete y malloc / calloc / realloc overloads - cómo hacerlo variará un poco dependiendo del compilador y la plataforma - y esto será una pequeña inversión - pero puede pagar a largo plazo. La lista de características deseables debería ser familiar desde dmalloc y electricfence, y el libro sorprendentemente excelente Writing Solid Code :
- valores centinela : permita un poco más de espacio antes y después de cada asignación, respetando el requisito de alineación máxima; llenar con números mágicos (ayuda a atrapar desbordamientos y subdesbordamientos de búfer, y el puntero "salvaje" ocasional)
- alloc fill : rellene nuevas asignaciones con un valor mágico que no sea 0: Visual C ++ ya lo hará por usted en compilaciones de depuración (ayuda a captar el uso de vars no inicializados)
- relleno libre : rellene la memoria liberada con un valor mágico que no sea 0, diseñado para desencadenar una segfault si se desreferencia en la mayoría de los casos (ayuda a capturar punteros colgantes)
- retraso libre : no devuelva la memoria liberada al montón por un tiempo, manténgala llena pero no disponible (ayuda a atrapar más punteros colgantes, atrapa casi a doble libre)
- seguimiento : poder registrar dónde se realizó una asignación a veces puede ser útil
Tenga en cuenta que en nuestro sistema homebrew local (para un objetivo incrustado) mantenemos el seguimiento separado de la mayoría de las otras cosas, porque la sobrecarga en tiempo de ejecución es mucho mayor.
Si le interesan más motivos para sobrecargar estos operadores / funciones de asignación, eche un vistazo a mi respuesta a "¿Hay alguna razón para sobrecargar al operador global nuevo y eliminar?" ; descarada autopromoción aparte, enumera otras técnicas que son útiles en el seguimiento de errores de corrupción de montón, así como otras herramientas aplicables.
Para realmente ralentizar las cosas y realizar una gran cantidad de comprobación de tiempo de ejecución, intente agregar lo siguiente en la parte superior de su main()
o equivalente en Microsoft Visual Studio C ++
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );