Hlavní navigace

Valgrind

15. 5. 2003
Doba čtení: 11 minut

Sdílet

Ladit programy je dřina, známe to všichni, že? Programátoři pod Linuxem pravděpodobně budou důvěrně znát gdb, ale víte, jak snadno najít memory leaky ve vašem programu? Toto a mnoho dalších užitečných věcí za vás zjistí valgrind.

Stručný úvod

Valgrind je pružný nástroj pro ladění a profilování spustitelných souborů pro Linux-x86. Skládá se z jádra, které poskytuje virtuální x86 softwarový procesor, a z mnoha nadstaveb, kterým budu říkat skiny (žádný vhodný překlad jsem nenašla, i když ve slovníku jsou krásné výrazy jako třeba držgrešle nebo měch na víno :)), kde každý z nich je ladící nebo profilovací nástroj. Architektura programu je modulární, takže každý nový skin lze lehce začlenit do existující struktury. Poslední (stable) verze je v1.9.6 a byla vypuštěna 5. května 2003. V tomto článku budu mluvit výhradně o této verzi.

Valgrind je distribuován pod licencí GNU General Public License, verze 2. Některé ze souborů pracujících s PThreads (pth_*.c) jsou převzaté z „Pthreads Programming“ od Bradforda Nicholse, Dicka Buttlara a Jacqueline Proulx Farrella).

Součástí základní distribuce je několik užitečných skinů:
memcheck – detekuje chyby související se správou paměti. Poskytuje služby, které jsou stejné jako v sérii valgrind-1.0.X. Je to vlastně valgrind-1.0.X, ze kterého vznikl samostatný skin. Kontrolovány jsou všechny zápisy do paměti, každé čtení z paměti a zachycuje se každé volání funkcí malloc/free/new/de­lete. Výsledkem je, že memcheck dokáže detekovat nasledující problémy:

  • použití neinicializované paměti
  • čtení / zápis do paměti po jejím uvolnění
  • čtení / zápis za konec alokovaného bloku paměti
  • čtení / zápis do nepatřičných oblastí zásobníku
  • memory leaks
  • neshody v použití malloc / new / new[] vs free / delete / delete []
  • špatné použití knihoven POSIX pthreads API

cachegrind je balíček cache profileru od Nicka Nethercota. Provádí detailní simulaci I1, D1 a L2 cache procesoru a může tak přesně určit místa, kde vznikají chyby v použití cache. Je možné nechat si ukázat počet chyb cache, paměťových referencí a instrukcí pro každý řádek zdrojového kódu, se shrnutím pro každou funkci, modul nebo celý program. Dokonce lze zobrazit statistiky pro každou x86 instrukci.

Cachegrind autodetekuje konfiguraci cache pomoci CPUID instrukcí, a tak většinou nepotřebuje žádnou další konfiguraci, aby tyto informace získal.

Pro KDE byl napsán grafický interface pro cachegrind (tento program se jmenuje kcachegrind) a poskytuje grafické a tím pádem přehlednější výstupy.

addrcheck skin je „lehčí“ verze memchecku. Pracuje stejně, až na to, že nekontroluje neicializované hodnoty. Ostatní kontroly (hlavně kontrola adres) jsou identické.

Na jednu stranu je zřejmé, že addrcheck nezachytí chyby „neinicializovaných hodnot“, které memcheck najde, ale na druhou stranu je program asi dvakrát rychlejší, pokud používá jenom addrcheck, a zabírá mnohem méně paměti.

helgrind je nový ladící skin, jehož úkolem je hledat sdílená data ve vícevláknových programech. Helgrind hledá místa v paměti, na která přistupuje víc než jedno (POSIXové) vlákno, ale k nimľ nelze najít příslušný (pthread mutex) lock. Taková místa totiž předznamenávají chybějící synchronizaci mezi vlákny a můžou způsobit těžko nalezitelné problémy, které závisí na časování.

V helgrindu je implementován tzv. „Eraser“ algoritmus na detekci sporných dat s mnohými optimalizacemi, které redukují počet chybných hlášek ve výstupu. Tato funkce je ale teprve ve fázi vývoje.

Dodává se ještě několik menších skinů: corecheck, lackey a none. Samotné balíčky toho moc neumějí, jejich úkolem je spíš ukázat, jak vytvořit jednoduché skiny a pomoci vývojářům valgrindu.

Komentáře

Valgrind vypisuje tzv. komentáře – textové řetězce – které upřesňují chybové výpisy a ostatní významné události. Všechny řádky komentářů mají tvar:

==12345== nějaký_vzkaz_od_valgrindu

12345 je ID číslo procesu. Toto schéma zaručuje jednoduché rozeznání komentářů valgrindu od výstupu laděného pogramu i jiných procesů, které by byly jinak na obrazovce těžko odlišitelné.

Defaultně vypisuje valgrind jenom základní zprávy, aby nezaplavil uživatele nedůležitými poznámkami. Pokud chcete více informací o tom, co se děje, spusťte program znovu s argumentem -v pro valgrind.

Verze 2 valgrindu je mnohem flexibilnější než 1.0.X v tom, že umožňuje uživateli určit, kam bude posílat komentáře. Jsou tři možnosti:

  • defaultně je pošle do file descriptoru (obyčejně je to stderr). Takže pokud jádro nebude mít žádné argumenty, vypíše komentáře na standardní chybový výstup. Pokud to chcete poslat na jiný file descriptor, třeba s číslem 9, musíte to specifikovat pomocí –logfile-fd=9
  • méně intuitivní možnost je psát komentáře do souboru, který určíme argumentem –logfile=název_sou­boru. Zprávy se ale nezapíší do souboru, jehož jmého jste určili, ale do souboru název_souboru­.pid12345, pokud je číslo daného procesu 12345. Tohle se hodí, když valgrindem pouštíme více procesů najednou, protože každý proces si vytvoří vlastní logfile.
  • poslední možností je poslat komentáře do síťového socketu. Socket je specifikován jako IP adresa + číslo portu, např.: –logsocket=192­.168.0.1:12345, pokud chcete poslat výstup na IP 192.168.0.1, port 12345. Číslo portu lze vynechat (–logsocket=192­.168.0.1) , v tom případě je defaultně použitý port 1500. Ve zdrojovém kódu valgrindu je toto číslo definováno konstantou VG_CLO_DEFAUL­T_LOGPORT.

Jádro valgrindu nemůže používat knihovnu GNU C, proto není jednoduché udělat lookup z hostname na IP.

Psaní na síťový socket je zbytečné, pokud na druhé straně někdo posílaná data nečte. K valgrindu se dodává jednoduchý přijímací program – valgrind-listener, který je připojen na specifikovaný port a kopíruje vše, co je posláno do stdout. (Zatím to není zcela bezpečné, odchycení dat je jednoduché, ale počítá se s tím, že brzy bude napsána sofistikovanejší verze přijímacího programu).

Valgrind listener může simultánně přijímat spojení až z 50 procesů valgrindu. Na začátku každé řádky vytiskne v uvozovkách číslo aktivního spojení. Valgrind listener je možné spusit z příkazové řádky se dvěma přepínači:

  • -e nebo –exit-at-zero: Pokud počet připojených procesů klesne zpět na nulu, program se ukončí. Bez tohoto příznaku bude proces běžet, dokud ho nezastavíte stiskem kláves CTRL-C.
  • portnumber: mění port naslouchání z defaultního (1500) na specifikovaný uživatelem. Ten musí být od 1024 do 65535. Stejné omezení platí i pro porty z argumentu –logsocket= u samotného valgrindu.

Pokud proces, který spouští valgrind, neuspěje při pokusu spojit se s listenerem z jakéhokoliv důvodu (listener neběží, nedosažitelný port nebo adresa), valgrind se sám přepne zpátky na vypisování na stderr. To samé se stane, pokud valgrind ztratí navázané spojení s listenerem. Jinými slovy, „smrt“ přijímacího programu neznamená ztrátu dat, která mu proces posílá.

Důležité je pochopení rozdílů mezi komentáři a profilovacími výstupy ze skinů. Komentáře obsahují zprávy z jádra valgrindu i z vybraného skinu. Pokud skin hlásí chyby, vypíše je v komentářích. Pokud daný skin profiluje, profilovací data se zapisují do souboru, který závisí na skinu, ale nezáleží na žádných argumentech –log*. Komentáře by měly být snadno stravitelné, tj. aby nezatěžovaly příliš detailním výpisem. Profilování dat je většinou objemné a postrádá smysl bez důkladnějšího zpracování.

Valgrind je navržen tak, aby byl co nejméně „dotěrný“. Pracuje s už existujícími programy a není potřeba je překompilovávat, znovu slinkovávat nebo nějak jinak modifikovat. Stačí na začátek příkazu, kterým program spouštíme, vložit slovo valgrind a říct mu, jaký skin má použít.

Takže pokud chceme, aby program ls -l použil memcheck, vypadá příkaz takhle:
valgrind –skin=memcheck ls -l
 –skin=parametr říká jádru, jaký skin chceme použít

Aby se zachovala kompatibilita se serií 1.0.X, není potřeba specifikovat skin, defaultně se použije memcheck. Takže příkaz z předešlého odstavce se dá zjednodušit na valgrind ls -l

Bez ohledu na to, který skin se použije, převezme valgrind kontrolu nad programem ještě před jeho spuštěním. Ladící informace se čtou ze spustitelných programů a z příslušných knihoven.

Program pak běží na virtuálním x86 procesoru, který řídí jádro valgrindu. Pokud se program spouští prvně, jádro předá kód vybranému skinu. Skin si k němu přidá svůj vlastní nástrojový kód a výsledek vrátí zpátky jádru, které koordinuje pokračující běh „nového“ kódu.

Množství přidaného kódu se liší a závisí na zvoleném skinu. Na jedné straně je memcheck, který přidává kód na kontrolu každého přístupu do paměti a každé vypočítané hodnoty a tak zvyšuje velikost zdrojového kódu nejméně 12krát a proces běží 25–50krát pomaleji než normálně. Na druhé straně je nejtriviálnější skin none, který nepřidává žádný kód a způsobuje „jenom“ 4násobné zpomalení.

Valgrind simuluje každou instrukci, kterou provádí program. Kvůli tomu aktivní skin kontroluje nebo analyzuje nejenom kód aplikace, ale i všechny dynamicky slinkované knihovny (.so formát) včetně GNU C, knihoven X, Qt, případně KDE, …

Pokud používáte skin na detekci chyb ve vlastním programu, což je nejběžnější, valgrind často najde chyby v knihovnách, které musíte používat, jako například GNU C nebo X11. Právě kvůli tomu poskytuje valgrind možnost potlačit hledání vybraných chyb. Tyto chyby se zaznamenají do souboru, který se načte při spouštění valgrindu. Ten nejdřív zkontroluje, zda odchytání uvedených chyb zaručuje rozumné chování pro verze libc a XFree86, detekované na vašem stroji.

Různé skiny hlásí různé typy chyb. Mechanismus potlačování vám proto dovoluje rozhodnout, které chyby budou jednotlivé skiny potlačovat.

Je docela rozumné si rozmyslet, zda není jednodušší překompilovat vaši aplikaci s knihovnami obsahujícími ladící informace (flag -g). Bez toho může valgrind jenom hádat, které funkci patří daný kód, což produkuje mnoho zmatených chybových výpisů a profilování činí vesměs nepoužitelným. S parametrem -g lze získat hlášky přímo k náležejícím řádkům zdrojového kódu.

Není nutné to dělat, ale zaručíme tím, že valgrind skončí s přesnějšími výsledky. Je možné, že je program už takhle „nastaven“, a to tehdy, pokud byl použit GNU gdb nebo některé další ladící programy.

Valgrind očekává spustitelný program, ne shellovský nebo perl skript, takže je nutné je modifikovat. Jejich přímé spuštění valgrindem by způsobilo oznámení chyby týkající se /bin/sh, /usr/bin/perl, ..

Hlášení chyb

Pokud jeden ze skinů kontrolujících chyby (memcheck, addrcheck, helgrind) něco objeví, vypíše se chybová hláška (jako komentář). Např.:

==25832== Invalid read of size 4
==25832== at 0x8048724: CreateObj:DrawSelf(int, int, int) (gobj.cpp:30)
==25832== by 0x80487AF: main (gobj.cpp:318)
==25832== by 0x40371E5E: __libc_start_main (libc-start.c:129)
==25832== by 0x80485D1: (within /home/zuzka/puzzle/gobj)
==25832== Address 0xBFFFF74C is not stack'd, malloc'd or free'd

Výpis říká, že program provedl chybné načtení 4 bytů z adresy 0×BFFFF74C, která – jak je memcheck schopen zjistit – neodpovídá žádnému mallocovanému bloku nebo bloku uvolněnému pomocí free. Čtení se provádí na řádce 30 v souboru gobj.cpp, volané z řádky 318 stejného souboru (funkce main). U chyb, které vznikly kontrolou bloku u malloc a free, např. čtení z uvolněné paměti, valgrind nehlásí jenom místo, kde se chyba vyskytla, ale také, kde byl daný blok alokován (uvolněn).

Valgrind si pamatuje všechny chybové hlášky. Když se objeví nová chyba, nejdřív se zkontroluje, zda se neopakuje. Pokud ano, chyba se zaznamená, ale nevyšle se žádný podrobný komentář. Tohle zabraňuje vzniku situace, kdy vás program zaplaví spoustou stejných chybových hlášek.

Pokud chcete vědět, kolikrát se každá chyba vyskytla, spusťte valgrind s argumentem -v. Když program proběhne, všechny výpisy se vytisknou, seřazeny podle četnosti výskytu dané chyby. To zjednodušuje hledání nejfrekventova­nějších chyb.

Chyby se hlásí ještě předtím, než se provede daná operace. Pokud používáte skin, který kontroluje adresy (memcheck, addrcheck) a váš program se pokusí číst z adresy 0, skin nejdřív zobrazí chybové hlášení a program pak spadne kvůli segmentation fault.

Většinou by se programátor měl snažit opravit chyby v pořadí, v jakém jsou hlášeny. Pokud to budete dělat jinak, může to být zmatené. Např. program, který kopíruje neinicializované hodnoty do několika paměťových oblasti a později je používá, vygeneruje s použitím skinu memcheck několik chybových hlášek. První takový výpis dává nejpřímější stopu k problému.

Proces detekování duplicitních chyb je celkem pomalý a může výrazně zpomalit běh, pokud program generuje spoustu chyb. Aby se zabránilo těmto problémům, valgrind si jednoduše přestane pamatovat chyby poté, co jich najde alespoň 300 různých nebo celkově přes 30000. V této situaci můžete program zastavit a opravovat chyby, protože valgrind už neřekne nic užitečného. Limit 300/30000 se aplikuje až poté, co se odstraní chyby, které se mají potlačit. Tyto limity jsou definovány v vg_include.h, a pokud je potřeba, lze je zvýšit.

Pokud nechcete mít toto ořezávání chyb, stačí pustit program s parametrem –error-limit=no. Valgrind ukáže všechny chyby, bez ohledu na to, kolik jich je. Používejte to z rozvahou, protože to může mít velký vliv na výkon.

Potlačení chyb

Skiny, které vyhledávají chyby, jich obykle najdou hodně v základních knihovnách, které se dodávají předinstalované (GNU C, XFree86 knihovny…). Ty nemůžete jednoduše opravit, ale zároveň je pravděpodobně nebudete chtít vidět. Proto valgrind hned po spuštění načte seznam chyb, které se mají ignorovat. Defaultně tento soubor vzniká zároveň s instalací valgrindu při konfigurování.

Můžete si dle libovůle přidávat / odebírat záznamy v souboru, nebo (což je ještě lepší) si napsat vlastní soubor. Těchto souborů můžete mít klidně víc. To je užitečné, pokud váš projekt obsahuje chyby, které nechcete či nemůžete opravit nebo je jenom zatím nechcete vidět.

Každá chyba, která se má potlačit, je popsaná velmi specificky, aby se zminimalizovala šance, že tento záznam pokryje i některé podobné chyby, které chceme vidět. Mechanismus potlačování je navržen tak, aby povoloval přesnou a přitom flexibilní specifikaci chyb.

Pokud použijete argument -v, na konci svého běhu vytiskne valgrind jeden řádek pro každé potlačení, přiřadí mu jméno a číslo, které říká, kolikrát bylo použito. Tady je potlačení, které vznikne po spuštění příkazem:

--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

Instalace

Valgrind v1.9.6 lze stáhnout třeba z domovské stránky (bzip).

Používá se standardní mechanismus ./configure, make, make install. Instalace potřebuje kernel verze 2.2 nebo 2.4 a glibc 2.1.X nebo 2.2.X, ale i glibc-2.3.2+ (až do verze valgrindu 1.9.6 to nefungovalo spolehlivě, teď by neměly být žádné problémy s používaním těchto knihoven). K tomu asi nelze (a není třeba) nic víc říct. Nejsou tady žádné optiony, kromě standardního –prefix, který se přidává k ./configure.

root_podpora

Pokud chcete sestavit binární balíček valgrindu pro nějakou distribuci, je nutné si přečíst README_PACKAGERS. Obsahuje několik důležitých informací.

Pokračování příště :)

Byl pro vás článek přínosný?