Wprowadzenie do Valgrinda Jan Karwowski Wydział Matematyki i Nauk Informacyjnych PW 10 lutego 2015 Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 1 / 33
Valgrind Valgrind jest narzędziem wspomagającym analizę programów, pod kątem potencjalnych błędów i problemów. Może być stosowany do programów w napisanych w C i C++. W rzeczywistości Valgrind nie jest pojedynczym narzędziem, ale zbiorem analizatorów, które można wywołać z użyciem pojedynczego programu valgrind Podstawowy sposób wywołania: $ valgrind --tool=<narzędzie> [opcje]./program [opcje] Gdzie: narzędzie nazwa narzędzia,./program program do przeanalizowania. Valgrind uruchomi podany program i wyświetli możliwe problemy. Dostępne w pakiecie narzędzia pozawalają na: Analizę alokacji, zwalniania, odczytu i zapisu do pamięci Profilowanie (analizę użycia procesora i pamięci) Analizę programów wielowątkowych Dalej omówione są możliwości dostarczane przez narzędzie memcheck. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 2 / 33
Problem 1: (nie)zwalnianie pamięci I Rozważmy następujący program: 1 #i n c l u d e < s t d l i b. h> 2 3 i n t main ( i n t argc, char argv ) { 4 i n t i ; 5 v o i d p t r ; 6 7 f o r ( i =0; i <20; i ++) { 8 p t r = m a l l o c (10 s i z e o f ( i n t ) ) ; 9 } 10 11 f r e e ( p t r ) ; 12 r e t u r n 0 ; 13 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 3 / 33
Problem 1: (nie)zwalnianie pamięci II Program pozostawia 19 niezwolnionych bloków pamięci w momencie powrotu z funkcji main. Przykład 1 Kompilacja programu i uruchomienie Valgrinda $ gcc -O0 -ggdb leak1.c -o leak1 $ valgrind./leak1 Należy pamiętać aby skompilować program z symbolami debugowania. Memcheckjest Valgrinda, w związku z czym można pominąć opcję --tool Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 4 / 33
Problem 1: (nie)zwalnianie pamięci III Przykład 2 Możliwe wyjście ==215== Memcheck, a memory error detector ==215== Copyright (C) 2002-2013, and GNU GPL d, by Julian Seward et al. ==215== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info ==215== Command:./problem1 ==215== ==215== ==215== HEAP SUMMARY: ==215== in use at exit: 760 bytes in 19 blocks ==215== total heap usage: 20 allocs, 1 frees, 800 bytes allocated ==215== ==215== LEAK SUMMARY: ==215== definitely lost: 760 bytes in 19 blocks ==215== indirectly lost: 0 bytes in 0 blocks ==215== possibly lost: 0 bytes in 0 blocks ==215== still reachable: 0 bytes in 0 blocks ==215== suppressed: 0 bytes in 0 blocks ==215== Rerun with --leak-check=full to see details of leaked memory ==215== ==215== For counts of detected and suppressed errors, rerun with: -v ==215== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 5 / 33
Problem 1: (nie)zwalnianie pamięci IV W powyższym wyjściu można zauważyć dwie ważne informacje: definitely lost: 760 bytes in 19 blocks Rerun with leak-check=full to see details of leaked memory Pierwsza informacja mówi, że nie zostało zwolnionych 19 bloków z malloc lub pochodnych. W sumie 760 bajtów. Druga informacja to sugestia uruchomienia narzędzia z opcją wykonującą dodatkową analizę. Po uruchomieniu z dodatkową opcją: $ valgrind --leak-check=full./leak1 Otrzymujemy wynik zawierający informacje o miejscu alokacji niezwolnionych bloków. ==216== 760 bytes in 19 blocks are definitely lost in loss record 1 ==216== at 0x4C28C20: malloc (vg_replace_malloc.c:296) ==216== by 0x400567: main (in/home/jan/git/unix/valgrind/leak1) Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 6 / 33
Problem 1: (nie)zwalnianie pamięci V Uwaga! W tym miejcu wykorzystana są informacje debugowe włączone w trakcie kompilacji. Ćwiczenie 1 Informacje dostępne bez symboli debugowych Jaka będzie różnica w wyjściu valgrinda, gdy program skompilujemy bez opcji -ggdb? Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 7 / 33
Wskaźniki globalne I Abstrahując od faktu, że stosowanie zmiennych globalnych jest złą praktyką, rozważmy program: 1 / problem6. c / 2 #i n c l u d e < s t d l i b. h> 3 4 v o i d b u f f 1 ; 5 6 i n t main ( i n t argc, char argv ) { 7 v o i d b u f f 2 ; 8 9 b u f f 1=m a l l o c ( 1 0 2 4 ) ; 10 b u f f 2=m a l l o c ( 1 0 2 4 ) ; 11 12 r e t u r n 0 ; 13 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 8 / 33
Wskaźniki globalne II Przykład 3 Analiza programu $ valgrind --leak-check=full./leak2 LEAK SUMMARY: definitely lost: 1,024 bytes in 1 blocks indirectly lost: 0 bytes in 0 blocks possibly lost: 0 bytes in 0 blocks still reachable: 1,024 bytes in 1 blocks suppressed: 0 bytes in 0 blocks Reachable blocks (those to which a pointer was found) are not shown. To see them, rerun with: --leak-check=full --show-leak-kinds=all Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 9 / 33
Wskaźniki globalne III Bloki pamięci, do których istnieje wskaźnik po zakończeniu funkcji main, są domyślnie traktowane jako niestracone. Aby wydrukować szczegóły dotyczące tych bloków, należy użyć dodatkowej opcji Valgrinda. Ćwiczenie 2 Opcja --show-leak-kinds=all Uruchomić analizę ze sprawdzeniem wszystkich możliwych wycieków pamięci i porównać wyniki. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 10 / 33
Bloki pośrednio utracone I Prosty program implementujący listę: 1 / l i s t a. c / 2 #i n c l u d e < s t d l i b. h> 3 4 t y p e d e f s t r u c t node { 5 s t r u c t node next ; 6 i n t v a l u e ; 7 } node ; 8 9 node c r e a t e n o d e ( i n t value, node next ){ 10 node n = m a l l o c ( s i z e o f ( node ) ) ; 11 n >next=next ; 12 n >v a l u e=v a l u e ; 13 r e t u r n n ; 14 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 11 / 33
Bloki pośrednio utracone II 15 16 i n t main ( i n t argc, char argv ){ 17 node head=c r e a t e n o d e ( 1, c r e a t e n o d e ( 2,NULL ) ) ; 18 head=null ; 19 20 r e t u r n 0 ; 21 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 12 / 33
Bloki pośrednio utracone III Przykład 4 Utrata całych łańcuchów wskaźników $ valgrind --leak-check=full./lista HEAP SUMMARY: in use at exit: 32 bytes in 2 blocks total heap usage: 2 allocs, 0 frees, 32 bytes allocated 32 (16 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2 at 0x4C28C20: malloc (vg_replace_malloc.c:296) by 0x40051E: create_node (lista.c:10) by 0x400568: main (lista.c:17) LEAK SUMMARY: definitely lost: 16 bytes in 1 blocks indirectly lost: 16 bytes in 1 blocks possibly lost: 0 bytes in 0 blocks still reachable: 0 bytes in 0 blocks suppressed: 0 bytes in 0 blocks Valgrind informuje, że do pewnych bloków pamięci istnieją wskaźniki, ale te wskaźniki znajdują się w obszarach pamięci, które zostały utracone. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 13 / 33
Bloki pośrednio utracone IV NB Na systemie 64-bitowym, gdzie sizeof(int)=4 i sizeof(void*)=16 rozmiar struktury node wyniósł 16 bajtów. Dlaczego? Ćwiczenie 3 Pośrednia utrata pamięci Napisać program w którym nastąpi pośrednia utrata fragmentu pamięci. Kod alokujący blok pośrednio utracony ma się znaleźć w innym miejscu programu niż kod alokujący blok utracony bezpośrednio. Porównać wyniki analizy z przykładem. Ćwiczenie 4 Pośrednia utrata pamięci Napisać podobny przykład ze strukturą listy cyklicznej. Porównać wyniki analizy z przykładem. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 14 / 33
Bloki potencjalnie utracone I W analizie poprzednich przykładów można zauważyć jeszcze jedną linię opisującą liczbę utraconych bloków: possibly lost: 0 bytes in 0 blocks Przykład programu: 1 / a r r a y. c / 2 #i n c l u d e < s t d l i b. h> 3 4 i n t a r r a y ; 5 6 i n t main ( i n t argc, char argv ) { 7 a r r a y = m a l l o c (10 s i z e o f ( i n t ) ) ; 8 i n t i ; 9 s r a n d ( 1 4 1 0 ) ; 10 f o r ( i =0; i <5; i ++) { Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 15 / 33
Bloki potencjalnie utracone II 11 ( a r r a y++)=rand ( ) ; 12 } 13 r e t u r n 0 ; 14 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 16 / 33
Bloki potencjalnie utracone III Przykład 5 Bloki potencjalnie utracone HEAP SUMMARY: in use at exit: 40 bytes in 1 blocks total heap usage: 1 allocs, 0 frees, 40 bytes allocated 40 bytes in 1 blocks are possibly lost in loss record 1 of 1 at 0x4C28C20: malloc (vg_replace_malloc.c:296) by 0x4005AF: main (array.c:6) LEAK SUMMARY: definitely lost: 0 bytes in 0 blocks indirectly lost: 0 bytes in 0 blocks possibly lost: 40 bytes in 1 blocks still reachable: 0 bytes in 0 blocks suppressed: 0 bytes in 0 blocks Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 17 / 33
Bloki potencjalnie utracone IV Bloki potencjalnie utracone, to bloki to których istnieje wskaźnik do jakiegoś z bajtów, ale nie istnieje wskaźnik na początek bloku. W rzeczywistych programach taki problem należy traktować tak samo jak bloki całkowicie utracone. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 18 / 33
Nadzwyczajne zakończenie programu I Wszystkie pokazane wcześniej przykłady kończyły się zwróceniem wartości z funkcji main. Program może również zostać zakończony z użyciem funkcji exitlub poprzez sygnał dla którego zostało ustawione takie zachowanie. 1 / e x i t. c / 2 #i n c l u d e < s t d l i b. h> 3 4 i n t main ( i n t argc, char argv ){ 5 i n t tab ; 6 tab = m a l l o c (100 s i z e o f ( i n t ) ) ; 7 8 e x i t ( 0 ) ; // tu konczy s i e program 9 r e t u r n 0 ; 10 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 19 / 33
Nadzwyczajne zakończenie programu II Przykład 6 Zakończenie z użyciem exit() HEAP SUMMARY: in use at exit: 400 bytes in 1 blocks total heap usage: 1 allocs, 0 frees, 400 bytes allocated 400 bytes in 1 blocks are still reachable in loss record 1 of 1 at 0x4C28C20: malloc (vg_replace_malloc.c:296) by 0x40055E: main (exit.c:5) LEAK SUMMARY: definitely lost: 0 bytes in 0 blocks indirectly lost: 0 bytes in 0 blocks possibly lost: 0 bytes in 0 blocks still reachable: 400 bytes in 1 blocks suppressed: 0 bytes in 0 blocks Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 20 / 33
Nadzwyczajne zakończenie programu III Wszystkie bloki, do których znajdowały się wskaźniki na stosie w momencie zakończenia programu są oznaczone osiągalne. Uwaga W trakcie oceny programu na laboratorium UNIX rozróżniamy kończenie programu w dwóch sytuacjach: Błąd krytyczny W tym przypadku dopuszczalne jest pozostawienie zaalokowanej pamięci dostępnej ze stosu. Zakończenie wynikające z wymagań zadania Na przykład zakończenie programu poprzez użycie C-c. W tym przypadku program powinien zostać poprawnie zamknięty, w szczególności powinny zostać zwolnione wszystkie bloki pamięci, zamknięte wszystkie pliki i połączenia. Nie jest dopuszczalne pozostawienie bloków still reachable w takiej sytuacji. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 21 / 33
Odczyt niezainicjowanej pamięci I Standard języka C nie daje żadnych gwarancji co do zawartości komórek pamięci. Zarówno zmiennych na stosie, jak i bloków uzyskanych z malloc. 1 / read1. c / 2 3 i n t main ( i n t argc, char argv ) { 4 i n t v a l ; 5 6 i f ( v a l == 2) 7 r e t u r n 0 ; 8 e l s e 9 r e t u r n 1 ; 10 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 22 / 33
Odczyt niezainicjowanej pamięci II Przykład 7 Odczyt niezainicjowanej pamięci Conditional jump or move depends on uninitialised value(s) at 0x4004C5: main (read1.c:6) Ćwiczenie 5 Przekazywanie niezainicjowanych wartości dalej Valgrind wykrywa użycie niezainicjowanej pamięci dopiero, gdy jest ona faktycznie potrzebna. (Kopiowanie niezainicjowanej wartości do innej zmiennej lub przekazanie jej jako argument funkcji takim użyciem nie jest). Napisać program, który przekazuje niezainicjowane wartości do funkcji bibliotecznej (np. printf, qsort). Kiedy problem jest raportowany przez Valgrinda? Sprawdzić opcję --track-origins=yes Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 23 / 33
Odczyt niezainicjowanej pamięci III Ćwiczenie 6 Przekazywanie niezainicjowanych buforów do wywołań systemowych Napisać program, który przekazuje niezainicjowany bufor do funkcji write. Porównać wyjście valgrinda z poprzednim przykładem. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 24 / 33
Dostęp do nieprzydzielonej pamięci I Valgrind potrafi wykryć część przypadków dostępu do niezaalokowanej pamięci, które nie powodują przerwania programu. Konkretnie dotyczy to fragmentów, które zostały zmapowane do przestrzeni adresowej procesu, ale nie zostały przydzielone żadną funkcją alokującą pamięć. 1 / b a d a d d r e s s. c / 2 #i n c l u d e < s t d l i b. h> 3 4 i n t main ( i n t argc, char argv ) { 5 i n t a1 = m a l l o c (5 s i z e o f ( i n t ) ) ; 6 a1 [ 6 ] = 5 ; 7 f r e e ( a1 ) ; 8 r e t u r n 0 ; 9 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 25 / 33
Dostęp do nieprzydzielonej pamięci II Przykład 8 Odczyt poza zakresem tablicy $ valgrind./bad-address Invalid write of size 4 at 0x40052B: main (bad-address.c:6) Address 0x51de058 is 4 bytes after a block of size 20 alloc at 0x4C28C20: malloc (vg_replace_malloc.c:296) by 0x40051E: main (bad-address.c:5) Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 26 / 33
Dostęp do nieprzydzielonej pamięci III Ćwiczenie 7 Przekroczenie zakresu tablicy na stosie Niestety Valgrind nie kontroluje zakresów pamięci przy odwołaniach do stosu. Sprawdzić poniższy przykład: 1 / b a d a d d r e s s. c / 2 #i n c l u d e < s t d l i b. h> 3 4 i n t main ( i n t argc, char argv ) { 5 i n t a1 [ 5 ] ; 6 a1 [ 5 ] = 5 ; 7 r e t u r n 0 ; 8 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 27 / 33
Kontrola otwartych plików I 1 / f i l e. c / 2 #i n c l u d e <s y s / s t a t. h> 3 #i n c l u d e < f c n t l. h> 4 5 i n t main ( i n t argc, char argv ) { 6 i n t f d=open ( / dev / z e r o, O RDONLY ) ; 7 r e t u r n 0 ; 8 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 28 / 33
Kontrola otwartych plików II Przykład 9 Sprawdzenie otwartych deskryptorów $ valgrind --track-fds=yes./file ILE DESCRIPTORS: 4 open at exit. Open file descriptor 3: /dev/zero at 0x4F0E050: open_nocancel (syscall-template.s:81) by 0x400528: main (file.c:6) Open file descriptor 2: /dev/pts/15 <inherited from parent> Open file descriptor 1: /dev/pts/15 <inherited from parent> Open file descriptor 0: /dev/pts/15 <inherited from parent> Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 29 / 33
Kontrola otwartych plików III W poprawnym programie powinny pozostać tylko 3 otwarte deskryptory plików stdin (0), stdout(1), stderr(2). Analiza dotyczy wszystkich deskryptorów, włączając w to pliki, FIFO, sockety. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 30 / 33
Programy wieloprocesowe I 1 / b a d a d d r e s s. c / 2 #i n c l u d e < s t d l i b. h> 3 #i n c l u d e <u n i s t d. h> 4 5 i n t main ( i n t argc, char argv ) { 6 i n t a1 = m a l l o c (5 s i z e o f ( i n t ) ) ; 7 f o r k ( ) ; f o r k ( ) ; 8 a1 [ 6 ] = 5 ; 9 f r e e ( a1 ) ; 10 r e t u r n 0 ; 11 } Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 31 / 33
Programy wieloprocesowe II Ćwiczenie 8 Badanie procesów potomnych Uruchomić Valgrinda dla powyższego programu. Zwrócić uwagę, że każda linia jest poprzedzona numerem procesu, który powoduje problem. W przypadku, gdy programu używa również funkcji exec, konieczne jest użycie dodatkowej opcji Valgrinda, aby go śledzić. Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 32 / 33
Podsumowanie Podstawowy zestaw opcji, który będzie używany przy do zadań sprawdzanych na miejscu, w trakcie zajęć, to: $ valgrind --leak-check=full \ --show-leak-kinds=all --track-fds=yes Jan Karwowski (MiNI) Wprowadzenie do Valgrinda 10 lutego 2015 33 / 33