¿Cómo lidiar con las pérdidas de memoria detectadas por el detector visual de fugas?
La primera vez que vi un detector visual de fugas
La flexibilidad y la libertad son una característica importante del lenguaje C/C, lo que también plantea un problema para los programadores de C/C. A medida que los programas se vuelven cada vez más complejos, la administración de la memoria también se volverá cada vez más compleja y pueden ocurrir problemas de memoria accidentalmente. Las pérdidas de memoria son uno de los problemas de memoria más comunes. Si la pérdida de memoria no es muy grave, no tendrá mucho impacto en el programa en un corto período de tiempo, lo que también hace que el problema de pérdida de memoria esté muy oculto y sea difícil de detectar. Sin embargo, no importa cuán pequeña sea la pérdida de memoria, cuando el programa se ejecuta durante mucho tiempo, su poder destructivo es asombroso, desde la degradación del rendimiento hasta el agotamiento de la memoria e incluso afecta el funcionamiento normal de otros programas. Además, una característica común de los problemas de memoria es que no existe un fenómeno evidente de los problemas de memoria en sí. Cuando ocurre una anomalía, ha pasado el tiempo y la escena no es la misma que cuando ocurrió el problema, lo que dificulta mucho la depuración de problemas de memoria. ltXML namespace prefix = "o"/gt;
Visual Leak Detector es una herramienta gratuita de detección de fugas de memoria. Se puede descargar en /tools/visualleakdetector.asp. En comparación con otras herramientas de detección de pérdidas de memoria, tiene las siguientes características al detectar pérdidas de memoria:
1. y, si es posible, puede obtener el archivo y el número de línea donde se encuentra
2. Obtener los datos completos de la memoria filtrada
3. del informe de pérdida de memoria;
4. Es una biblioteca empaquetada y no es necesario compilar su código fuente al usarla. Para el código propio de los usuarios, sólo se requieren cambios menores;
5. Su código fuente se distribuye bajo licencia GNU, con documentación detallada y comentarios. Esta es una buena opción para los lectores que desean aprender más sobre la administración de memoria dinámica.
Se puede observar que desde la perspectiva del uso, el detector visual de fugas es simple y fácil de usar. Para el código del usuario, la única modificación es #incluir el archivo de encabezado del detector visual de fugas. Luego, el usuario puede ejecutar su propio programa normalmente, para que se puedan descubrir problemas de memoria. Desde una perspectiva de investigación, si profundiza en el código fuente del detector visual de fugas, puede aprender los principios de asignación y liberación de memoria del montón, los principios de detección de fugas de memoria y técnicas comunes para operaciones de memoria.
Este artículo primero presentará los métodos de uso y los pasos del detector visual de fugas, y luego trabajará con los lectores para aprender inicialmente el código fuente del detector visual de fugas y comprender el principio de funcionamiento del detector visual de fugas. .
ltXML namespace prefix = " v "/ gt;
Uso del detector visual de fugas (1.0)
La siguiente es una introducción a cómo utilizar este dispositivo. .
Primero descargue el paquete zip del sitio web y descomprímalo para obtener archivos como vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll, etc. Copiar. h al directorio de inclusión predeterminado de Visual C y. El archivo lib se copia al directorio lib predeterminado de Visual C y se completa la instalación. Debido a problemas de versión, si está utilizando Windows 2000 o una versión anterior, debe copiar dbghelp.dll al directorio de ejecución de su programa u otros directorios a los que se pueda hacer referencia.
Nota: Descargué la versión actualizada 1.9 y la instalé directamente en el sistema. Por lo tanto, primero debe configurar el directorio en VC cuando lo use.
A continuación, debes agregarlo a tu propio código. El método es muy simple, solo incluye vld.h. Archivo cpp que contiene la función de entrada.
Si este archivo cpp contiene stdafx.h, coloque la declaración que contiene vld.h después de la declaración que contiene stdafx.h; de lo contrario, colóquela delante. El siguiente es un programa de muestra:
# include ltvld.h gt
void main()
{
…
}
A continuación, demostremos cómo utilizar un detector visual de fugas para detectar pérdidas de memoria. A continuación se muestra un programa simple que utiliza new para asignar una memoria dinámica de tamaño int y no la libera. Utilice printf para mostrar la dirección de memoria de la aplicación en la pantalla.
Después de compilar y ejecutar, obtendrá el siguiente contenido en la ventana de salida estándar:
p=003a89c0
Ingrese a la ventana de salida de Visual C:
Advertencia: ¡El detector visual de fugas detectó una pérdida de memoria!
- Bloque 57 en 0x003A89C0: 4 bytes - Bloque 57 La dirección en 0x003a89C0 filtró 4 bytes.
Pila de llamadas:- Esta es la pila de llamadas.
d:/test/testvldconsole/testvldconsole/main. CPP(7): función f-main.f() en la línea 7 de CPP.
d:/test/testvldconsole/testvldconsole/main. CPP(14): main-Haga doble clic para iniciar con el código correspondiente.
f:/RTM/VC tools/CRT_bld/self_x86/CRT/src/crtexe c(586):__tmainCRTStartup
f:/RTM/VC tools/CRT_bld/self_x86/ CRT/src/crtexe.c(403): mainCRTStartup
0x7C816D4F (archivo y número de línea no disponibles): RegisterWaitForInputIdle
Datos: - Este es el contenido de la memoria filtrada, 0x12345678 .
78 56 34 12 xV4.................
El detector visual de fugas detectó una pérdida de memoria.
La segunda línea indica que el bloque 57 tiene una pérdida de memoria de 4 bytes en la dirección 0x003A89C0. Según la salida de la consola del programa, podemos saber que la dirección es el puntero p. En la línea 7 del programa, en la función f (), se asignan 4 bytes de espacio de memoria dinámica en esta dirección y el valor es 0x12345678. , entonces en el informe vemos que el contenido de estos 4 bytes es el mismo.
Como puede ver, para cada pérdida de memoria, este informe enumera su punto de fuga, longitud, pila de llamadas al asignar memoria y el contenido de la memoria perdida (enumerados en 16 formatos base y de texto respectivamente). ). Al hacer doble clic en una línea en el informe de la pila, se saltará automáticamente a la línea correspondiente en el archivo al que hace referencia en el editor de código. Esta información nos resulta muy útil para encontrar pérdidas de memoria.
Esta es una herramienta muy cómoda y fácil de usar. Para usarlo después de cada instalación, simplemente incluya sus archivos de encabezado y compílelo. Además, la herramienta solo se conectará a su programa cuando cree versiones de depuración. Si está creando una versión de lanzamiento, esta herramienta no tendrá ningún impacto en el rendimiento de su programa. Para que pueda incluir su archivo de encabezado en su código fuente.
Cómo funciona un detector visual de fugas
Veamos cómo funciona esta herramienta.
Antes de eso, echemos un vistazo a cómo funciona la herramienta de detección de pérdidas de memoria incorporada en Visual C. El trabajo del montón de depuración CRT de la herramienta incorporada de Visual C es muy simple.
Cuando se utiliza la versión de depuración de malloc para asignar memoria, malloc registrará el nombre del archivo y el número de línea de la memoria asignada en el encabezado del bloque de memoria. Cuando el programa sale, el CRT realizará un trabajo de limpieza después de que regrese la función main(). En este punto, verifique la memoria del montón de depuración. Si todavía hay memoria que no se ha liberado, entonces debe haber una pérdida de memoria. El nombre del archivo y el número de línea se pueden obtener del encabezado de estos bloques de memoria inéditos.
Este método estático puede detectar pérdidas de memoria y el nombre del archivo y el número de línea del punto de fuga, pero no sabemos cómo se produce la fuga ni cómo se ejecuta la declaración de asignación de memoria. Para comprender esto, debe rastrear dinámicamente el proceso de asignación de memoria del programa. Esto es lo que hace un detector visual de fugas. Cada vez que se asigna memoria, registra su contexto y, cuando el programa sale, busca pérdidas de memoria detectadas en la información de contexto registrada y la convierte en un informe.
Inicialización
El detector visual de fugas registra cada asignación de memoria. ¿Cómo monitorea la asignación de memoria? Windows proporciona enlaces de asignación para monitorear la asignación de memoria del montón de depuración. Es una función de devolución de llamada definida por el usuario que se llama antes de cada asignación de memoria desde el montón de depuración. Durante la inicialización, Visual Leak Detector registra esta función de enlace con _ CrtSetAllocHook para que pueda monitorear todas las asignaciones de memoria del montón a partir de ese momento.
¿Cómo garantizar que no haya ninguna asignación de memoria dinámica antes de que se inicialice el detector visual de fugas? Cuando se inicia el programa, las variables globales se inicializan. Si el detector visual de fugas se utiliza como variable global, se puede iniciar mediante programación. Sin embargo, C/C no especifica el orden de inicialización entre variables globales. Si hay una asignación de memoria dinámica en el constructor de otras variables globales, es posible que no se detecte. Visual Leak Detector utiliza #pragma init_seg proporcionado por C/C, lo que hasta cierto punto reduce la probabilidad de que otras variables globales se inicialicen antes que él. Según la definición de #pragma init_seg, la inicialización de variables globales se divide en tres etapas: primero, la sección del compilador, cuando se inicializa la biblioteca de tiempo de ejecución del lenguaje C general, luego la sección lib, generalmente utilizada para la inicialización de terceros; bibliotecas de clases. Finalmente, está el segmento de usuarios, donde ocurre la mayor parte de la inicialización. Visual Leak Detector establece su inicialización en la parte del compilador, por lo que se inicializa antes que la mayoría de las variables globales y casi todas las variables globales definidas por el usuario.
Asignación de memoria de registro
La función de enlace de asignación debe tener la siguiente forma:
int YourAllocHook( int allocType, void *userData, size_t size, int blockType , long requestNumber , const unsignedchar *filename, int line number);
Como se mencionó anteriormente, se registra cuando se inicializa Visual Leak Detector y se llama cada vez antes de que se asigne memoria desde el montón de depuración. Lo que esta función necesita procesar es registrar la pila de llamadas en este momento y el identificador único de este número de solicitud de asignación de memoria dinámica.
Obtener la representación binaria de la pila actual no es muy complicado, pero debido a diferentes arquitecturas, compiladores y convenciones de llamada de funciones, el contenido de la pila es ligeramente diferente entre sí, por lo que interpretar la pila y obtener la totalidad El proceso de llamada a la función es un poco complicado. Sin embargo, Windows proporciona la función StackWalk64 para obtener el contenido de la pila.
La declaración de StackWalk64 es la siguiente:
Pila booleana Walk64(
Tipo de máquina DWORD,
Manejo de hProcess,
Manejo de alta hilo de temperatura,
p>
LPSTACKFRAME64 StackFrame,
PVOID ContextRecord,
PREAD_PROCESS_MEMORY_routine 64 ReadMemoryRoutine,
p function_TABLE_ACCESS_routine 64 FunctionTableAccessRoutine ,
PGET_module_base_routine 64 GetModuleBaseRoutine,
p Translate_ADDRESS_routine 64 Translate ADDRESS
);
La estructura STACKFRAME64 representa un marco en la pila. Proporcione el STACKFRAME64 inicial y llame a esta función repetidamente para obtener la pila de llamadas del punto de asignación de memoria.
//Atraviesa la pila.
while(count lt;_VLD_maxtraceframes) {
count;
if (!pStackWalk64(architecture, m_process, m_thread, ampframes y . context, p>
NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {
// No se pueden retroceder más fotogramas.
Romper;
}
if (Frame.AddrFrame.Offset == 0) {
//Fin de la pila.
Pausa;
}
//Empuje el contador del programa de este marco a la pila de llamadas proporcionada.
Pila de llamadas - gt; push_back((DWORD_PTR) frame. add RPC . Offset);
}
Entonces, ¿cómo obtener la estructura STACKFRAME64 inicial? En la arquitectura STACKFRAME64, se puede acceder fácilmente a otra información, mientras que el software en la arquitectura x86 no puede leer directamente el contador del programa actual (EIP). El detector visual de fugas utiliza un método para obtener el contador del programa actual. Primero, llama a una función cuya dirección de retorno es el contador del programa actual. La dirección de retorno de la función se puede obtener fácilmente de la pila.
El siguiente es el programa para que el detector visual de fugas obtenga el contador del programa actual:
#Si está definido (_M_IX86) || Definido (_M_X64)
#pragma auto_inline(off)< / p>
Detector visual de fugas DWORD_PTR::getprogramcounter x86x 64()
{
Contador de programa DWORD_PTR;
__asm mov AXREG, [ BPREG TAMAÑOOFPTR] //Obtener la dirección de retorno del marco de pila actual
__asm mov [programcounter], AXREG //Colocar la dirección de retorno en la variable que queremos devolver
Volver al contador de programas p>
}
#pragma auto_inline(on)
#endif //Definido (_M_IX86) || Definido (_M_X64)
Cuando obtenga la pila de llamadas, naturalmente se grabará. Los detectores visuales de fugas utilizan una estructura de datos similar a un mapa para registrar esta información. Esto puede encontrar fácilmente su pila de llamadas desde requestNumber. El parámetro AllocType de la función de enlace de asignación indica el tipo de asignación de memoria del montón, incluidos _HOOK_ALLOC, _HOOK_REALLOC y _HOOK_FREE. El siguiente código es una visualización de cómo el detector de fugas maneja diversas situaciones.
cambiar(tipo){
case_hook_allocation:
visualleakdetector.hookmalloc(solicitud);
romper;
p>
caso _HOOK_FREE:
detector visual de fugas sin gancho(pdata);
rotura;
caso _HOOK_REALLOC:
detector de fugas visual . hook realloc(pdata, request);
Break;
Valor predeterminado:
visualleakdetector.report("Advertencia: Detector de fugas visual : en allochook(): tipo de asignación no controlada (d). /n", tipo);
Break;
}
Aquí, hookmalloc() El La función obtiene la pila actual y agrega la pila actual y el número de solicitud a una estructura de datos similar a un mapa. La función hookfree() elimina esta información de una estructura de datos similar a un mapa. La función Hookrealloc() llama a hookfree() y hookmalloc() en secuencia.
Detección de pérdidas de memoria
Como se mencionó anteriormente, el principio de funcionamiento de la herramienta de detección de fugas de memoria incorporada de Visual C. Siguiendo el mismo principio, debido a que las variables globales se destruyen en el orden inverso a la construcción, cuando se destruye el detector visual de fugas, casi todas las demás variables se destruyen. En este momento, si todavía hay memoria de montón no liberada, se trata de una pérdida de memoria.
La memoria del montón asignada está organizada en una lista vinculada. Verificar pérdidas de memoria es verificar esta lista vinculada. Pero Windows no proporciona un método para acceder a esta lista vinculada. El detector visual de fugas utilizó un truco para conseguirlo. Primero solicite una memoria temporal en el montón y luego la dirección de la memoria se puede convertir para que apunte a una estructura _CrtMemBlockHeader, en la que se puede obtener una lista vinculada.
El código es el siguiente:
char * pheap = new char
_ CrtMemBlockHeader * pheader = pHdr(pheap)-gt;pBlockHeaderNext
Eliminar pheap p>
donde pheader es el primer puntero de la lista enlazada.
Generación de informes
Hablé sobre cómo el detector visual de fugas detecta y registra pérdidas de memoria y sus pilas de llamadas. Pero para que esta información sea útil para los programadores, debe convertirse a un formato legible. Visual Leak Detector utiliza SymGetLineFromAddr64() y SymFromAddr() para generar informes legibles.
//Recorre cada fotograma de la pila de llamadas.
for(frame = 0; frame lt call stack - gt; size(); frame) {
//Intenta obtener el archivo fuente y el número de línea asociado con
//La dirección del contador de este programa.
if(psymgetlinefromaddr 64(m_process,
(*callstack)[frame], ampdisplacement ampsourceinfo)) {
... p>
}
//Intenta obtener el nombre de la función que contiene este programa
//Dirección del contador.
if (pSymFromAddr(m_process, (*callstack)[frame],
desplazamiento de amplificador 64, pfunctioninfo)) {
nombre de función = pfunctioninfo- gt; Nombre;
}
De lo contrario{
functionname = "(el nombre de la función no está disponible)";
}
...
}
En términos generales, el trabajo del detector visual de fugas se divide en tres pasos: primero, registrar una función de enlace durante la inicialización y luego cuando se asigna la memoria; Llame a la función de enlace para registrar la escena en ese momento; finalmente, verifique la lista de asignación de memoria del montón para determinar si hay una pérdida de memoria y convierta la memoria perdida en un formato legible para su salida. Los lectores interesados pueden leer el código fuente del detector visual de fugas.
Resumen
En uso, el detector visual de fugas es simple y conveniente, y el informe de resultados es claro de un vistazo. En principio, un detector visual de fugas puede detectar las características de las pérdidas de memoria. ¿No son difíciles de encontrar? Luego registre cada asignación de memoria y calcule el libro mayor cuando salga el programa, ¿no es que ha pasado el tiempo cuando ocurre la pérdida de memoria y no es el escenario de la pérdida en ese momento? Luego, la escena se graba para decirle claramente al usuario cómo se filtró la memoria filtrada durante una llamada.
Visual Leak Detector es una herramienta de detección de fugas de memoria sencilla y fácil de usar. La última versión ahora es 1.9a, que adopta un nuevo mecanismo de detección y tiene muchas mejoras en la funcionalidad. También podría experimentarlo.