¿Cómo depurar errores de corrupción de montón?

165

Estoy depurando una aplicación C ++ multiproceso (nativa) en Visual Studio 2008. En ocasiones aparentemente aleatorias, aparece un error "Windows ha desencadenado un punto de interrupción ..." con una nota de que esto 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 se bloquee poco después.

El gran problema con estos errores es que aparecen solo después de que la corrupción realmente 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.

Peter Mortensen
fuente

Respuestas:

128

Application Verifier combinado con Debugging Tools for 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 . (Descubrí Application Verifier cuando investigé una pregunta anterior sobre un problema de corrupción del montón ). También he usado BoundsChecker e Insure ++ (mencionado en otras respuestas) en el pasado, aunque me sorprendió cuánta funcionalidad había en Application Verifier.

Cerca eléctrica (también conocida como "efence"), dmalloc , valgrindVale la pena mencionar , etc., pero la mayoría de estos son mucho más fáciles de ejecutar con * nix que Windows. Valgrind es ridículamente flexible: he depurado un gran software de servidor con muchos problemas de montón al usarlo.

Cuando todo lo demás falla, puede proporcionarle a su propio operador global nuevas / eliminar y sobrecargas de malloc / calloc / realloc; cómo hacerlo variará un poco dependiendo del compilador y la plataforma, y ​​esto será una pequeña inversión. pero puede dar sus frutos a largo plazo. La lista de características deseables debería ser familiar por dmalloc y cerca eléctrica, y el libro sorprendentemente excelente Writing Solid Code :

  • valores centinela : permiten 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 detectar desbordamientos y desbordamientos del búfer, y el puntero "salvaje" ocasional)
  • alloc fill : llene nuevas asignaciones con un valor mágico distinto de 0: Visual C ++ ya lo hará por usted en las compilaciones de depuración (ayuda a detectar el uso de variables no inicializadas)
  • Relleno libre : rellene la memoria liberada con un valor mágico distinto de 0, diseñado para desencadenar una falla predeterminada si en la mayoría de los casos se desreferencia (ayuda a atrapar punteros colgantes)
  • sin demora : no devuelva la memoria liberada al montón por un tiempo, manténgala libre pero no disponible (ayuda a capturar más punteros colgantes, captura la doble liberación próxima)
  • seguimiento : poder registrar dónde se realizó una asignación a veces puede ser útil

Tenga en cuenta que en nuestro sistema local de elaboración casera (para un objetivo incrustado) mantenemos el seguimiento separado de la mayoría de las otras cosas, porque la sobrecarga del tiempo de ejecución es mucho mayor.


Si está interesado en más razones para sobrecargar estas funciones / operadores de asignación, consulte mi respuesta a "¿Alguna razón para sobrecargar al operador global nuevo y eliminar?" ; Dejando de lado la descarada autopromoción, enumera otras técnicas que son útiles para rastrear los errores de corrupción del montón, así como otras herramientas aplicables.


Debido a que sigo encontrando mi propia respuesta aquí cuando busco valores de aloc / free / fence que usa MS, aquí hay otra respuesta que cubre los valores de relleno de dbgheap de Microsoft .

leander
fuente
3
Una cosa pequeña que vale la pena señalar sobre Application Verifier: debe registrar los símbolos de Application Verifier antes de los símbolos del servidor de símbolos de microsoft en su ruta de búsqueda de símbolos, si usa eso ... ¡Me llevó un poco de búsqueda descubrir por qué! Avrf no estaba encontrando los símbolos que necesitaba.
leander
Application Verifier fue de gran ayuda, y combinado con algunas conjeturas, ¡pude resolver el problema! Muchas gracias, y también para todos los demás, por mencionar puntos útiles.
¿Application Verifier tiene que usarse con WinDbg, o debería funcionar con el depurador de Visual Studio? He estado tratando de usarlo, pero no genera ningún error o aparentemente no hace nada cuando depuro en VS2012.
Nathan Reed
@NathanReed: Creo que también funciona con VS: consulte msdn.microsoft.com/en-us/library/ms220944(v=vs.90).aspx , aunque tenga en cuenta que este enlace es para VS2008, no estoy Seguro sobre versiones posteriores. La memoria es un poco confusa, pero creo que cuando tuve el problema en el enlace "pregunta anterior" simplemente ejecuté Application Verifier y guardé las opciones, ejecuté el programa, y ​​cuando se bloqueó, elegí VS para depurar. AV acaba de hacer que se bloquee / afirme antes. Sin embargo, el comando! Avrf es específico de WinDbg hasta donde yo sé. ¡Ojalá otros puedan proporcionar más información!
leander
Gracias. De hecho, resolví mi problema original y resultó que no era una corrupción del montón después de todo, sino algo más, por lo que probablemente eso explica por qué el Verificador de aplicaciones no encontró nada. :)
Nathan Reed
35

Puede detectar muchos problemas de corrupción del montón al habilitar Page Heap para su aplicación. Para hacer esto, debe 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 montón de página".

Ahora reinicie su exe y adjúntelo a un depurador. Con Page Heap habilitado, la aplicación se dividirá en depurador cada vez que ocurra algún daño en el montón.

Canopus
fuente
sí, pero una vez que recibo esta llamada de función en mi volcado de pila de llamadas (después de un bloqueo de corrupción de memoria): wow64! Wow64NotifyDebugger, ¿qué puedo hacer? Todavía no sé qué está mal en mi aplicación
Guillaume07
Acabo de probar gflags para depurar la corrupción del montón aquí, pequeña herramienta MUY útil, muy recomendable. Resultó que estaba accediendo a la memoria liberada, que, cuando se instrumenta con gflags, entrará inmediatamente en el depurador ... ¡Práctico!
Dave F
Gran herramienta! Acabo de encontrar un error, que estaba buscando durante días, porque Windows no dice la dirección de la corrupción, solo que "algo" está mal, lo cual no es realmente útil.
Devolus
Un poco tarde para la fiesta, pero noté un aumento significativo en el uso de memoria en la aplicación que estoy depurando cuando encendí Page Heap. Desafortunadamente, hasta el punto en que la aplicación (32 bits) se queda sin memoria antes de que se active la detección de corrupción del montón. ¿Alguna idea de cómo abordar ese problema?
uceumern
13

Para realmente ralentizar las cosas y realizar muchas comprobaciones 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 );
Dave Van Wagner
fuente
8

¿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 que se haya liberado nuevamente en el montón.

¿Cómo los depuro?

Use un instrumento que agregue la verificación automática 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 estos retrasarán su aplicación, por lo que pueden ser inutilizables si la suya es una aplicación suave en tiempo real.

Otra posible ayuda / herramienta de depuración podría ser HeapAgent de MicroQuill.

ChrisW
fuente
1
Reconstruir la aplicación con tiempo de ejecución de depuración (/ MDd o / MTd flag) sería mi primer paso. Estos realizan comprobaciones adicionales en malloc y gratis, y a menudo son eficaces para reducir la ubicación de los errores.
Empleado ruso
HeapAgent de MicroQuill: no se ha escrito ni se ha escuchado mucho al respecto, pero para la corrupción del montón, debería estar en su lista.
Samrat Patil
1
BoundsChecker funciona bien como prueba de humo, pero ni siquiera piense en ejecutar un programa debajo mientras intenta ejecutar ese programa en producción también. La ralentización puede ser de 60x a 300x, según las opciones que esté utilizando y si está utilizando o no la función de instrumentación del compilador. Descargo de responsabilidad: soy uno de los tipos que mantiene el producto para Micro Focus.
Rick Papo
8

Un consejo rápido que obtuve al 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 establecer el puntero de memoria en 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
Apilado
fuente
5

La mejor herramienta que encontré útil y funcionó siempre es la revisión de código (con buenos revisores de código).

Aparte de la revisión de código, primero probaría Page Heap . Page Heap tarda unos segundos en configurarse y, con suerte, podría determinar su problema.

Si no tiene suerte con Page Heap, descargue Debugging Tools para Windows de Microsoft y aprenda a usar WinDbg. Lo sentimos, no podría darte ayuda más específica, pero depurar la corrupción del montón de subprocesos múltiples es más un arte que una ciencia. Google para "corrupción de montón WinDbg" y debería encontrar muchos artículos sobre el tema.

Shing Yip
fuente
4

También es posible que desee verificar si está vinculando con la biblioteca de tiempo de ejecución C dinámica o estática. Si sus archivos DLL se vinculan con la biblioteca de tiempo de ejecución C estática, entonces los archivos DLL tienen montones separados.

Por lo tanto, si creara un objeto en una DLL e intentara liberarlo en otra DLL, obtendría el mismo mensaje que está viendo arriba. Este problema se menciona en otra pregunta de desbordamiento de pila, liberando memoria asignada en una DLL diferente .

dreadpirateryan
fuente
3

¿Qué tipo de funciones de asignación está utilizando? Recientemente recibí un error similar al usar las funciones de asignación de estilo Heap *.

Resultó que estaba creando el montón por error con la HEAP_NO_SERIALIZEopción. Esto esencialmente hace que las funciones de Heap se ejecuten sin seguridad de subprocesos. Es una mejora en el rendimiento si se usa correctamente, pero nunca se debe usar si está usando HeapAlloc en un programa multiproceso [1]. Solo menciono esto porque su publicación menciona que tiene una aplicación multiproceso. Si está utilizando HEAP_NO_SERIALIZE en cualquier lugar, elimínelo y probablemente solucionará su problema.

[1] Hay ciertas situaciones en las que esto es legal, pero requiere que serialice las llamadas a Heap * y, por lo general, no es el caso de los programas multiproceso.

JaredPar
fuente
Sí: mire las opciones de compilación / compilación de la aplicación y asegúrese de que se esté creando para vincular con una versión "multiproceso" de la biblioteca en tiempo de ejecución de C.
ChrisW
@ChrisW para las API de estilo HeapAlloc, esto es diferente. En realidad, es un parámetro que se puede cambiar en el momento de la creación del montón, no en el momento del enlace.
JaredPar
Oh. No se me ocurrió que el OP podría estar hablando de ese montón, y no del montón en el CRT.
ChrisW
@ChrisW, la pregunta es bastante vaga, pero acabo de dar con el problema que detallé hace ~ 1 semana, así que estoy fresco en mi mente.
JaredPar
3

Si estos errores ocurren al azar, existe una alta probabilidad de que haya encontrado carreras de datos. Por favor, compruebe: ¿modifica punteros de memoria compartida de diferentes hilos? Intel Thread Checker puede ayudar a detectar tales problemas en un programa multiproceso.

Vladimir Obrizan
fuente
1

Además de buscar herramientas, considere buscar un probable culpable. ¿Hay algún componente que esté utilizando, quizás no escrito por usted, que no haya sido diseñado y probado para ejecutarse en un entorno multiproceso? O simplemente uno que no conoces ha ejecutado en ese entorno.

La última vez que me sucedió, fue un paquete nativo que se había utilizado con éxito en trabajos por lotes durante años. Pero era la primera vez en esta compañía que se usaba desde un servicio web .NET (que es multiproceso). Eso fue todo: habían mentido acerca de que el código era seguro para subprocesos.

John Saunders
fuente
1

Puede usar macros VC CRT Heap-Check para _CrtSetDbgFlag : _CRTDBG_CHECK_ALWAYS_DF o _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .

TipoDragon
fuente
0

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 itera sobre ella (creo que hay indicadores de depuración en Visual Studio para detectar estas cosas; lo detecté durante la revisión del código)
  • Este es más complejo, lo dividiré en pasos:
    • Desde un hilo nativo de C ++, vuelva a llamar al código administrado
    • En tierra gestionada, llame Control.Invoke y disponga de un objeto gestionado que envuelva 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 Control.Invokefinalice). Debo aclarar que uso boost::thread, por lo que uso una función miembro como la función de hilo.
    • Solución : utilice Control.BeginInvoke(mi GUI se hace con Winforms) para que el hilo nativo pueda terminar antes de que el objeto sea destruido (el propósito de la devolución de llamada es notificar con precisión que el hilo terminó y el objeto puede ser destruido).
dario_ramos
fuente
0

Tuve un problema similar, y surgió al azar. Quizás algo estaba dañado en los archivos de compilación, pero terminé arreglándolo limpiando el proyecto primero y luego reconstruyéndolo.

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? Limpieza del proyecto y reconstrucción. Si se soluciona, probablemente este sea el problema.

Marty
fuente