Philip Withnall philip.withnall@collabora.co.uk 2015 Použití správných nástrojů pro různé úkoly Nástroje

Vývojářskými nástroji nejsou jen textový editor a kompilátor. Správné použití správných nástrojů může, mimo jiné, významně usnadnit ladění a sledování složitých problémů s přidělováním paměti a systémovým voláním. Některé z nejběžněji používaných nástrojů jsou popsány dále. Ostatní existující nástroje jsou povětšinou pro specializovanější situace a měly by být použity, když je to vhodné.

Obecný princip, který platí při vývoji, je mít vždy povoleno tolik ladicích voleb, kolik je jen možné, a ne je mít vypnuté až do doby těsně před vydáním. Tím že se kód neustále testuje všemi dostupnými ladicími nástroji, zachytíte chyby již v zárodku, dřív než se zakoření hluboko do kódu a bude těžší je odstranit.

Prakticky to znamená, mít zapnutá varování u kompilátoru a všech dalších nástrojů a zapnout selhání procesu sestavení s chybou při jejich výskytu.

Shrnutí

Kompilujte často pomocí druhého kompilátoru. ()

Povolte co největší počet varování kompilátoru a nastavte je jako kritická. ()

Používejte GDB k ladění a krokování kódu. ()

Používejte Valgrind k analýze využití paměti, paměťových chyb, výkonu mezipaměti a procesoru a chyb s vlákny. ()

Používejte gcov a lcov k pokrytí analytickými jednotkovými testy. ()

Používejte sanitizéry k analýze problémů s pamětí, vlákny a nedefinovaným chováním. ()

Kontrolu pomocí Coverity provádějte jako pravidelnou úlohu a chyby statické analýzy odstraňte hned, jak se objeví. ()

Používejte pravidelně statický analyzátor Clang a Tartan k odstranění místních chyb, které lze analyzovat staticky. ()

GCC a Clang

GCC je standardní kompilátor pro Linux. Existuje i alternativa v podobě kompilátoru Clang s obdobnou funkcionalitou. Jeden z nich (nejspíše GCC) si zvolte jako hlavní, ale příležitostně provádějte kompilaci kódu i druhým, abyste objevili trochu jinou skupinu chyb a varování v kódu. Clang dáva k dispozici také nástroj pro statickou analýzu, který můžete použít k odhalení chyb v kódu bez jeho kompilace nebo spuštění. Viz .

Oba dva kompilátory by se měly používat s co nejvíce zapnutými přepínači varování je možné. Přestože kompilátor může příležitostně poskytnout falešné varování, většina varování oprávněně ukazuje na problém v programovém kódu a proto by měla být opravena, a ne ignorována. Zásadou pro vývojáře je zapnout co nejvíce přepínačů varování a rovněž zadat -Werror (ten způsobí, že všechna varování způsobí selhání kompilace), aby byli přinuceni opravit varování hned, jak se objeví. To pomáhá zvyšovat kvalitu kódu. Naopak ignorování varování vede ve výsledků ke zdlouhavému ladění kvůli vyhledání chyby, která je způsobená problémem, který byl již dříve označen varováním. Stejně tak, když budete ignorovat varování až do konce vývojového cyklu, strávíte násobně více času jejich opravami na závěr.

Jak GCC, tak Clang, podporují velké množství přepínačů, ale jen některé z nich se vztahují k modernímu víceúčelovému kódu (ostatní jsou například zastaralé nebo závislé na architektuře). Najít vhodnou sadu přepínačů, které je třeba zapnout, může být trochu magie, a proto existuje makro AX_COMPILER_FLAGS.

AX_COMPILER_FLAGS zapíná jednotnou sadu varování kompilátoru a také, před tím než je zapne, jednotlivě testuje, jestli je kompilátor podporuje. Vyrovnává se tím s rozdíly mezi překladači GCC a Clang v sadě podporovaných přepínačů. Pro použití stačí přidat AX_COMPILER_FLAGS do configure.ac. Pokud používáte kopii maker autoconf-archive ve stromu se zdrojovými kódy, zkopírujte ax_compiler_flags.m4 do složky m4 ve svém projektu. Mějte na paměti, že závisí na následujících makrech z autoconf-archive, která jsou pod licenci GPL, takže existuje možnost, že z licenčních důvodů je nebudete moci nakopírovat. Pak je budete muset ponechat v autoconf-archive, který pak bude závislostí pro sestavovací proces projektu:

ax_append_compile_flags.m4

ax_append_flag.m4

ax_check_compile_flag.m4

ax_require_defined.m4

AX_COMPILER_FLAGS podporuje vypnutí -Werror u sestavení pro vydání, takže tato vydání mohou být vždy sestavena oproti novějšímu kompilátoru, který může mít zavedeno více varování. Pro zapnutí této funkce nastavte u sestavení pro vydání (a jen u nich) jeho třetí parametr na „yes“. Vývojová sestavení a sestavení průběžné integrace by měla mít přepínač -Werror vždy zapnutý.

Sestavení pro vydání lze detekovat pomocí makra AX_IS_RELEASE, jehož výsledek můžete předat přímo do AX_COMPILER_FLAGS:

AX_IS_RELEASE([git]) AX_COMPILER_FLAGS([WARN_CFLAGS],[WARN_LDFLAGS],[$ax_is_release])

Volba zásad pro stabilitu vydání (první argument pro AX_IS_RELEASE) by měla být prováděna pro každý projekt zvlášť, přičemž je třeba brát do úvahy číslování verzí podle stability.

GDB

GDB je standardní ladicí program pro C v Linuxu. Je nejběžněji používán k ladění pádů a ke krokování spuštěného kódu. Ucelený průvodce použitím GDB je poskytnut zde.

Ke spuštění GDB vůči programu uvnitř stromu jeho zdrojových kódů použijte: libtool exec gdb --args ./nazev-programu --nejake --jeho --argumenty

Takto je to nutné z důvodu, aby libtool obalila ve stromu zdrojových kódů každý zkompilovaný binární soubor do shellového skriptu, který nastaví některé proměnné pro libtool. Není to zapotřebí pro ladění nainstalovaných spustitelných souborů.

GDB má spoustu pokročilých funkcí, které lze kombinovat, abyste v podstatě vytvořili malé ladicí skripty, které se spouští v různých bodech přerušení v kódu. Někdy se to opravdu hodí (například pro ladění počítání odkazů), ale jindy je jednodušší použít prostě samotné g_debug() pro výpis ladicích zpráv.

Valgrind

Valgrind je sada nástrojů pro měření a profilování programů. Jeho nejznámějším nástrojem je memcheck, ale má i několik dalších mocných a užitečných nástrojů. Jednotlivě se jim věnují následující oddíly.

Vhodným způsobem, jak spustit Valgrind, je spustit pod ním sadu jednotkových testů programu a nastavit jej, aby vrátil stavový kód sdělující množství napočítaných chyb. Když běží jako součást make check, způsobí to, že je kontrola úspěšná, když Valgrind nenajde žádný problém a selže v opačném případě. Avšak spuštění make check pod Valgrind není jednoduché provést v příkazové řádce. Můžete použít makro AX_VALGRIND_CHECK, které přidá nový cíl make check-valgrind pro zautomatizování. Použijte jej následovně:

Zkopírujte ax_valgrind_check.m4 do složky m4/ ve svém projektu.

Přidejte AX_VALGRIND_CHECK do configure.ac.

Přidejte @VALGRIND_CHECK_RULES@ do Makefile.am v každé složce, která obsahuje jednotkové testy.

Když je spuštěno make check-valgrind, uloží se jeho výsledky do test-suite-*.log, jeden soubor se záznamem pro každý z nástrojů. Nezapomeňte, že jej musíte spustit ve složce, která obsahuje jednotkové testy.

Valgrind má způsob, jak potlačit falešná hlášení pomocí potlačovacích souborů. V nich jsou uvedeny vzory, které mohou odpovídat výpisům chyb zásobníku. Pokud výpis zásobníku pro nějakou chybu odpovídá části potlačovacího záznamu, není nahlášen. Z jistých důvodů GLib v současnosti způsobuje řadu falešných hlášení v nástrojích memcheck a helgrind a drd, která je potřeba standardně potlačovat, aby byl Valgrind vůbec použitelný. Proto by měl každý projekt mimo svého vlastního potlačovacího souboru používat i standardní potlačovací soubor pro GLib.

Potlačovací soubory jsou podporovány pomocí makra AX_VALGRIND_CHECK:

@VALGRIND_CHECK_RULES@ VALGRIND_SUPPRESSIONS_FILES = my-project.supp glib.supp EXTRA_DIST = $(VALGRIND_SUPPRESSIONS_FILES)
memcheck

memcheck je analyzátor využití a přidělování paměti. Zjišťuje problémy s přístupem k paměti a změnami v haldě (přidělování a uvolňování). Jedná se o robustní a zaběhnutý nástroj a jeho výstupům se dá vcelku důvěřovat. Když oznámí, že se „určitě“ jedná o únik paměti, tak se určitě jedná o únik paměti, který byste měli opravit. Když řekne, že se jedná o „potenciální“ únik paměti, možná jde o únik, který byste měli opravit, nebo může jít o přidělení paměti při počáteční inicializaci, které je pak používána po celou dobu běhu programu a není potřeba ji uvolňovat.

K ručnímu spuštění memcheck vůči nainstalovanému programu použijte:

valgrind --tool=memcheck --leak-check=full název-vašeho-programu

Nebo, když spouštíte svůj program ze složky se zdrojovými kódy, použijte následující, abyste se vyhnuli kontrole úniků v pomocných skriptech libtool:

libtool exec valgrind --tool=memcheck --leak-check=full ./název-vašeho-programu

Valgrind vypíše všechny zjištěné problémy s pamětí, včetně krátkého výpisu zásobníku volání u každého z nich (za předpokladu, že jste program zkompilovali s ladicími symboly), díky čemuž lze paměťovou chybu dohledat a opravit.

Ucelený průvodce použitím nástroje memcheck je zde.

cachegrind a KCacheGrind

cachegrind je profilovací nástroj výkonu mezipaměti, který může měřit také provádění instrukcí a je tak velmi užitečný i pro profilování obecného výkonu programu. KCacheGrind je pak pro něj šikovné uživatelské rozhraní, které provádí grafické znázornění a můžete v něm zkoumat profilovací data. Tyto dva nástroje se tak obvykle používají dohromady.

cachegrind funguje pomocí simulace hierarchie paměti procesoru, takže existují situace, kdy není zcela přesný. Jeho výsledky jsou však vždy dostatečně reprezentativní, aby byly užitečné pro ladění výkonu v dotčených místech.

Ucelený průvodce používáním nástroje cachegrind je zde.

helgrind a drd

helgrind a drd slouží ke zjišťování chyb týkajících se vláken, ke kontrole souběhových chyb v přístupu k paměti a chybného používání API vláken podle standardu POSIX. Jedná se o dva podobné nástroje, ale používající ke své práci rozdílné techniky, takže by se měly používat oba.

Druhy chyb detekované nástroji helgrind a drd jsou: přístup k datům z více vláken bez jednotného zamykání, změny v pořadí získávání zámků, uvolňování zamknutého mutexu, zamykání zamknutého mutexu, odemykání odemčeného mutexu a několik dalších chyb. Každá chyba, která je objevena, je vypsána do konzole v podobě drobné výstupní sestavy s oddělenu částí poskytující podrobnosti o alokaci nebo vzniku mutexů nebo vláken, se kterými to souvisí, takže pak můžete snadno najít jejich definici.

helgrind a drd produkují více falešných hlášení, než memcheck a cachegrind, takže jejich výstupy je třeba prostudovat trochu obezřetněji. Problémy s vlákny jsou ale notoricky těžko polapitelné i pro zkušené programátory, takže tyto nástroje by neměly být jen tak zavrhnuty.

Ucelení průvodci používáním helgrind a drd jsou zde a zde.

sgcheck

sgcheck provádí kontrolu rozsahu polí. Umí odhalit přístup do pole, při kterém dojde k překročení délky pole. Jedná se ale o velmi raný nástroj, zatím označený jako experimentální, a proto může produkovat více falešných hlášení než jiné nástroje.

Protože je experimentální, musí být spuštěn s předáním --tool=exp-sgcheck do Valgrindu, namísto --tool=sgcheck.

Ucelený průvodce použitím sgcheck je zde.

gcov a lcov

gcov je profilovací nástroj vestavěný v GCC, který během kompilace obohatí programový kód přidáním doplňujících instrukcí. Když je pak program spuštěn, generuje výstupní profilovací soubory .gcda a .gcno. Tyto soubory lze analyzovat pomocí nástroje lcov, který generuje přehlednou výstupní sestavu pokrytí kódu za běhu, v níž zvýrazní řádky kódu, které běží déle než ostatní.

Kritické využití pro nasbíraná data o pokrytí kódu je při běhu jednotkových testů: když je množství kódu pokrytého jednotkovými testy (např. které konkrétní řádky proběhly) známé, může být použito jako vodítko pro další rozšiřování jednotkových testů. Pravidelnou kontrolou pokrytí kódu získaného jednotkovými testy a jeho dalším rozšířením na 100 % můžete zajistit, že bude testován celý projekt. Častým případem je, že jednotkové testy zkouší většinu kódu, ale ne konkrétně cestu řízeného průchodu, ve které se pak ukrývá zbytek chyb.

lconv podporuje měření pokrytí větve, takže není vhodný jako ukázkové pokrytí kritického kódu z hlediska bezpečnosti. Je naopak perfektně vhodný pro kód, který není z hlediska bezpečnosti kritický.

Protože pokrytí kódu musí být povoleno jak při kompilaci, tak za běhu, je pro zjednodušení poskytováno makro. Makro AX_CODE_COVERAGE přidává do sestavovacího systému cíl make check-code-coverage, který spustí jednotkové testy s povoleným pokrytím kódu a vygeneruje výstupní sestavu pomocí lcov.

Když chcete do svého projektu přidat podporu pro AX_CODE_COVERAGE:

Zkopírujte ax_code_coverage.m4 do složky m4/ svého projektu.

Přidejte AX_CODE_COVERAGE do configure.ac.

Přidejte @CODE_COVERAGE_RULES do Makefile.am v nejvyšší úrovni.

AdPřidejte $(CODE_COVERAGE_CFLAGS) do proměnné *_CFLAGS systému automake pro každý cíl, který chcete pokrýt, například pro všechny knihovny, ale už ne tak pro jednotkové testy. To stejné proveďte pro $(CODE_COVERAGE_LDFLAGS) a *_LDFLAGS.

Dokumentace k používání gcov a lcov je zde.

Sanitizéry adres, vláken a nedefinovaného chování

Jak GCC, tak Clang, mají podporu několika sanitizérů: sadu dodatečného kódu a kontrol, které mohou být volitelně zakompilovány do vaší aplikace a použity k označené různorodého nesprávného chování za běhu. Jedná se o mocné nástroje, ale musí být zapnuty či vypnuty tak, že překompilujete svoji aplikaci. Nemohou být zapnuty zárovneň s jinými a nemohou být použity naráz s nástroji Valgrind. Jedná se stále o poměrně novou funkcionalitu, takže je jen málo integrována s jinými nástroji.

Všechny sanitizéry jsou dostupné pro překladač GCC i Clang a přijímají stejnou sadu přepínačů kompilátoru.

Sanitizér adres

Sanitizér adres („asan“) detekuje chyby „použití po uvolnění“ a přetečení vyrovnávací paměti v programech C a C++. Vyčerpávající výklad k používání asan je k dispozici pro Clang, ale stejné postupy platí i pro GCC.

Sanitizér vláken

Sanitizér vláken („tsan“ – thread sanitizer) zjišťuje souběhy v přístupu k datům v paměťových místech a k tomu také různá nesprávná použití API pro práci s vlákny podle standardu POSIX. Celistvý průvodce používáním nástroje tsan je k dispozici pro Clang, ale stejné postupy by měly platit i pro GCC.

Sanitizér nedefinovaného chování

Sanitizér nedefinovaného chování („ubsan“ – undefined behavior sanitizer) je sbírka drobných nástrojů, které zjišťují různá potencionálně nedefinovaná chování v programech napsaných v jazyce C. Sada pokynů pro zapnutí ubsanu je k dispozici pro Clang, ale stejně by to mělo fungovat i pro GCC.

Coverity

Coverity je jeden z nejpopulárenějších a největší komerční dostupný nástroj pro statickou analýzu. Přestože je komerční, je volně k dispozici pro projekty Open Source, akorát je projekt vyzván k přihlášení. Analýza je prováděna spuštěním některých analytických nástrojů lokálně a následně jsou zdrojový kód i výsledky v zabalené podobě nahrány na servery Coverity. Výsledky jsou pak členům projektu viditelné on-line v podobě poznámek u zdrojového kódu (obdobně, jako své výsledky podává lcov).

Protože Coverity nelze jako celek spustit lokálně, nelze jej ani pořádně zaintegrovat do systému sestavení. Existují ale skripty, které projekt automaticky pravidelně proskenují a zabalená data nahrají na servery Coverity. Doporučovaný přístup je spouštět tyto skripty pravidelně na serveru (typicky jako cronjob) a předtím stáhnout čisté zdrojové kódy z repozitáře git projektu. Coverity zašle členům projektu e-mail s nově nalezenými problémy ve statické analýze, takže může být zvolen stejný přístup, jako k varováním kompilátoru: řešte všechna varování statické analýzy a řešte je hned, jak jsou nalezena.

Coverity je dobrý, ale ne dokonalý nástroj a produkuje řadu falešných hlášení. Ta lze ale v rozhraní příkazového řádku označit, aby byla ignorována.

Statický analyzátor Clang

Jedním z nástrojů, které můžete používat k provádění místní statické analýzy, je statický analyzátor Clang, což je nástroj vyvíjený spolu s kompilátorem Clang. Detekuje různé problémy v kódu v jazyce C, které kompilátor rozpoznat nedokáže a které by tak byly zjištěny až za běhu (pomocí jednotkových testů).

Clang produkuje i pár falešných hlášení a není žádný jednoduchý způsob, jak je nechat ignorovat. Proto je při jejich výskytu doporučeno vyplnit pro statický analyzátor chybové hlášení, aby mohla být do budoucna opravena.

Ucelený průvodce používáním kompilátoru Clang je zde.

Tartan

Přes veškeré vymoženosti, které statický analyzátor Clang poskytuje, nedokáže detekovat problémy se specifickými knihovnami, jako je GLib. Což je problém, když projekt používá výhradně GLib a jen zřídka API standardu POSIX (kterému Clang rozumí). Proto je k dispozici zásuvný modul pro statický analyzátor Clang, který se nazývá Tartan a rozšiřuje Clang o podporu kontroly vůči některým běžným API knihovny GLib.

Tartan je zatím velmi mladý software a může produkovat falešná hlášení a při spuštění vůči některému kód se může i zhroutit. Pomůže vám ale velmi rychle najít skutečné chyby a je vhodné jej vůči základnímu programovému kódu spouštět často, abyste odhalili nové chyby v použití GLib v svém kódu. Pokud s Tartanem narazíte na nějaké problémy, nahlaste je prosím.

Ucelený návod na zprovoznění Tartanu pro použití se statickým analyzátorem Clang najdete zde. Pokud je nastavení správné, je výstup z Tartanu dán dohromady s výstupem z normálního statického analyzátoru.