Universidad Carlos III de Madrid

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2012 - Enero 2013

3. Actividades

3.1. Detección de anomalías con Valgrind

3.1.2. Plan de trabajo

En esta actividad vamos a ejecutar ficheros que contienen errores de codificación en Valgrind. Estos ficheros se encuentran bajo el directorio Valgrind_errors en vuestro repositorio (con copia local aquí). Para cada uno de los ficheros siguientes, compila con la directiva -g (gcc -Wall -g tufichero.c -o tuejecutable), ejecútalo con Valgrind (valgrind --leak-check=yes ./tuejecutable) y corrige los errores aparecidos, explicando en el mismo fichero (una pequeña explicación de un par de líneas encerrada entre comentarios) qué error se estaba produciendo.

  1. Fichero strcpy_exercise.c: copia una cadena de caracteres en otra.

  2. Fichero printing_exercise.c: copia el valor de un entero a un puntero e imprime la dirección de dicho puntero y su valor recién copiado.

3.1.3. Evaluación

Optionally, subid los ficheros modificados mediante SVN y enséñaselos al profesor para que pueda evaluarlos.

3.2. Valgrind, detector de fugas de memoria

3.2.2. Recursos

  • Guía rápida de Valgrind (secciones 1,2,3,4,5 y 6).

  • Carpeta con nombre Valgrind_first y fichero testing_valgrind.c en la carpeta compartida de Subversion. Tienes una copia local aquí

3.2.3. Plan de trabajo

Para la detección de fugas de memoria en C vamos a utilizar el framework Valgrind. Este framework tiene un conjunto de herramientas entre las que destaca Memcheck (que será en la que nos centremos), que te permite ejecutar un programa en C y ver si en alguna línea de tu código se puede incurrir en una fuga de memoria o algún otro error, advirtiendo así de posibles problemas en la futura ejecución de tu programa.

  1. Lee la guía rápida de Valgrind que se ofrece como recurso.

  2. Abre un terminal de comandos y compila y ejecuta el programa testing_valgrind.c que encontrarás en el directorio Valgrind_first de tu carpeta compartida. Ejecuta de nuevo el programa utilizando Valgrind y analiza el informe sobre el uso de memoria que imprime.

  3. Modifica el programa testing_valgrind.c para que, la ejecución con Valgrind no muestre error alguno. Súbelo corregido a tu repositorio con Subversion.

3.3. Errores detectados por Valgrind

3.3.2. Recursos

3.3.3. Plan de trabajo

Vamos a indagar más sobre los posibles errores que detecta Valgrind y, en concreto, con su herramienta Memcheck.

  1. Lee los tipos de errores que Memcheck detecta utilizando el enlace que se ofrece como recurso. Ignora el error denominado Mismatched use of malloc/new/new [] vs free/delete/delete [], pues este sólo se produce en programas escritos en C++.

  2. Lee los tipos de mensajes de error que pueden aparecer al ejecutar tu programa con Valgrind, utilizando el segundo enlace que se te da como recurso.

  3. Cuando termines de leer ambos documentos realiza un resumen (1 página por una cara), a modo de tabla, de manera que incluya el tipo de error, una descripción breve (un par de líneas) de por qué aparece ese error, los posibles mensajes que puede sacar Valgrind, y un código de ejemplo asociado. Puedes ver un ejemplo en la siguiente imagen:

    Haz el resumen con la hoja orientada en horizontal, para que la tabla se vea mejor. Observa que un mismo tipo de error puede tener varios mensajes asociados, como el de la figura del ejemplo, pero también puede ocurrir que varios tipos de error tengan casi siempre los mismos mensajes (como es el caso de los errores que implican una lectura/escritura inválida). Si no sabes cómo rellenar alguna fila o columna de la tabla, déjala en blanco. Podrás rellenarla a medida que trabajas con Valgrind.

3.4. Ejecución con Valgrind de programas previamente escritos

3.4.2. Recursos

  • Colección particular de programas escritos hasta ahora en la asignatura que hagan operaciones de gestión dinámica de memoria.

3.4.3. Plan de trabajo

  1. Recorre tu colección de programas escritos en C y la de tu compañero hasta ahora en el curso y marca aquellos en los que se incluyen operaciones de gestión dinámica de memoria (malloc, calloc, free o realloc).

  2. Para cada uno de ellos compila y ejecuta de nuevo el programa pero utilizando Valgrind. Sin arreglar ninguna de las anomalías, confecciona una tabla en la que conste esta información para cada programa. Muestra esta tabla al profesor.

  3. Repasa la implementación de cada programa para arreglar las anomalías que se hayan detectado.

3.5. Preguntas finales de autoevaluación

Cuando se ejecuta el siguiente código con Valgrind, obtienes una lista de errores. Responde a las preguntas observando los errores y el código.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
    int main()
    {
      char *p;

      // Allocation #1 of 19 bytes
      p = (char *) malloc(13);

      // Allocation #2 of 12 bytes
      p = (char *) malloc(11);
      free(p);

      // Allocation #3 of 16 bytes
      p = (char *) malloc(16);

      return 0;
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
==7279== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==7295== Memcheck, a memory error detector
==7295== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==7295== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==7295== Command: ./a
==7295== 
==7295== 
==7295== HEAP SUMMARY:
==7295==     in use at exit: 29 bytes in 2 blocks
==7295==   total heap usage: 3 allocs, 1 frees, 40 bytes allocated
==7295== 
==7295== 13 bytes in 1 blocks are definitely lost in loss record 1 of 2
==7295==    at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==7295==    by 0x8048428: main (in /home/gradient/Escritorio/ValgrindErrors/a)
==7295== 
==7295== 16 bytes in 1 blocks are definitely lost in loss record 2 of 2
==7295==    at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==7295==    by 0x8048454: main (in /home/gradient/Escritorio/ValgrindErrors/a)
==7295== 
==7295== LEAK SUMMARY:
==7295==    definitely lost: 29 bytes in 2 blocks
==7295==    indirectly lost: 0 bytes in 0 blocks
==7295==      possibly lost: 0 bytes in 0 blocks
==7295==    still reachable: 0 bytes in 0 blocks
==7295==         suppressed: 0 bytes in 0 blocks
==7295== 
==7295== For counts of detected and suppressed errors, rerun with: -v
==7295== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

  1. Mira el error error Allocation #1 (13 byte leak) y responde.

    • Allocation #1 (13 byte leak) se pierde porque p apunta a algún lugar antes de que la memoria de Allocation #1 sea liberada

    • Allocation #1 (13 byte leak) se pierde porque la petición de memoria de p no es correcta

    • La Allocation #1 (13 byte leak) no se pierde, es la Allocation #2 la que se pierde.

  2. Mira el error error Allocation #3 (16 byte leak) y responde.

    • Allocation #3 (16 byte leak) lista dos memory leaks porque ha detectado dos nuevos errores de memoria.

    • Allocation #3 (16 byte leak) lista dos memory leaks porque se refiere también al memory leak anterior.

    • Allocation #3 (16 byte leak) lista dos memory leaks porque tiene en cuenta el error de memoria del Allocation #2.

  3. Observa el siguiente código. ¿Cuál es el error que resultará si compilamos con Valgrind?

    1
    2
    3
    4
    5
    6
    #include <stdio.h>
              int main()
              {
              int x;
              printf ("x = %d\n", x);
              }

    • Conditional jump or move depends on uninitialised value(s)

    • Invalid free()

    • Syscall param write(buf) contains uninitialised or unaddressable byte(s)