Ladění aplikací "post mortem"

I přesto, že je aplikace napsaná co nejlépe a je také důkladně otestovaná, občas dojde k jejímu pádu. Příčinou může být odlišná konfigurace počítače, nová verze operačního systému, nebo konflikt s jiným softwarem. Výsledkem je (ne)chvalně známého hlášení o tom, že program provedl neplatnou operaci a bude ukončen (obr. vpravo ukazuje hlášení, které uvidíme ve Windows XP). Tento článek popisuje možnost analýzy pádu aplikací, které nastanou mimo vývojové prostředí, nebo dokonce mimo pracoviště, kde byla aplikaca vyvinuta nebo testována. Jde o tzv. ladění "post mortem", tedy ladění po pádu aplikace.

Když je to na mém počítači, jde to snáz

Oznámení o pádu aplikace ve Windows XP

Pokud k pádu dojde na počítači, na kterém je program vyvíjen (a tedy je nainstalováno vývojové prostředí), je možné příčinu pádu snadno najít i v případě, že spadla Release verze aplikace. Díky funkci Just-In-Time Debugging, dostupné v moderních ladicích nástrojích je možné zachytit vyjímku v aplikaci i přesto, že v okamžiku pádu neběží pod debuggerem. Pokud sestavíme Release verzi aplikace s ladicími informacemi, někdy dokonce Visual Studio ukáže přímo zdrojový soubor a řádek, kde nastala chyba. Pro nakonfigurování je nutné provést následující kroky:

  1. Nastavit MS Visual Studio.NET 2003 jako výchozí Just-In-Time debugger. Toto nastavení je provedeno automaticky při instalaci, ale pokud používáte také jiné vývojové prostředí, je možné že bylo od instalace MSVS.NET změněno. Pro obnovení nastavení zvolte "Tools\Options", skupina "Debugging\Just-In-Time", a zkontrolujte, zda je zatrhnutá volba "Native", tedy zachycování výjimek v aplikacích s neřízeným (unmanaged) kódem. Nastavení debuggeru je uloženo v klíči registru "HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug" v proměnné "Debugger".
  2. Nastavit Release verzi aplikace tak, aby byly při jejím sestavení generovány ladicí informace ve formátu PDB.

    • V nastavení projektu ("Project\Properties") na záložce "C/C++\General" nastavte položku "Debug Information Format" na "Program Database (/Zi)".
    • Na záložce "Linker\Debugging" nastavte položku "Generate Debug Info" na "YES (/DEBUG)".

    Tyto volby způsobí vygenerování ladicích informací ve formátu PDB, který je oddělen od samotného EXE nebo DLL souboru, a nezvětšuje tak velikost aplikace. Takto zkompilovanou aplikaci můžete i distribuovat. Podobným způsobem je zkompilován celý operační systém Windows včetně jádra (PDB soubory jsou dostupné na webu Microsoftu a na CD v rámci MSDN).
Jakmile dojde v aplikaci k vyjímce, systém přečte výše uvedený klíč v registru a spustí program zapsaný v proměnné "Debugger". Zkuste postup ověřit na aplikaci, jejíž WinMain obsahuje tento kód:
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int)
{
    LPTSTR szString;

    _stprintf(szString, _T("Uživatel %s na počítači %s"), _T("Test"), _T("TestComputer"));
    return 0;
}

Zkompilujeme Release verzi a spustíme aplikaci. Hlášení o chybě aplikace vypadat jinak než v přechozím případě (viz. obr.). Stiskem tlačítka "Yes" bude spuštěno MSVS.NET 2003, které se připojí na běžící proces a zastaví běh na adrese, na které nastala vyjímka. Hlášení o chybě zní "Unhandled exception at 0x0040312e in SuperAplikace.exe: 0xC0000005: Access violation writing location 0x00000006.". To v překladu znamená, že naše aplikace se snaží zapisovat nějaká data na adresu 0x00000006. Tato adresa opravdu nevypadá jako správné místo pro zapsání dat. Na adrese, kde nastala výjimka, vidíme následující kód (může se lišit v závislosti na použité verzi Visual Studia):

    return (wint_t) (0xffff & (*((wchar_t *)(str->_ptr))++ = (wchar_t)ch));

Tento kód ukládá jeden znak do bufferu. Protože víme, že jde o funkci swprintf, která ukládá formátovaný řetězec do námi specifikovaného bufferu, lze z toho usoudit, že při volání funkce swprintf předáváme nesprávný parametr pro výstupní buffer.

Pokud zobrazíme zásobník (call stack), ukáže nám hierarchii volaných funkcí:

Zásobník volaných funkcí (call stack)

Vidíme, že chyba nastala ve vnitřní funkcí knihovny CRT volané z funkce swprintf. Chybu ale nebudeme hledat ve funkci swprintf samotné, ale spíš v místě, odkud je funkce volaná (z naší funkce WinMain). Dvojklikem na řádek označený na obrázku se dostaneme na řádek v našem kódu, kde byla chyba. Nyní už jen zbývá zajistit nápravu a kód už bude fungovat, jak má.

Chyba v aplikaci mimo vývojovou kancelář nebo mimo testovací prostředí

Konfigurace programu Dr. Watson

Pokud aplikace spadne v reálném provozu, tedy většinou mimo vývojové prostředí, je možné velmi dobře analyzovat příčinu pádu pomocí tzv. crash dumpu aplikace. Jde o soubor, obsahující informace o příčině pádu. V každé instalaci operačního systému řady NT je přítomen nástroj Dr. Watson (drwtsn32.exe), který se umí nainstalovat jako výchozí Just-In-Time Debugger. V případě pádu aplikace je spuštěn a vytvoří crash dump aplikace. Pro nakonfigurování Dr. Watsona proveďte následující kroky:

  1. Spusťte nástroj Dr. Watson s parametrem -i ("drwtsn32.exe -i"). Hlášení informuje o nastavení Dr. Watsona jako výchozího debuggeru.
  2. Spusťte nástroj Dr. Watson (bez parametrů). Objeví se konfigurační obrazovka (viz. obr. vpravo)
  3. Zvolte typ crash dumpu. Volba "Full" vygeneruje kompletní dump vč. paměti procesu. Jeho velikost je až v desítkách MB, není jej tedy možné např. poslat elektronickou poštou. Oproti tomu obsahuje nejvíce informací potřebných pro ladění. Volba "Mini" vygeneruje minimální crashdump obsahující pouze registry, call stack a popis chyby. Jeho velikost ale umožňuje poslat dump e-mailem.
  4. Zaškrtněte všechny volby kromě "Sound notification". Volby "Visual Notification" a "Sound notification" nemají vliv na obsah a velikost dumpu, pouze upřesňují na způsob oznámení o jeho vygenerování.

Nyní je Dr. Watson připraven k vygenerování crash dumpu v případě, že v aplikace dojde k chybě. Program Dr. Watson neběží na pozadí, spuštěn bude až v případě že v libovolné aplikaci dojde k vyjímce. Crash dump bude uložen do adresáře specifikovaném v políčku "Crash Dump".

Jakmile k výjimce dojde, Dr. Watson vygeneruje soubor, který můžeme dále analyzovat. K analýze dumpu budeme potřebovat tzv. Debugging Tools for Windows, dostupné zdarma na webu Microsoftu. Velikost instalačního balíčku je asi 11 MB. Aplikaci nainstalujte s výchozími volbami.

Dále je potřeba nakonfigurovat tzv. Symbol Server. Symbol Server je databáze PDB souborů (symbolů) pro binárky operačního systému, např. kernel32.dll. Ačkoliv jsou tyto PDB symboly dostupné jako balíček na webu Microsoftu a také v rámci MSDN, po instalaci záplaty operačního systému přestanou odpovídat symboly ke komponentám jejichž nové verze byly při aplikaci záplaty nainstalovány. Nakonfigurováním WinDbg pro použití Symbol Serveru zajistíme, že při potřebě symbolů k operačnímu systému si WinDbg stáhne z webu Microsoftu souboru symboly přesně k té verzi, kterou máte na počítači nainstalovanou.

Vytvoříme složku, do které budeme kopírovat jak symboly k naší aplikaci, tak i symboly k operačnímu systému (např. "C:\Symbols"). Do této složky nakopírujeme PDB soubor vygenerovaný linkerem při sestavování naší aplikace. Pokud má aplikace více komponent (např. knihovny DLL), přidáme do složky také jejich PDB soubory. Symboly musejí být vždy synchronizovány s binárními moduly aplikace, jinak WinDbg bude zobrazovat nesprávné informace. Dokonce i pouhé přeložení aplikace bez provedení změn může způsobit stížnosti programu WinDbg na nesprávné symboly.

Symbol server nakonfigurujeme pomocí proměnné prostředí _NT_SYMBOL_PATH ("My Computer\Properties\Advanced\Environment Variables"). Do "System Variables" přidejte proměnnou "_NT_SYMBOL_PATH" s hodnotou "C:\Symbols;srv*C:\Symbols*http://msdl.microsoft.com/download/symbols". Pro správnou funkci Symbol Serveru je nutné připojení k internetu, ale pokud jsou všechny potřebné symboly již staženy, WinDbg se bez internetu obejde. Databázi symbolu je také možné přenést na jiný počítač prostým zkopírováním celého adresáře. Podrobné informace najdete také v nápovědě k aplikaci WinDbg.

Nyní spusťte aplikaci WinDbg a otevřete aplikační crash dump (File\Open Crash Dump). WinDbg je příkazový debugger, jeho ovládání se vykonává pomocí příkazů. Základním příkazem debuggeru pro analýzu crash dumpu je "!analyze -v". Zadáme tento příkaz a WinDbg vypíše podrobnou analýzu, vč. obsahu registrů a call stacku. Právě call stack je nejdůležitější vypsanou informací o pádu, umožňuje zjistit sekvenci volaných funkcí, která vedla k pádu aplikace:

STACK_TEXT:  
0012f7c0 00401481 7ffd0055 0012fc54 0040174f SuperAplikace!fputwc+0xb0 [f:\vs70builds\3077\vc\crtbld\crt\src\fputwc.c @ 131]
0012f7cc 0040174f 7ffd0055 00000006 77d96116 SuperAplikace!write_char+0x16 [f:\vs70builds\3077\vc\crtbld\crt\src\output.c @ 1133]
0012fc3c 004010d5 0012fc54 004060ee 0012fc84 SuperAplikace!_woutput+0x25d [f:\vs70builds\3077\vc\crtbld\crt\src\output.c @ 406]
0012fc74 0040107f 00000006 004060ec 0012fd98 SuperAplikace!swprintf+0x2e [f:\vs70builds\3077\vc\crtbld\crt\src\swprintf.c @ 106]
0012fe98 00401304 00400000 00000000 00020758 SuperAplikace!wWinMain+0x7f [e:\ladik\appdir\superaplikace\winmain.cpp @ 51]
0012ffc0 7c816d4f 00360032 00360032 7ffd6000 SuperAplikace!wWinMainCRTStartup+0x18a [f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c @ 251]
0012fff0 00000000 0040117a 00000000 78746341 kernel32!BaseProcessStart+0x23

Protože máme k modulu SuperAplikace.exe kompletní symboly, WinDbg zobrazí i jméno zdrojového souboru a číslo řádku u každé položky ve výpisu. Další postup je shodný s postupem při ladění pomocí Visual Studia.

Údržba databáze symbolů

Pokud vyvíjíme aplikaci s větším množstvím modulů a navíc udržujeme více verzí, je možné integrovat PDB symboly do Symbol Serveru. Odpadnou starosti o to, která verze aplikace patří ke kterým symbolům. WinDbg pak automaticky načte symboly, které jsou potřeba (kontrola se provádí na základě časové známky modulu). Po sestavení Release verze aplikace je nutné spustit utilitu SymStore.exe (součást balíčku Debugging Tools for Windows).

symstore.exe add /f BinaryFile.exe /s C:\Symbols /t Comment

Je vhodné umístit uložení symbolů do dávkového souboru např. "PostBuild.bat", který Visual Studio spustí po každém sestavení Release verze aplikace.

A ještě na závěr

V reálné praxi samozřejmě není hledání chyb tak jednoduché, jak ukázal tento příklad. Složitost nalezení chyby se závisí na povaze problému, zkušenosti a schopnostech programátora. Tento článek by měl sloužit jako úvod do možností ladění aplikací "post-mortem". Další informace a postupy o možnostech analýzy najdete v nápovědě k aplikaci WinDbg.