Aquí podría ser tu PUBLICIDAD


Heap corrupción bajo Win32; cómo localizar?

votos
54

Estoy trabajando en una aplicación multiproceso de C ++ que está corrompiendo el montón. Las herramientas habituales para localizar esta corrupción parecen ser inaplicables. Las compilaciones antiguas (18 meses) del código fuente exhiben el mismo comportamiento que las versiones más recientes, por lo que esto ha existido por mucho tiempo y simplemente no se notó; a la baja, los deltas fuente no se pueden usar para identificar cuándo se introdujo el error: hay muchos cambios de código en el repositorio.

La solicitud de bloqueo del comportamiento consiste en generar un rendimiento en este sistema: transferencia de datos del socket que se envía a una representación interna. Tengo un conjunto de datos de prueba que causarán periódicamente que la aplicación haga una excepción (varios lugares, varias causas, incluido el error de asignación de heap, 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 tiene la máquina, más fácil es estrellarse. Deshabilitar un núcleo de hiper-threading 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 este es el problema:
cuando se ejecuta bajo un entorno ligero de depuración (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 horrendo y excepciones, como alloc;cuando se ejecuta en un entorno sofisticado de depuración (Rational Purify, VS2008/MSVC9o incluso Microsoft Application Verifier) ​​el sistema pasa a la velocidad de memoria y no se cuelga (En memoria: la CPU no está arriba 50%, la luz del disco no está encendida, el programa funciona tan rápido como puede, consume 1.3G2G de RAM) . Entonces, tengo que elegir entre poder reproducir el problema (pero no identificar la causa) o ser capaz de identificar la causa o un problema que no puedo reproducir.

Mis mejores conjeturas actuales sobre dónde ir a continuación son:

  1. Obtenga una caja de Grunty locamente (para reemplazar la caja de desarrollo actual: 2Gb de RAM en una E6550 Core2 Duo); esto hará posible reproducir el error que causa el mal comportamiento cuando se ejecuta bajo un poderoso entorno de depuración; o
  2. Reescriba los operadores newy deleteuse VirtualAllocy VirtualProtectmarque la memoria como de solo lectura tan pronto como haya terminado. Corre bajo MSVC6y haz que el sistema operativo atrape al tipo malo que está escribiendo en la memoria liberada. Sí, esto es un signo de desesperación: ¿quién diablos reescribe newy delete?! Me pregunto si esto va a ser tan lento como en Purify et al.

Y, no: el envío con instrumentación Purify integrada no es una opción.

Un colega acaba de pasar y preguntó Stack Overflow? ¿Estamos recibiendo desbordamientos de pila ahora?!?

Y ahora, la pregunta: ¿cómo localizo el corruptor de montón?


Actualización: equilibrio new[]y delete[]parece haber recorrido un largo camino hacia la solución del problema. En lugar de 15 minutos, la aplicación ahora dura unas dos horas antes de estrellarse. Todavía no está allí. ¿Alguna otra sugerencia? La corrupción del montón persiste.

Actualización: una compilación de lanzamiento en Visual Studio 2008 parece dramáticamente mejor; la sospecha actual se basa en la STLimplementación que se envía VS98.


  1. Reproduce el problema. Dr Watsonproducirá un volcado que podría ser útil en análisis posteriores.

Tomaré nota de eso, pero me preocupa que el Dr. Watson solo se tropiece después del hecho, no cuando se aprieta el montón.

Otro intento podría estar utilizando WinDebugcomo una herramienta de depuración que es bastante poderosa siendo al mismo tiempo también ligera.

Tengo que ir en este momento, de nuevo: no hay mucha ayuda hasta que algo sale mal. Quiero atrapar el vándalo en el acto.

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

No tengo mucha esperanza, pero los tiempos desesperados requieren ...

¿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++ tabcategoría de generación de código en la configuración del proyecto VS 6.0)?

No, no, y pasaré un par de horas mañana revisando el espacio de trabajo (58 proyectos) y verificando que todos compilan y enlazan con las banderas correspondientes.


Actualización: esto tomó 30 segundos. Seleccione todos los proyectos en el Settingscuadro de diálogo, deseleccione hasta que encuentre el / los proyecto (s) que no tienen la configuración correcta (todos tienen la configuración correcta).

Publicado el 04/08/2008 a las 08:30
fuente por usuario Josh
En otros idiomas...        العربية       

15 respuestas

votos
26

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

Volver a escribir nuevo y eliminar podría ser útil, pero eso no atrapa los allocs cometidos por el código de nivel inferior. Si esto es lo que desea, es mejor desviarse low-level alloc APIutilizando Microsoft Desvíos.

También comprobaciones de cordura, tales como: verifique que coincidan las bibliotecas en tiempo de ejecución (release vs. debug, multi-threaded vs. single-thread, dll vs. lib estáticas), busque malas eliminaciones (por ejemplo, eliminar donde debería haber sido delete []) utilizado), asegúrese de no mezclar y hacer coincidir sus allocs.

También intente apagar selectivamente los hilos y ver cuándo / si el problema desaparece.

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

Respondida el 04/08/2008 a las 08:51
fuente por usuario user2189331


Aquí podría ser tu PUBLICIDAD


votos
11

Tengo los mismos problemas en mi trabajo (también los utilizamos a VC6veces). Y no hay una solución fácil para eso. Solo tengo algunos consejos:

  • Intente con volcados automáticos en la máquina de producción (consulte Descargador de procesos ). Mi experiencia dice que el Dr. Watson no es perfecto para tirar basura.
  • Elimine todas las capturas (...) de su código. A menudo esconden excepciones de memoria graves.
  • Verifique la depuración avanzada de Windows : hay muchos consejos excelentes para problemas como el suyo. Lo recomiendo con todo mi corazón.
  • Si usas STLtry STLPorty checked builds. El iterador inválido es el infierno.

Buena suerte. Problemas como el tuyo tardan meses en resolverse. Prepárate para esto ...

Respondida el 06/08/2008 a las 01:41
fuente por usuario Michal Sznajder

votos
8

Ejecute la aplicación original con ADplus -crash -pn appnename.exe Cuando aparezca el problema de memoria, obtendrá un buen volcado grande.

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

Después de saber qué fue lo que se ha estropeado, puede restringir el uso de la aplicación con configuraciones especiales de almacenamiento dinámico. es decir, puede especificar qué DLLsupervisa o qué tamaño de asignación supervisar.

Esperemos que esto acelere el monitoreo lo suficiente como para atrapar al culpable.

En mi experiencia, nunca necesité el modo de verificador de montón completo, pero pasé mucho tiempo analizando el / los volcado (s) de bloqueo y las fuentes de exploración.

PD: Puedes usar DebugDiag para analizar los vertederos. Puede señalar que es DLLpropietario del montón corrupto y darle otros detalles útiles.

Respondida el 16/09/2008 a las 08:33
fuente por usuario Tal

votos
7

Tuvimos mucha suerte escribiendo nuestras propias funciones gratuitas y malloc. En producción, simplemente llaman al estándar Malloc y gratuito, pero en depuración, pueden hacer lo que quieran. También tenemos una clase base simple que no hace más que anular los operadores nuevos y eliminar para usar estas funciones, entonces cualquier clase que escriba simplemente puede heredar de esa clase. Si tienes un montón de código, puede ser un gran trabajo reemplazar las llamadas a malloc y gratis al nuevo malloc y gratuito (¡no olvides el realloc!), Pero a la larga es muy útil.

En el libro de Steve Maguire Writing Solid Code (altamente recomendado), hay ejemplos de depuración que puede hacer en estas rutinas, como:

  • Mantenga un registro 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 estén todavía allí.
  • memset la memoria con un marcador en la asignación (para encontrar el uso de la memoria no inicializada) y en libre (para encontrar el uso de la memoria libre)

Otra buena idea es no usar cosas como strcpy, strcato sprintf- utilizar siempre strncpy, strncaty snprintf. También hemos escrito nuestras propias versiones de estos, para asegurarnos de que no cancelamos el buffer, y estos también han atrapado muchos problemas.

Respondida el 22/08/2008 a las 06:11
fuente por usuario Graeme Perrow

votos
4

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

Para el análisis estático, considere compilar con PREfast ( cl.exe /analyze). Detecta desfases de búfer deletey no coincidentes delete[], y una serie de otros problemas. Sin embargo, prepárate para recorrer muchos kilobytes de advertencia L6, especialmente si tu proyecto aún L4no se ha solucionado.

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

Respondida el 12/10/2008 a las 10:55
fuente por usuario Constantin

votos
3

¿Esto es en condiciones de poca memoria? Si es así, es posible que lo nuevo esté volviendo en NULLlugar de arrojar std :: bad_alloc. Los VC++compiladores antiguos no implementaron correctamente esto. Hay un artículo sobre fallas de asignación de memoria heredada que bloquea STLaplicaciones compiladas VC6.

Respondida el 02/09/2008 a las 07:03
fuente por usuario Steve Steiner

votos
3

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 (trozos de memoria) se comparten entre subprocesos y la sincronización (sección crítica, mutex, semáforo, otro) las primitivas no están en base a clase (por objeto, por clase), entonces es posible llegar a una situación donde la clase (fragmento de memoria) se elimina / libera durante el uso, o se utiliza después de eliminar / liberar.

Como prueba para eso, puede 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 esperar el uno al otro, 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.

Respondida el 25/08/2008 a las 08:55
fuente por usuario Ignas Limanauskas

votos
1

Si eliges reescribir new / delete, he hecho esto y tengo un código fuente simple en:

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

Esto capta pérdidas de memoria y también inserta datos de guardia antes y después del bloque de memoria para capturar la corrupción del montón. Simplemente puede integrarse con él poniendo #include "debug.h" en la parte superior de cada archivo CPP y definiendo DEBUG y DEBUG_MEM.

Respondida el 17/09/2008 a las 02:40
fuente por usuario Steve Hanov

votos
1

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

  • Uso incorrecto del montón, es decir, doble libre, lectura después de libre, escritura después de libre, estableciendo el indicador HEAP_NO_SERIALIZE con allocs y libre de múltiples hilos en el mismo montón
  • Sin memoria
  • Código incorrecto (es decir, desbordamientos de búfer, subdesbordamientos de búfer, etc.)
  • Problemas de "sincronización"

Si se trata de los dos primeros, pero no el último, debería haberlo capturado ya sea con pageheap.exe.

Lo que probablemente significa que se debe a la forma en que el código está accediendo a la memoria compartida. Desafortunadamente, rastrear eso será bastante doloroso. El acceso no sincronizado a la memoria compartida a menudo se manifiesta como problemas extraños de "temporización". Cosas como no usar la semántica de adquisición / liberación para sincronizar el acceso a la memoria compartida con una bandera, no usar bloqueos de manera apropiada, etc.

Por lo menos, sería útil poder seguir las asignaciones de alguna manera, como se sugirió anteriormente. Al menos entonces puedes ver lo que realmente sucedió hasta la corrupción del montón e intentar diagnosticar a partir de eso.

Además, si puede redirigir fácilmente las asignaciones a varios montones, puede intentar eso para ver si eso soluciona el problema o si produce un comportamiento de errores más reproducible.

Cuando estaba probando con VS2008, ¿se ejecutó con HeapVerifier con Conserve Memory establecido en Sí? Eso podría reducir el impacto en el rendimiento del asignador de montón. (Además, debe ejecutarlo Depurar-> Comenzar con el Verificador de aplicación, pero es posible que ya lo sepa).

También puede probar la depuración con Windbg y varios usos del comando! Heap.

MSN

Respondida el 22/08/2008 a las 05:51
fuente por usuario Mat Noguchi

votos
0

Un par de sugerencias Mencionas las abundantes advertencias en W4, te sugiero que te tomes el tiempo de arreglar tu código para compilar limpio en el nivel de advertencia 4, esto evitará sutiles errores difíciles de encontrar.

En segundo lugar, para el interruptor / analizar, sí genera muchas advertencias. Para utilizar este modificador en mi propio proyecto, lo que hice fue crear un nuevo archivo de encabezado que utilizara la advertencia #pragma para desactivar todas las advertencias adicionales generadas por / analizar. Luego, más abajo en el archivo, enciendo solo esas advertencias que me importan. A continuación, utilice 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 usar el interruptor / analyze mientras controla la salida

Respondida el 03/10/2009 a las 05:48
fuente por usuario Dan

votos
0

¿Crees que esta es una condición de carrera? ¿Hay varios hilos que comparten un montón? ¿Puede dar a cada hilo un montón privado con HeapCreate, y luego pueden ejecutarlo rápidamente con HEAP_NO_SERIALIZE? De lo contrario, un montón debe ser seguro para subprocesos, si está utilizando la versión de subprocesos múltiples de las bibliotecas del sistema.

Respondida el 30/07/2009 a las 02:48
fuente por usuario Don

votos
0

El poco tiempo que tuve para resolver un problema similar. Si el problema persiste, le sugiero que haga esto: monitoree todas las llamadas a new / delete y malloc / calloc / realloc / free. Hago DLL solo exportando una función para registrar todas las llamadas. Esta función recibe un parámetro para identificar la fuente del código, el puntero al área asignada y el tipo de llamada que guarda esta información en una tabla. Se elimina todo el par asignado / liberado. Al final o después de que lo necesite, realice una llamada a otra función para crear un informe de los datos de la izquierda. Con esto puedes identificar las llamadas incorrectas (nuevas / gratis o malloc / eliminar) o faltantes. Si tiene un caso de búfer sobreescrito 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 carreras para ayudar a identificar los errores. Buena suerte.

Respondida el 19/12/2008 a las 12:52
fuente por usuario lsalamon

votos
0

La sugerencia de Graeme de personalizar malloc / free es una buena idea. Vea si puede caracterizar algún patrón sobre la corrupción para darle un manejo para aprovechar.

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

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

Respondida el 02/09/2008 a las 05:23
fuente por usuario Rob Walker

votos
0

Mi primera acción sería la siguiente:

  1. Cree 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 el Dr. Watson como un depurador de defualt (DrWtsn32 -I) en una máquina en la que desea reproducir el problema.
  3. Replicar el problema. El Dr. Watson producirá un vertedero que podría ser útil en análisis posteriores.

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

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

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

Respondida el 04/08/2008 a las 09:26
fuente por usuario Piotr Tyburski

votos
0

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

De lo contrario, sugeriría agregar un registro simple de algún tipo para ayudar a rastrear el problema, aunque estoy perdiendo específicamente lo que podría querer registrar.

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

Respondida el 04/08/2008 a las 08:48
fuente por usuario Mike Stone


Aquí podría ser tu PUBLICIDAD