Dziś o narzędziu, które pozwoli Ci poradzić sobie z błędami związanymi z pamięcią, np. zlokalizować wycieki. Czyli zbawienie dla Programisty C++ ;)

Szybki przykład z wyciekiem pamięci

Mamy programik z wyciekiem pamięci:

// program.cpp
void func()
{
	char *str  = new char[10];
}

int main()
{
	func();
	return 0;
}

1. Najpierw kompilujemy  go w trybie debugowania z wyłączoną optymalizacją:

> g++ -g -O0 -o program program.cpp

(Jeśli nie znasz kompilatora g++, zobacz Kompilator g++)

2. Następnie odpalamy go tak jak zwykle, tylko że pod kontrolą Valgrinda:

> valgrind --tool=memcheck --leak-check=full ./program

Flaga -tool=memcheck oznacza, że chcemy użyć narzędzia do debugowania pamięci, a flaga –leak-check=full oznacza, że szczególnie interesują nas informacje na temat wycieków pamięci.

3. W rezultacie dostajemy taki oto komunikat:

==31221== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==31221==    at 0x40057F5: operator new[](unsigned) (vg_replace_malloc.c:195)
==31221==    by 0x8048485: func() (program.cpp:4)
==31221==    by 0x80484A1: main (program.cpp:9)
==31221==
==31221== LEAK SUMMARY:
==31221==    definitely lost: 10 bytes in 1 blocks.
==31221==      possibly lost: 0 bytes in 0 blocks.
==31221==    still reachable: 0 bytes in 0 blocks.
==31221==         suppressed: 0 bytes in 0 blocks.

.

Liczba ==31221== to PID (Process ID) naszego procesu. Na samej górze komunikatu mamy dokładnie wyośloną nazwę modułu, nazwę funkcji i numer linijki powiązanej z naszym wyciekiem pamięci. Czyli wszystko, czego nam trzeba do naprawy błędu :)

Widzimy też, że wyciek zapoczątkowała funkcja main, która wywołała funkcję func, która wywołała operator new[ ].

Piękno narzędzia memcheck polega na tym, że dokładnie wskaże nam źródło problemu z pamięcią, nieważne jak głęboko w kodzie jest ono ukryte! Prawdziwy rocket launcher w walce z błędami pamięci.

Valgrind

Valgrind to cały zestaw narzędzi do profilowania i debugowania aplikacji wraz ze wszystkimi podpiętymi bibliotekami dynamicznymi. Dzięki niemu możesz zwiększyć szybkość działania i zmniejszyć ilość błędów w swoim programie. A jak on działa? Emuluje normalny procesor x86 i bacznie przygląda się temu, co program wyczynia…

Cały pakiet jest standardowo dostarczany z dystrybucjami Linuxa, wiec jeśli masz Linucha – najpewniej masz również Valgrinda gotowego do działania. Zeby to sprawdzić – po prostu wpisz z terminala polecenie: valgrind  –version.

W skład pakietu wchodzi kilka narzędzi: do walki z problemami z pamięcią, z problemami związanymi z wielowątkowością, do profilowania cache’u itp.

Dziś zajmiemy się narzędziem memcheck, które pozwala w łatwy sposób wykryć problemy z pamięcią dowolnego programu!

Memcheck

Dzięki temu narzędziu możemy wykryć:

  • odczyt z niezainicjalizowanej pamięci
  • odczyt/zapis do pamięci, która została uprzednio zwolniona
  • odczyt/zapis poza zaalokowanym blokiem pamięci
  • odczyt/zapis do nieprawidłowych obszarów stosu
  • nieprawidłowe łączenie funkcji typu mallocdelete oraz newfree
  • nakładanie się obszarów pamięci w funkcji memcpy
  • podwójne zwalnianie pamięci
  • i wreszcie – wycieki pamięci

Zeby skorzystać z Valgrinda trzeba skompilować program np. kompilatorem gcc z flagami -g -O0, czyli z dołączeniem symboli do debugowania i wyłączoną optymalizacją. Następnie odpalamy program pod kontrolą memcheck’a:

> valgrind --tool=memcheck --leak-check=full ./program

Tak naprawdę kompilowanie w trybie debugowania wcale nie jest konieczne – Valgrind poradzi sobie z dowolnym, już skompilowanym programem. Wersja debugowalna dostarczy nam jednak więcej informacji w razie wykrycia błędów – np. numer linijki kodu, a wyłączenie optymalizacji pozwoli uniknąć błędnych wyników.

Przydatne flagi Valgrinda i memchecka:

  • -v – tryb rozwlekły drukuje znacznie więcej informacji
  • –help – opis podstawowych opcji Valgrida
  • –leak-check=full – włącza sprawdzania wycieków pamięci (standardowo wyłączone)
  • –leak-resolution=high – nie łączy raportów o kilku wyciekach w jeden wyciek. Stosuj z opcją wyżej
  • –num-callers=10 – “błąd pamięci w funkcji A, którą wywołała funkcja B, którą wywołała funkcja C, którą wywołała …” i tak do dziesięciu wywołań w głąb, zamiast standardowych max czterech
  • –log-file=raport.txt – informacje będą zapisywane do pliku a nie na standardowe wyjście. Do nazwy pliku zostanie dodany PID, a więc ostatecznie plik będzie wyglądał np. tak: raport.txt.31030
  • więcej opcji memcheck’a znajdziesz tutaj: http://valgrind.org/docs/manual/mc-manual.html

Nareszcie!

Koniec teorii. Następnym razem pokażę Ci kilka przykładowych błędów, jakie memcheck może wychwycić, oraz jak są one raportowane.

Related Posts: