TUTORIAL SOBRE VALGRIND

1. Un primer vistazo

La distribución Valgrind tiene múltiples herramientas. La herramienta de chequeo de memoria (Memcheck) puede detectar errores comunes de manejo de memoria tales como:



Esta sección describe la mínima información que necesitas para comenzar a detectar errores de memoria en sus programas usando Memcheck. Esta guía es válida para Valgrind versión 2.4.0.

1. Preparando el programa. Compila el programa utilizando la opción -g para que incluya en el ejecutable final información de depuración con el objetivo de que los mensajes de error de Memcheck incluyan los números de línea exactos.

2. Ejecuta tu programa bajo Memcheck. Si normalmente ejecutas tu programa de la siguiente forma:

miprograma arg1 arg2

Usa la siguiente línea de órdenes:

valgrind –tool=memcheck miprograma arg1 arg2

Memcheck es la herramienta por defecto. La opción –leak-check activa el “memory leak detector”.

El programa se ejecutará con una velocidad inferior a la normal(en un orden de 20-30%), y usará una cantidad de memoria mayor. Memcheck emitirá mensajes sobre las “fisuras” y resto de errores de memoria que detecte.

3. Interpretar la salida de Memcheck. A continuación puedes ver un programa en C con errores de memoria y una “grieta de memoria”

#include <stdlib.h>


void f(void)

{

int* x = malloc(10 * sizeof(int));

x[10] = 0; // problem 1: heap block overrun

} // problem 2: memory leak -- x not freed


int main(void)

{

f();

return 0;

}

Los mensajes de error generados se parecerán al siguiente listado, el cual describe el primer problema: asignación de memoria fuera de la memoria reservada en el heap (heap block overrun).

==19182== Invalid write of size 4

==19182== at 0x804838F: f (example.c:8)

==19182== by 0x80483AB: main (example.c:14)

==19182== Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd

==19182== at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)

==19182== by 0x8048385: f (example.c:7)

==19182== by 0x80483AB: main (example.c:14)

Aspectos a tener en cuenta:


Es aconsejable reparar los errores en el orden en que aparecen en el listado ya que errores posteriores pueden haber sido causados por errores previos.

Los mensajes de “fisuras en la memoria” presentan un aspecto similar al siguiente:

==19182== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1

==19182== at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)

==19182== by 0x8048385: f (a.c:7)

==19182== by 0x80483AB: main (a.c:14)

La traza de pila nos dice donde se asignó la memoria agrietada. Mencheck no puede decirte por qué se agrietó la memoria. (Ignora el mensaje “vg_replace_malloc.h”, ya que es un detalle de la implementación).

Existen varias clases de fisuras; las dos categorías más importantes son:

definitely lost”: tu programa tiene filtraciones de memoria. Repáralo.

probably lost”: tu programa tiene filtraciones de memoria, a menos que estés “jugando” con los punteros (cosas del tipo mover un puntero para que apunte a la mitad de un bloque del heap).

Si no entiendes algún mensaje de error, consulta la sección 3 del manual de usuario que contiene ejemplos de todos los mensajes de error que puede producir como salida Memcheck.

2. Introducción

Valgrind es un sistema flexible para depurar (debugging) y perfilar (profiling) ejecutables sobre Linux-x86. El sistema está compuesto por un núcleo, the valgrind core, que proporciona una CPU x86 en software (CPU virtual), y una serie de herramientas, cada una de las cuales realiza alguna clase de depuración, perfilado o tarea similar. La arquitectura es modular, por lo que se pueden crear nuevas herramientas de forma fácil y sin afectar la estructura existente.

Se suministran en la distribución estándar las siguientes herramientas:



Memcheck detecta problemas de gestión de memoria en tus programas. Se chequean todas las lecturas y escrituras de memoria, y se interceptan todas las llamadas a malloc/free (C) o new/delete (C++). Como consecuencia, Memcheck puede detectar los siguientes problemas:

Los problemas de este tipo pueden ser difíciles de encontrar por otros medios, permaneciendo a menudo sin ser detectados durante largos periodos de tiempo, y produciendo “cuelgues” (crashes) ocasionales del programa, difíciles de diagnosticar.



Addrcheck es una versión ligera de Memcheck. Es idéntico a Memcheck excepto por el detalle de que no realiza chequeos de valores no inicializados. El resto de chequeos, principalmente el chequeo de memoria exhaustivo, se realizan de igual forma. El lado negativo de esto es que no puedes capturar los errores de valores no inicializados que sí puede encontrar Memcheck.

Pero el lado positivo es significativo: los programas se ejecutan dos veces más rápido que sobre Memcheck y además se utiliza mucha menos memoria. Todavía se encuentran lecturas/escrituras de memoria liberada previamente, memoria referenciada fuera de los límites de bloques del heap y en otros lugares no válidos, ¡errores que querrás encontrar antes de liberar el programa!

Como Addrcheck es más ligero y rápido que Memcheck, puedes ejecutar más programas y por tanto podrías cubrir más escenarios de prueba.



Cachegrind es un perfilador de caché. Realiza simulación detallada de las cachés de nivel L1, D1 y L2 de tu CPU y por tanto puede señalar de forma precisa las fuentes de que provocan fallos en la caché de tu código. Cachegrind auto-detecta la configuración de caché de tu máquina usando la instrucción CPUID, y por tanto no necesita ninguna información de configuración adicional, en la mayoría de los casos.

Cachegrind se complementa por la maravillosa herramienta de visualización de Josef Weidendorfer: KcacheGrind ( http://kcachegrind.sourceforge.net), una aplicación KDE que presenta estos resultados de la perfilado de una forma gráfica y fácil de comprender.



Helgrind encuentra condiciones de carrera de datos (data races) en programas multihebrados. Helgrind busca secciones de memoria que son accedidas por más de una hebra (POSIX pthread) pero para las cuales no se puede encontrar ningún uso consistente de un (pthread_mutex_lock). Tales secciones indican una sincronización perdida entre hebras, y podrían causar problemas dependientes del orden de ejecución de las hebras difíciles de encontrar.



También se suministran un número de herramientas menores (corecheck, lackey y Nulgrind). No son particularmente útiles –existen para ilustrar como crear herramientas simples y para ayudar a los desarrolladores de valgrind de diferentes formas.

Valgrind está fuertemente ligado a detalles de la CPU, sistema operativo y en menor grado, compilador y bibliotecas básicas de C. Esto hace difícil hacerlo portable, por lo que se ha decidido implementarlo sobre la plataforma más ampliamente usada: Linux sobre x86s. Valgrind usa los mecanismos estándar de UNIX ./configure, make, make install, y funciona sobre máquinas con kernel 2.2 o 2.4 y glibc 2.1.X, 2.2.X o 2.3.1. Esto debería cubrir la mayoría de las instalaciones Linux modernas1.



2.1. Entender y usar el núcleo de Valgrind

Esta sección describe los servicios del núcleo de Valgrind, los flags y comportamientos. Esto significa que es importante sin considerar que tipo particular de herramienta vayamos a usar posteriormente. Una cuestión terminológica: muchas de las referencias a “valgrind” en el resto de esta sección se refieren a los servicios del núcleo de valgrind.

2.1.1. Qué hace con tu programa

Valgrind está diseñado para interferir lo mínimo posible en tu programa. Trabaja directamente con los ejecutables. No necesitas recompilar, reenlazar, o realizar cualquier otro tipo de modificación en el programa que vas a chequear. Simplemente escribe valgrind –tool=tool_name al comienzo de la línea de órdenes que normalmente usarías para ejecutar el programa. Por ejemplo, si quieres ejecutar la orden ls -l usando la herramienta pesada de chequeo de memoria Memcheck, utiliza la orden:

valgrind –tool=memcheck ls -l



Independientemente de que herramienta se use, Valgrind toma el control de tu programa antes de que éste comience. La información de depuración se lee del ejecutable y las bibliotecas asociadas, para que los mensajes de error y otra información de salida puedan ser formateadas en términos de localizaciones de código fuente (si es apropiado).

Tu programa se ejecuta luego en una CPU x86 virtual proporcionada por el núcleo de Valgrind. Cuando se ejecuta nuevo código por primera vez, el núcleo entrega el código a la herramienta seleccionada. La herramienta le añade su propio código de instrumentación y devuelve el resultado al núcleo, el cuál coordina la ejecución continuada de este código instrumentalizado.

La cantidad de código de instrumentación añadido varía ampliamente entre las herramientas. En un extremo de la escala, Memcheck añade código para chequear cada acceso a memoria y cada valor calculado, incrementando el tamaño del código al menos 12 veces, y haciendo que se ejecute a una velocidad 25 a 30 veces más lenta que en su forma nativa. En el otro extremo del espectro, la ultra-trivial herramienta Nulgrind no añade ninguna instrumentación y causa que se ejecute solamente a una velocidad 4 veces menor.

Valgrind simula cada instrucción simple que ejecuta tu programa. Por esta causa, la herramienta activa chequea, o perfila, no solo el código de tu aplicación sino también el de todas las bibliotecas de enlace dinámico soportadas (formato .so), incluyendo la biblioteca de C de GNU, las bibliotecas del cliente X, Qt, si trabajas con KDE, y así sucesivamente.

Si estás usando una de las herramientas de detección de errores, Valgrind detectará a menudo errores en bibliotecas, por ejemplo las bibliotecas C o X11 de GNU, que tienes que usar. Podrías no estar interesado en estos errores, ya que probablemente no tengas control sobre este código. Por tanto, Valgrind te permite suprimir errores de forma selectiva, grabándolos en un archivo de supresiones que se es leído por Valgrind al comienzo de su ejecución. El mecanismo de construcción intenta seleccionar supresiones que den un comportamiento razonable para las versiones de libc y Xfree86 detectadas en tu máquina. Para hacer más fácil escribir supresiones, puedes usar la opción –gen-supresions=yes que indica a Valgrind que imprima una supresión por cada error que aparezca, la cual puedes copiar luego en un archivo de supresiones.

Las diferentes herramientas de chequeo de error devuelven diferentes clases de errores. El mecanismo de supresión por tanto te permite decir que supresión se aplica a que herramienta(s).

2.1.2. Comenzando

Lo primero de todo, considera si podría ser beneficioso recompilar tu aplicación y bibliotecas de soporte habilitando la información de depuración (el flag -g). Sin información de depuración, lo mejor que las herramientas de Valgrind serán capaces de haces es conjeturar (suponer) a qué función pertenece una pieza particular de código, lo que hace que la información de salida de los mensajes de error y el perfilado carezca de utilidad. Con -g, obtendrás mensajes de error que apuntarán directamente a las líneas de código fuente relevantes.

Otro flag que podría ser útil considerar, si estás trabajando con C++, es -fno-inline. Este hace más fácil ver la cadena de llamadas a función, que puede ayudar a reducir la confusión cuando se navega a través de grandes aplicaciones en C++. Depurar OpenOffice por ejemplo, utilizando Memcheck es un poco más fácil cuando se usa este flag.

Valgrind comprende el viejo formato de depuración “stabs”, usado por las versiones de gcc previas a la 3.1, y el más moderno formato DWARF2 usado por gcc 3.1 y posteriores.

Cuando estés preparado, simplemente ejecuta tu aplicación como lo harías normalmente, pero coloca valgrind –tool=tool_name al principio de la línea de órdenes que invocaría tu aplicación. Ten en cuenta que deberías ejecutar aquí el ejecutable real (código-máquina). Si tu aplicación se lanza a través de, por ejemplo, un script del shell o de perl, necesitarás modificarla para invocar Valgrind sobre los ejecutables reales. Ejecutar tales scripts bajo Valgrind provocará que obtengas informes de error que pertenecen a /bin/sh, /usr/bin/perl, o cualquier interprete que estés usando. Esto puede no ser lo que quieres y puede resultar confuso. Puedes forzar la salida utilizando el flag –trace-children=yes, pero será igualmente confuso.

2.1.3. El comentario

Las herramientas de Valgrind escriben un comentario, un flujo de texto, detallando los informes de error y otros eventos significativos. Todas las líneas del comentario presentan la siguiente forma:

==12345== algún-mensaje-de-Valgrind

El 12345 es el identificador de proceso (PID). Este esquema hace fácil distinguir la salida del programa del comentario de Valgrind, y también facilita la distinción entre comentarios de diferentes procesos que han llegado a mezclarse por cualquier razón.

Por defecto, las herramientas de Valgrind escriben solamente mensajes esenciales en el comentario, para evitar que pierdas el tiempo con información secundaria. Si quieres más información sobre lo que está ocurriendo, ejecuta de nuevo pasándole a Valgrind el flag -v.

Puedes dirigir el comentario a tres lugares diferentes:



Ten en cuenta que desafortunadamente tienes que usar una dirección IP en vez de un nombre de host.

Escribir en un socket de red no tiene ninguna utilidad si no tienes algo escuchando en el otro extremo. La distribución valgrind incluye un simple programa de escucha, valgrind-listener, que acepta conexiones sobre el puerto especificado y copia cualquier cosa que se le envía a stdout.

Valgrind-listener puede aceptar conexiones simultaneas de hasta 50 procesos ejecutándose bajo Valgrind. Enfrente de cada línea de salida imprime el número actual de conexiones activas entre paréntesis.

Valgrind-listener acepta dos flags de línea de órdenes:

-e o –exit-at-zero: cuando el número de procesos conectados cae hasta 0, finaliza (exit). Sin este flag, se ejecutará ininterrumpidamente, es decir, hasta que le envíes la señal Control-C.

Portnumber: cambia el puerto que escucha del valor por defecto (1500). El puerto especificado debe estar en el rango 1024 a 65535. La misma restricción se aplica a los números de puerto especificados por la opción –log-socket= para el mismo Valgrind.



Un punto importante sobre la relación entre el comentario y la salida de perfilado de las herramientas. El comentario contiene una mezcla de mensajes del núcleo de Valgrind y la herramienta seleccionada. Si la herramienta informa de un error, los informes van al comentario. Sin embargo, si la herramienta hace perfilado, los datos de perfilado se escribirán a algún tipo de archivo, dependiendo de la herramienta, independientemente de que opciones –log-* se hayan establecido. El comentario está pensado para ser un canal de bajo ancho de banda legible para los usuarios. Los dados de perfilado, por otra parte, son normalmente voluminosos y faltos de significado sin no se realiza un procesamiento adicional, que es por lo que hemos escogido esta disposición.

2.1.4. Información de errores

Cuando una de las herramientas de chequeo de errores (Memcheck, Addrcheck, Helgrind) detecta que algo va mal en el programa, se escribe un mensaje de error en el comentario. Por ejemplo:

==25832== Invalid read of size 4

==25832== at 0x8048724: BandMatrix::ReSize(int, int, int) (bogon.cpp:45)

==25832== by 0x80487AF: main (bogon.cpp:66)

==25832== by 0x40371E5E: __libc_start_main (libc-start.c:129)

==25832== by 0x80485D1: (within /home/sewardj/newmat10/bogon)

==25832== Address 0xBFFFF74C is not stack'd, malloc'd or free'd



Este mensaje indica que el programa hizo una lectura ilegal de 4-bytes de la dirección 0xBFFFF74C, la cual, tan lejos como Memcheck puede indicar, no es una dirección de pila válida, ni se corresponde con ningún bloque del heap reservado o liberado (malloc'd or free'd). La lectura se produce en la línea 45 de bogon.cpp, llamada desde la línea 66 del mismo archivo, etc. Para errores asociados con un bloque identificado del heap, reservado o liberado, por ejemplo leer memoria liberada, Valgrind devuelve no solo la localización donde ocurrió el error, sino también donde fue reservado/liberado el bloque asociado.

Valgrind recuerda todos los informes de error. Cuando se detecta un error, se compara con informes anteriores, para comprobar si es un duplicado. Si es así, se anota el error, pero no se emite un comentario adicional. Esto evita que seas bombardeado con billones de informes de error duplicados.

Si quieres saber cuantas veces ocurre cada error, ejecuta con la opción -v. Cuando finaliza la ejecución, todos los informes son escritos, junto con y ordenados por sus contadores de ocurrencia. Esto hace fácil comprobar que errores han ocurrido con más asiduidad.

Los errores se indican antes de que la operación asociada ocurra realmente. Si estás usando una herramienta (Memcheck,Addrcheck) que hace chequeo de direcciones, y tu programa intenta leer de la dirección cero, la herramienta emitirá un mensaje a tal efecto, y el programa finalizará con un mensaje de violación de segmento.

En general, deberías tratar y reparar errores en el orden en que son presentados. No seguir este método puede provocar confusión. Por ejemplo, un programa que copia valores no inicializados a varias posiciones de memoria, y después los usa, generará varios mensajes de error cuando sea ejecutado bajo Memcheck. El primer mensaje de error puede dar la pista más directa para la raíz del problema.

El proceso de detectar errores duplicados es bastante costoso y puede llegar a ser una sobrecarga de rendimiento significativa si tu programa genera enormes cantidades de errores. Para evitar problemas con este proceso, Valgrind simplemente parará de recolectar errores después de haber encontrado 300 errores diferentes, o después de haber encontrado 30000 errores. En esta situación lo mejor que puedes hacer es parar tu programa y repararlo, ya que Valgrind no te dirá nada más útil después de esta situación. Ten en cuenta que los límites de 300/30000 se aplican después de descartar los errores suprimidos. Estos límites están definidos en vg_include.h y pueden incrementarse si es necesario.

Para evitar este recorte puedes usar el flag –error-limit=no. Entonces Valgrind siempre mostrará errores, sin tener en cuenta cuantos se produzcan. Usa con cuidado este flag, ya que puede tener un efecto directo sobre el rendimiento.

2.1.5. Supresión de errores

Las herramientas de chequeo de errores detectan numerosos problemas en las bibliotecas base, tales como la biblioteca de C del GNU, y las bibliotecas clientes de Xfree86, las cuales vienen preinstaladas en tu sistema GNU/Linux. Por esto, Valgrind lee una lista de errores a suprimir al comienzo. El script ./configure especifica un archivo de supresión por defecto cuando construye el sistema.

Puedes modificar y añadir a tu elección en el archivo de supresiones, o mejor, escribirlo tú mismo. Se permiten múltiples archivos de supresión. Esto es útil si parte de tu proyecto contiene errores que no puedes o no quieres reparar, y no deseas que se te recuerden continuamente.

Nota: Con mucho la forma más fácil de añadir supresiones es usar el flag –gen-suppressions=yes.

Cada error que va a suprimirse viene descrito muy específicamente, para minimizar la posibilidad de que una directiva de supresión inadvertidamente suprima una serie de errores que deseabas ver. El mecanismo de supresión está diseñado para permitir una especificación flexible aunque precisa de errores a suprimir.

Si usas el flag -v, al final de la ejecución, Valgrind imprime una línea por cada supresión usada, dando su nombre y el número de veces que fué usada. Aquí puedes ver las supresiones usadas al ejecutar: valgrind –tool=memcheck ls -l:

--27579-- supp: 1 socketcall.connect(serv_addr)/__libc_connect/__nscd_getgrgid_r

--27579-- supp: 1 socketcall.connect(serv_addr)/__libc_connect/__nscd_getpwuid_r

--27579-- supp: 6 strrchr/_dl_map_object_from_fd/_dl_map_object



Se permiten muchos archivos de supresiones. Por defecto, Valgrind usa $PREFIX/lib/valgrind/default.supp. Podrías solicitar añadir supresiones desde otro archivo, espedificando –suppressions=/path/to/file.supp.

Si quieres saber más sobre supresiones, consulta un archivo de supresiones ya creado mientras lees la siguiente documentación. El archivo glibc-2.2.supp, en la distribución fuente, proporciona algunos buenos ejemplos.

Cada supresión tiene los siguientes componentes:



Un supresión solo suprime un error cuando el error se corresponde con todos los detalles de la supresión. A continuación puedes ver un ejemplo:

{

__gconv_transform_ascii_internal/__mbrtowc/mbtowc

Memcheck:Value4

fun:__gconv_transform_ascii_internal

fun:__mbr*toc

fun:mbtowc

}

Su significado es: sólo para Memcheck, suprimir el error uso-de-valores-no-inicializados, cuando el tamaño de los datos es 4, cuando ocurra en la función __gconv_transform_ascii_internal, cuando se llama desde cualquier función cuyo nombre se corresponda con __mbr*toc, y cuando se llame desde mbtowc.

Otro ejemplo para la herramienta Memcheck:

{

libX11.so.6.2/libX11.so.6.2/libXaw.so.7.0

Memcheck:Value4

obj:/usr/X11R6/lib/libX11.so.6.2

obj:/usr/X11R6/lib/libX11.so.6.2

obj:/usr/X11R6/lib/libXaw.so.7.0

}

Suprime cualquier error de valor-no-inicializado (unnitialised-value) de tamaño 4 que ocurra en cualquier lugar de libX11.so.6.2 y cuando se llama desde cualquier lugar de la misma biblioteca, cuando se llama desde cualquier lugar de libXaw.so.7.0. La pena es que se especifican de forma inexacta las posiciones, pero es todo lo que se puede esperar teniendo en cuenta que se han eliminado las tablas de símbolos de las las bibliotecas X11 suministradas.

Nota: Aunque en los dos ejemplos anteriores no queda claro, puedes mezclar libremente los estilos de descripción obj: y fun: dentro del mismo registro de supresión.



2.1.6. Soporte para hebras POSIX (Pthreads)

Funciona de la siguiente manera: las aplicaciones multihebradas están (dinámicamente) enlazadas con la biblioteca libpthread.so. Normalmente se instala con tu distribución de Linux. Valgrind, sin embargo, suministra su propia libpthread.so y automáticamente conecta tu programa con esta biblioteca en vez de con la instalada en la distribución.

El hecho es que libpthread.so y Valgrind cooperan para implementar un paquete de hebras en espacio de usuario (hebras usuario). Este enfoque evita los difíciles problemas de implementación que surgirían al implementar una verdadera versión multiprocesador de Valgrind, pero esto significa que las aplicaciones multihebradas se ejecutan sólo en una CPU, aunque tengas una máquina multiprocesador.

Valgrind planifica tus hebras usando un algoritmo round-robin, teniendo todas las hebras la misma prioridad. Intercambia hebras cada 50000 bloques básicos (típicamente alrededor de 300000 instrucciones x86), lo que significa que obtienes un entremezclamiento de ejecución de hebras mucho más fino que cuando se ejecutan de forma nativa. Esto puede causar que tu programa se comporte de forma diferente si tienes algún tipo de “bug” de concurrencia, sección crítica, bloqueo o similar.

Como en la versión 1.0 de Valgrind, el estado de soporte de las pthread es el siguiente:

Mutexes, variables condición, datos específicos de hebra, pthread_once, bloqueos de lector-escritor, semáforos, “cleanup stacks”, cancelación y enlace de hebras funcionando en ese momento.

Actualmente las siguientes llamadas al sistema son “thread-safe” (nonblocking): write, read, nanosleep, sleep, select, poll, recvmsg y accept.

Las señales y las hebras se manejan ahora de manera apropiada. Se implementan pthread_sigmask, pthread_kill, sigwait y raise. Cada hebra tiene su propia máscara de señal, como requiere POSIX.



2.1.7. Limitaciones

Valgrind ejecutará binarios enlazados dinámicamente con formato ELF x86-GNU/Linux, sobre un núcleo 2.4.X o 2.6.X, sujeto a las siguientes restricciones:

No soporta instrucciones 3DNow. Si el traductor las encuentra, Valgrind generará una señal SIGILL cuando la instrucción sea ejecutada.

Se está mejorando el soporte para hebras POSIX (Pthread), pero todavía existen limitaciones significativas en ese apartado. Ten en cuenta que tu programa debe ser enlazado dinámicamente con la biblioteca libpthread.so, para que Valgrind pueda sustituirla por su propia implementación en tiempo de arranque del programa. Si la enlazas de forma estática, las cosas irán mal.



3. Helgrind: un detector de condiciones de carrera de datos

Para usar esta herramienta, debes especificar en la línea de órdenes de Valgrind –tool=helgrind.

Helgrind es una herramienta de Valgrind que sirve para detectar condiciones de competencia de datos en programas escritos en C y C++ que utilicen la biblioteca de hebras POSIX (Pthreads).

Con este fin utiliza el algoritmo Eraser descrito en

Eraser: A Dynamic Data Race Detector for Multithreaded Programs

Stefan Savage, Michael Burrows, Greg Nelson, Patrick Sobalvarro and Thomas Anderson

ACM Transactions on Computer Systems, 15(4):391-411

November 1997.


También se incorporan mejoras significativas del siguiente artículo:

Runtime Checking of Multithreaded Applications with Visual Threads Jerry J. Harrow, Jr.

Proceedings of the 7th International SPIN Workshop on Model Checking of Software

Stanford, California, USA

August 2000

LNCS 1885, pp331--342

K. Havelund, J. Penix, and W. Visser, editors.

Lo que hace Helgrind es básicamente buscar posiciones de memoria que sean accedidas por más de una hebra. Para cada una de estas posiciones, Helgrind registra que bloqueos (locks) del programa, pthread_mutex_locks, fueron levantados (se activaron) por la hebra (accesing thread) en el momento del acceso. La esperanza es descubrir que existe al menos un bloqueo que es utilizado por todas las hebras para proteger esa zona de memoria. Si no se puede encontrar tal bloqueo, entonces aparentemente no existe una estrategia de bloqueo consistente que se esté aplicando sobre esa posición, y por tanto podrían surgir condiciones de competencia.

Helgrind también aboga por “thread segment lifetimes” Si la ejecución de dos hebras no puede solaparse – por ejemplo, si tu hebra principal espera la finalización de otra hebra por medio de la función pthread_join() -- entonces ambas pueden acceder a la misma variable sin levantar un bloqueo.


1 La glibc-2.3.2+, con el paquete NPTL ( Native Posix Threads Library) no funciona.