Sistemi di Calcolo

Corso di Laurea in Ingegneria Informatica e Automatica - A.A. 2017-2018

HomePage | Avvisi | Diario lezioni | Programma | Materiale didattico | Esami | Forum | Login
Esercizio 1 (duplicazione di stringhe)
Eseguendo il programma sotto Valgrind:
$ valgrind ./strdup

==2683== Memcheck, a memory error detector
==2683== Copyright (C) 2002-2013, and GNU GPL d, by Julian Seward et al.
==2683== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==2683== Command: ./strdup
==2683==
binary name: ./strdup
==2683==
==2683== HEAP SUMMARY:
==2683==     in use at exit: 9 bytes in 1 blocks
==2683==   total heap usage: 1 allocs, 0 frees, 9 bytes allocated
==2683==
==2683== LEAK SUMMARY:
==2683==    definitely lost: 9 bytes in 1 blocks
==2683==    indirectly lost: 0 bytes in 0 blocks
==2683==      possibly lost: 0 bytes in 0 blocks
==2683==    still reachable: 0 bytes in 0 blocks
==2683==         suppressed: 0 bytes in 0 blocks
==2683== Rerun with --leak-check=full to see details of leaked memory
==2683==
==2683== For counts of detected and suppressed errors, rerun with: -v
==2683== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

HEAP SUMMARY di Valgrind mostra come il nostro programma in fase di terminazione (exit) risulta avere ancora allocato un blocco da 9 bytes. Infatti, Valgrind identifica che a fronte di un'allocazione, nessuna free() è stata effettuata dal programma. Per questo motivo, il programma presenta un memory leak. Rieseguendo il programma sotto Valgrind ed utilizzando l'opzione --leak-check=full possiamo ottenere ulteriori dettagli riguado al memory leak:

$ valgrind --leak-check=full ./strdup

==3197== Memcheck, a memory error detector
==3197== Copyright (C) 2002-2013, and GNU GPL d, by Julian Seward et al.
==3197== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==3197== Command: ./strdup
==3197==
binary name: ./strdup
==3197==
==3197== HEAP SUMMARY:
==3197==     in use at exit: 9 bytes in 1 blocks
==3197==   total heap usage: 1 allocs, 0 frees, 9 bytes allocated
==3197==
==3197== 9 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3197==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3197==    by 0x4EBF839: strdup (strdup.c:42)
==3197==    by 0x40059A: main (strdup.c:6)
==3197==
==3197== LEAK SUMMARY:
==3197==    definitely lost: 9 bytes in 1 blocks
==3197==    indirectly lost: 0 bytes in 0 blocks
==3197==      possibly lost: 0 bytes in 0 blocks
==3197==    still reachable: 0 bytes in 0 blocks
==3197==         suppressed: 0 bytes in 0 blocks
==3197==
==3197== For counts of detected and suppressed errors, rerun with: -v
==3197== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Il blocco di memoria su cui avviene il leak è stato allocato dalla funzione main(), alla riga 6 del file sorgente C. Tale riga di codice contiene l'invocazione alla funzione strdup(). Per evitare il memory leak è sufficiente effettuare una free() della stringa ottenuta da strdup():

strdup.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // malloc/free

int main (int argc, char* argv[]) {

    // strdup() ritorna una copia della stringa fornita in input
    // tale copia è memorizzata in un buffer ottenuto con malloc()
    // vedere documentazione: man 3 strdup
    char* prog_name = strdup(argv[0]);
    printf("binary name: %s\n", prog_name);

    // deallocazione della stringa ottenuta da strdup()
    free(prog_name);

    return 0;
}


Esercizio 2 (concatenazione di stringhe)
Eseguendo il binario nella shell della VM, otteniamo il seguente output:
$ ./strcat
*** stack smashing detected ***: ./strcat terminated
Final destination string : |This is destinationThis is source|Aborted

Il messaggio stack smashing detected è un indicazione del sistema operativo riguardo ad un accesso non valido sulla stack. Eseguendo il programma sotto Valgrind, otteniamo il seguente output:
$ valgrind ./strcat
==4185== Memcheck, a memory error detector
==4185== Copyright (C) 2002-2013, and GNU GPL d, by Julian Seward et al.
==4185== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==4185== Command: ./strcat
==4185==
*** stack smashing detected ***: ./strcat terminated
Final destination string : |This is destinationThis is source|==4185==
==4185== HEAP SUMMARY:
==4185==     in use at exit: 0 bytes in 0 blocks
==4185==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==4185==
==4185== All heap blocks were freed -- no leaks are possible
==4185==
==4185== For counts of detected and suppressed errors, rerun with: -v
==4185== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Aborted

Putroppo Valgrind non è in grado di fornirci alcun informazione utile riguardo l'accesso non valido alla memoria. Infatti, Valgrind ha un supporto limitato agli errori di accesso alla memoria su stack. Anche l'esecuzione sotto gdb non ci porta molte informazioni:
$ gdb ./strcat
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./strcat...done.
(gdb) run
Starting program: /media/sf_SC1-1617/lab-01/strcat
*** stack smashing detected ***: /media/sf_SC1-1617/lab-01/strcat terminated
Final destination string : |This is destinationThis is source|
Program received signal SIGABRT, Aborted.
0x00007ffff7a4bcc9 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56  ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff7a4bcc9 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007ffff7a4f0d8 in __GI_abort () at abort.c:89
#2  0x00007ffff7a88394 in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7ffff7b9452b "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:175
#3  0x00007ffff7b1fc9c in __GI___fortify_fail (msg=<optimized out>, msg@entry=0x7ffff7b94513 "stack smashing detected") at fortify_fail.c:37
#4  0x00007ffff7b1fc40 in __stack_chk_fail () at stack_chk_fail.c:28
#5  0x000000000040068f in main () at strcat.c:16

Al momento dell'errore, il backtrace (ottenuto con bt) mostra che il programma stava eseguendo la funzione main(), riga 16 del codice sorgente C. Tale riga corrisponde al return del main(). Pertanto, l'unica cosa che possiamo intuire è il che programma crasha in fase di uscita dalla funzione main, ma non abbiamo alcun indizio sulla causa dell'errore. In una fase più avanzata del corso, capiremo come è organizzata la stack e come utilizzare gdb per ispezionare lo stato della stack in queste situazioni al fine di rilevare eventuali errori nell'esecuzione. Per il momento, possiamo incentrare la nostra analisi sul codice sorgente, analizzando in particolare l'effetto di ogni funzione esterna.
strcat.c
#include <string.h>
#include <stdio.h>

#define BUFLEN 16

int main() {
    // buffer s1 e s2 hanno dimensione BUFLEN
    // e sono allocati sulla stack, nel frame
    // della funzione main()
    char s1[BUFLEN], s2[BUFLEN];

    // strcpy(dest, src) prende in input un puntatore (dest) ad un array di char
    // e una stringa (src), e ritorna una copia della stringa,
    // utilizzando l'area di memoria indicata
    // dal puntatore dest. La documentazione di strcpy(), riporta la seguente frase:
    //
    // The  strcpy()  function  copies  the string pointed to by src, including the terminating
    // null byte ('\0'), to the buffer pointed to by dest. The strings may not overlap, and the
    // destination string dest must be large enough to receive the copy.
    //
    // Emerge che s1 deve puntare ad un array sufficientemente grande per
    // ospitare la stringa "This is source". La lunghezza della stringa è 15.
    // Dovendo considerare anche il terminatore di stringa,
    // è necessario che s1 sia almeno 16 chars.
    strcpy(s1,  "This is source");
   
    // sprintf() si comporta in modo analogo a printf() ma scrive il risultato, non nello stdout, ma
    // nel buffer ricevuto in input come primo argomento. Come strcpy() è richiesto che il buffer
    // di destinazione sia sufficientemente grande per ospitare la stringa prodotta. Nel nostro caso,
    // il buffer s2 deve poter ospitare la stringa "This is destination". La lunghezza della stringa è 20.
    // Dovendo considerare anche il terminatore di stringa,
    // è necessario che s2 sia almeno 21 chars. Questo non risulta vero, pertanto occorre
    // ingrandire il buffer s2.
    sprintf(s2, "%s", "This is destination");

    // strcat(dest, src) appende la stringa puntata da src, alla stringa puntata da dest.
    // Pertanto, è richiesto che dest punti ad un array di char sufficientemente grande
    // da ospitare sia "This is source" che "This is destination". Le due stringhe hanno
    // una lunghezza complessiva pari a 35. Considerato il terminatore di stringa,
    // il buffer puntato da s2 deve essere di almeno 36 char. Questo non risulta vero,
    // pertanto occorre ulteriormente ingrandire il buffer s2.
    strcat(s2, s1);

    printf("Final string: |%s|&