Pregunta

estoy trabajando en un multiproceso Aplicación C++ que está dañando el montón.Las herramientas habituales para localizar esta corrupción parecen inaplicables.Las versiones antiguas (de 18 meses de antigüedad) del código fuente muestran el mismo comportamiento que la versión más reciente, por lo que esto ha existido durante mucho tiempo y simplemente no se notó;En el lado negativo, los deltas de fuente no se pueden utilizar para identificar cuándo se introdujo el error: hay mucho de cambios de código en el repositorio.

El motivo del comportamiento de bloqueo es generar rendimiento en este sistema: transferencia de datos por socket que se integra en una representación interna.Tengo un conjunto de datos de prueba que periódicamente causarán que la aplicación genere una excepción (varios lugares, varias causas, incluida la falla en la asignación del montón, por lo tanto:corrupción del montón).

El comportamiento parece estar relacionado con la potencia de la CPU o el ancho de banda de la memoria;cuanto más de cada uno tenga la máquina, más fácil será estrellarse.Deshabilitar un núcleo de hiperprocesamiento o un núcleo de doble núcleo reduce la tasa de corrupción (pero no la elimina).Esto sugiere un problema relacionado con el tiempo.

Ahora aquí está el problema:
Cuando se ejecuta en un entorno de depuración ligero (digamos Visual Studio 98 / AKA MSVC6) la corrupción del montón es razonablemente fácil de reproducir: pasan diez o quince minutos antes de que algo falle horrendamente y aparezcan excepciones, como una alloc; cuando se ejecuta en un entorno de depuración sofisticado (Rational Purify, VS2008/MSVC9 o incluso Microsoft Application Verifier) ​​el sistema queda limitado a la velocidad de la memoria y no falla (limitado a la memoria:La CPU no supera 50%, la luz del disco no está encendida, el programa va lo más rápido que puede, la caja consume 1.3G de 2G de RAM).Entonces, Tengo la opción de poder reproducir el problema (pero no identificar la causa) o poder identificar la causa o un problema que no puedo reproducir.

Mis mejores conjeturas actuales sobre el siguiente paso son:

  1. Obtenga un cuadro increíblemente gruñón (para reemplazar el cuadro de desarrollo actual:2Gb de RAM en un E6550 Core2 Duo);esto permitirá reproducir el fallo que causa el mal comportamiento cuando se ejecuta en un potente entorno de depuración;o
  2. Reescribir operadores new y delete usar VirtualAlloc y VirtualProtect para marcar la memoria como de solo lectura tan pronto como termine.correr bajo MSVC6 y hacer que el sistema operativo atrape al malo que escribe en la memoria liberada.Sí, esto es un signo de desesperación:¿Quién diablos reescribe? new y delete?!Me pregunto si esto hará que sea tan lento como en Purify et al.

Y no:El envío con instrumentación Purify incorporada no es una opción.

Un colega pasó y preguntó "¿Desbordamiento de pila?¿¡¿Estamos teniendo desbordamientos de pila ahora?!?"

Y ahora, la pregunta: ¿Cómo localizo el corruptor del montón?


Actualizar:equilibrio new[] y delete[] Parece haber avanzado mucho hacia la solución del problema.En lugar de 15 minutos, la aplicación ahora tarda aproximadamente dos horas antes de fallar.Aún no he llegado.¿Alguna otra sugerencia?La corrupción del montón persiste.

Actualizar:una versión de lanzamiento en Visual Studio 2008 parece dramáticamente mejor;La sospecha actual se basa en STL implementación que viene con VS98.


  1. Reproduzca el problema. Dr Watson producirá un volcado que podría ser útil en análisis posteriores.

Tomaré nota de eso, pero me preocupa que el Dr. Watson sólo se equivoque después del hecho, no cuando el montón esté siendo pisoteado.

Otro intento podría ser usar WinDebug como una herramienta de depuración que es bastante poderosa y al mismo tiempo también liviana.

Lo tengo en marcha en este momento, de nuevo:No es de mucha ayuda hasta que algo sale mal.Quiero atrapar al vándalo en el acto.

Quizás estas herramientas le permitan al menos limitar el problema a cierto componente.

No tengo muchas esperanzas, pero tiempos desesperados exigen...

¿Y está seguro de que todos los componentes del proyecto tienen la configuración correcta de la biblioteca de tiempo de ejecución (C/C++ tab, categoría Generación de código en la configuración del proyecto VS 6.0)?

No, no lo soy, y mañana pasaré un par de horas revisando el espacio de trabajo (58 proyectos en él) y verificando que todos estén compilando y vinculando con los indicadores apropiados.


Actualizar:Esto tomó 30 segundos.Seleccione todos los proyectos en el Settings cuadro de diálogo, anule la selección hasta que encuentre los proyectos que no tienen la configuración correcta (todos tenían la configuración correcta).

¿Fue útil?

Solución

Mi primera opción sería una herramienta de montón dedicada como pageheap.exe.

Reescribir nuevo y eliminar puede ser útil, pero eso no detecta las asignaciones confirmadas por el código de nivel inferior.Si esto es lo que quieres, mejor desvía el low-level alloc APIs usando Microsoft Detours.

También controles de cordura como:verifique que sus bibliotecas en tiempo de ejecución coincidan (versión vs.depuración, multiproceso vs.subproceso único, dll vs.static lib), busque eliminaciones incorrectas (por ejemplo, eliminar donde debería haberse usado eliminar []), asegúrese de no mezclar ni combinar sus asignaciones.

También intente desactivar los hilos de forma selectiva y vea si el problema desaparece.

¿Cómo se ve la pila de llamadas, etc. en el momento de la primera excepción?

Otros consejos

Tengo los mismos problemas en mi trabajo (también usamos VC6 a veces).Y no hay una solución fácil para ello.Sólo tengo algunas pistas:

  • Pruebe con volcados de memoria automáticos en la máquina de producción (consulte Volcador de procesos).Mi experiencia dice el Dr.watson es no es perfecto por vertido.
  • Eliminar todo atrapar(...) de tu código.A menudo ocultan graves excepciones de memoria.
  • Controlar Depuración avanzada de Windows - hay muchos consejos fantásticos para problemas como el suyo.Lo recomiendo con todo mi corazón.
  • Si utiliza STL intentar STLPort y compilaciones comprobadas.Los iteradores no válidos son un infierno.

Buena suerte.Problemas como el suyo nos llevan meses resolver.Prepárate para esto...

Ejecute la aplicación original con ADplus -crash -pn appnename.exeCuando surja el problema de la memoria, obtendrás un gran volcado.

Puede analizar el volcado para determinar qué ubicación de la memoria estaba dañada.Si tiene suerte, la memoria de sobrescritura es una cadena única, podrá averiguar de dónde proviene.Si no tienes suerte, tendrás que profundizar win32 Montar y calcular cuáles eran las características de la memoria original.(heap -x podría ayudar)

Una vez que sepa qué fue lo que falló, puede limitar el uso del verificador de aplicaciones con configuraciones de montón especiales.es decir.puedes especificar qué DLL que monitorea o qué tamaño de asignación monitorear.

Con suerte, esto acelerará el seguimiento lo suficiente como para atrapar al culpable.

En mi experiencia, nunca necesité el modo de verificación de montón completo, pero pasé mucho tiempo analizando los volcados de memoria y explorando las fuentes.

PD:Puedes usar Diagnóstico de depuración para analizar los vertederos.Puede señalar el DLL poseer el montón corrupto y brindarle otros detalles útiles.

Hemos tenido bastante suerte al escribir nuestro propio malloc y funciones gratuitas.En producción, simplemente llaman al malloc estándar y gratis, pero en depuración, pueden hacer lo que quieras.También tenemos una clase base simple que no hace más que anular los operadores nuevo y eliminar para usar estas funciones, luego cualquier clase que escriba puede simplemente heredar de esa clase.Si tiene un montón de código, puede ser un gran trabajo reemplazar las llamadas a malloc y free por el nuevo malloc y free (¡no olvide realloc!), pero a la larga es muy útil.

En el libro de Steve Maguire Escribir código sólido (muy recomendable), hay ejemplos de cosas de depuración que puedes hacer en estas rutinas, como:

  • Realice un seguimiento de las asignaciones para encontrar fugas
  • Asigne más memoria de la necesaria y coloque marcadores al principio y al final de la memoria; durante la rutina libre, puede asegurarse de que estos marcadores sigan ahí.
  • memset la memoria con un marcador en asignación (para encontrar el uso de memoria no inicializada) y en libre (para encontrar el uso de memoria libre)

Otra buena idea es nunca usa cosas como strcpy, strcat, o sprintf -- siempre usa strncpy, strncat, y snprintf.También hemos escrito nuestras propias versiones de estos, para asegurarnos de no cancelar el final de un búfer, y estos también han detectado muchos problemas.

Debería atacar este problema tanto con análisis estático como en tiempo de ejecución.

Para análisis estático considere compilar con PREfast (cl.exe /analyze).Detecta no coincidentes delete y delete[], desbordamientos del buffer y una serie de otros problemas.Esté preparado, sin embargo, para leer muchos kilobytes de advertencia L6, especialmente si su proyecto todavía tiene L4 no arreglado.

PREfast está disponible con Visual Studio Team System y, aparentemente, como parte del SDK de Windows.

La aparente aleatoriedad de la corrupción de la memoria se parece mucho a un problema de sincronización de subprocesos: se reproduce un error dependiendo de la velocidad de la máquina.Si los objetos (fragmentos de memoria) se comparten entre subprocesos y las primitivas de sincronización (sección crítica, exclusión mutua, semáforo, otros) no se realizan por clase (por objeto, por clase), entonces es posible llegar a una situación donde la clase (fragmento de memoria) se elimina/libera mientras está en uso, o se usa después de eliminar/liberar.

Como prueba, podrías agregar primitivas de sincronización a cada clase y método.Esto hará que su código sea más lento porque muchos objetos tendrán que esperarse unos a otros, pero si esto elimina la corrupción del montón, su problema de corrupción del montón se convertirá en uno de optimización del código.

¿Esto ocurre en condiciones de poca memoria?Si es así puede ser que vuelva algo nuevo. NULL en lugar de lanzar std::bad_alloc.Más viejo VC++ los compiladores no implementaron esto correctamente.Hay un artículo sobre Fallos de asignación de memoria heredada chocando STL aplicaciones creadas con VC6.

Probó compilaciones antiguas, pero ¿hay alguna razón por la que no puede retroceder más en el historial del repositorio y ver exactamente cuándo se introdujo el error?

De lo contrario, sugeriría agregar algún tipo de registro simple para ayudar a localizar el problema, aunque no sé qué es lo que específicamente desea registrar.

Si puede descubrir qué PUEDE causar exactamente este problema, a través de Google y la documentación de las excepciones que está obteniendo, tal vez eso le brinde más información sobre qué buscar en el código.

Mi primera acción sería la siguiente:

  1. Compile los binarios en la versión "Versión" pero creando un archivo de información de depuración (encontrará esta posibilidad en la configuración del proyecto).
  2. Utilice Dr Watson como depurador predeterminado (DrWtsn32 -I) en una máquina en la que desee reproducir el problema.
  3. Reproduzca el problema.El Dr. Watson producirá un volcado que podría resultar útil en análisis posteriores.

Otro intento podría ser utilizar WinDebug como herramienta de depuración, que es bastante potente y, al mismo tiempo, ligera.

Quizás estas herramientas le permitan al menos limitar el problema a cierto componente.

¿Y está seguro de que todos los componentes del proyecto tienen la configuración correcta de la biblioteca de tiempo de ejecución (pestaña C/C++, categoría Generación de código en la configuración del proyecto VS 6.0)?

Entonces, según la información limitada que tiene, esto puede ser una combinación de una o más cosas:

  • Mal uso del montón, es decir, doble liberación, lectura después de la liberación, escritura después de la liberación, configuración del indicador HEAP_NO_SERIALIZE con asignaciones y liberación de múltiples subprocesos en el mismo montón
  • Sin memoria
  • Código incorrecto (es decir, desbordamientos de búfer, desbordamientos de búfer, etc.)
  • Problemas de "timing"

Si son los dos primeros pero no el último, ya debería haberlo detectado con pageheap.exe.

Lo que probablemente significa que se debe a cómo el código accede a la memoria compartida.Desafortunadamente, rastrearlo será bastante doloroso.El acceso no sincronizado a la memoria compartida a menudo se manifiesta como extraños problemas de "sincronización".Cosas como no utilizar la semántica de adquisición/liberación para sincronizar el acceso a la memoria compartida con un indicador, no utilizar bloqueos de forma adecuada, etc.

Como mínimo, sería útil poder realizar un seguimiento de las asignaciones de alguna manera, como se sugirió anteriormente.Al menos entonces podrás ver lo que realmente sucedió hasta la corrupción del montón e intentar diagnosticar a partir de ahí.

Además, si puede redirigir fácilmente las asignaciones a varios montones, es posible que desee intentarlo para ver si soluciona el problema o genera un comportamiento de error más reproducible.

Cuando estaba probando con VS2008, ¿ejecutó HeapVerifier con Conservar memoria configurado en Sí?Eso podría reducir el impacto en el rendimiento del asignador de montón.(Además, debe ejecutar Depurar->Iniciar con el Verificador de aplicaciones, pero es posible que ya lo sepa).

También puedes intentar depurar con Windbg y varios usos del comando !heap.

MSN

Si elige reescribir nuevo/eliminar, he hecho esto y tengo un código fuente simple en:

http://gandolf.homelinux.org/~smhanov/blog/?id=10

Esto detecta pérdidas de memoria y también inserta datos de protección antes y después del bloque de memoria para capturar la corrupción del montón.Puede integrarlo colocando #include "debug.h" en la parte superior de cada archivo CPP y definiendo DEBUG y DEBUG_MEM.

La sugerencia de Graeme de malloc/free personalizado es una buena idea.Vea si puede caracterizar algún patrón sobre la corrupción que le permita aprovecharlo.

Por ejemplo, si siempre está en un bloque del mismo tamaño (digamos, 64 bytes), cambie su par malloc/free para asignar siempre fragmentos de 64 bytes en su propia página.Cuando libere un fragmento de 64 bytes, configure los bits de protección de memoria en esa página para evitar lecturas y escrituras (usando VirtualQuery).Entonces, cualquiera que intente acceder a esta memoria generará una excepción en lugar de dañar el montón.

¡Esto supone que la cantidad de fragmentos de 64 bytes pendientes es solo moderada o que tienes mucha memoria para grabar en la caja!

El poco tiempo que tuve para solucionar un problema similar.Si el problema persiste te sugiero que hagas esto:Supervise todas las llamadas a nuevo/eliminar y malloc/calloc/realloc/free.Hago una DLL única exportando una función para registrar todas las llamadas.Esta función recibe parámetros para identificar la fuente de su código, un puntero al área asignada y el tipo de llamada, guardando esta información en una tabla.Se elimina todo par asignado/liberado.Al final o después, necesita realizar una llamada a otra función para crear un informe para los datos restantes.Con esto puedes identificar llamadas incorrectas (nuevas/gratuitas o malloc/eliminar) o perdidas.Si tiene algún caso de sobrescritura del búfer en su código, la información guardada puede ser incorrecta, pero cada prueba puede detectar/descubrir/incluir una solución de falla identificada.Muchas ejecuciones para ayudar a identificar los errores.Buena suerte.

¿Crees que esta es una condición de carrera?¿Hay varios hilos compartiendo un montón?¿Puedes darle a cada hilo un montón privado con HeapCreate, luego podrán ejecutarse rápidamente con HEAP_NO_SERIALIZE?De lo contrario, un montón debería ser seguro para subprocesos, si está utilizando la versión multiproceso de las bibliotecas del sistema.

Un par de sugerencias.Usted menciona las numerosas advertencias en W4; sugeriría que se tome el tiempo para corregir su código para compilarlo limpiamente en el nivel de advertencia 4; esto contribuirá en gran medida a prevenir errores sutiles y difíciles de encontrar.

En segundo lugar, para el modificador /analyze, de hecho genera numerosas advertencias.Para usar este modificador en mi propio proyecto, lo que hice fue crear un nuevo archivo de encabezado que usaba #pragma advertencia para desactivar todas las advertencias adicionales generadas por /analyze.Luego, más abajo en el archivo, activo solo las advertencias que me interesan.Luego use el modificador del compilador /FI para forzar que este archivo de encabezado se incluya primero en todas sus unidades de compilación.Esto debería permitirle utilizar el interruptor /analyze mientras controla la salida.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top