Linux E X P R E S

Facebook

Vývoj jádra III. – ladění a testování

Často se stane, že vytvářený modul zkompilujeme, slavnostně zavedeme do paměti... a počítač zatuhne. Nebo sice běží bez problémů dál, ale modul nedělá to, co by měl. V takové chvíli nezbývá, než se pustit do ladění. Nejlépe je ale problémům s modulem předcházet a každou novou nebo změněnou věc důkladně otestovat. Tyto činnosti se v rámci jádra dělají obtížněji než v běžných programech, ale bezmocní rozhodně nejsme.


Výpisy jádra

S výpisy jsme se setkali již minule. Modul v příkladu vypisoval informace o tom, že se úspěšně načetl, resp. uvolnil z paměti. Používali jsme na to funkci printk(), která je jadernou obdobou známé céčkovské funkce printf() - dokonce je ještě silnější, protože umožňuje určit úroveň závažnosti zprávy pro filtraci nebo třídění.

Prototyp funkce vypadá takto: printk(const char* format, arg...). Je tedy stejný jako u printf() - rozdíl je v tom, jak začíná formátovací řetězec. Funkce printk() má totiž na jeho začátku právě úroveň závažnosti - ta se určuje obvykle příslušným makrem. Zde je několik příkladů:

printk(KERN_EMERG "Fatal error - giving up :-(\n");
printk(KERN_ERR "Error: %i\n", err);
printk(KERN_WARNING "Address %p is invalid\n", p);
printk(KERN_INFO "Module loaded.\n");
printk(KERN_DEBUG "offset=%lu\n",offs);

První uvedená úroveň se používá velice zřídka - značí totiž stav, kdy je další běh systému vyloučen. Druhý příklad je chybové hlášení. Mělo by se používat ve všech situacích, které by neměly nastat (chybové stavy). Třetím příkladem je varování, použitelné např. při špatných hodnotách předaných modulu nějakou cestou zvenku. Pak tu máme informativní zprávu, která nachází uplatnění při některých důležitých událostech v modulu (načtení, uvolnění, změna konfigurace apod.). Poslední příklad je ladicí zpráva. Ta se od předchozích úrovní liší tím, že se vypisuje jen v případě, že je při kompilaci zapnuto ladění (je definován symbol DEBUG).

Ladicí (debug) jádro může výrazně pomoci v ladění modulu. Musí být ovšem celé takto zkompilováno (nestačí tak zkompilovat jen modul), po zapnutí konfigurační volby Kernel debugging a těch funkcí, které požadujeme. Můžeme tak např. kontrolovat alokace paměti, zamykání, operace se zásobníkem, lze aktivovat klávesu SysRq (velmi užitečná věc), výpisy zpráv ohledně ovladačů a subsystémů a mnoho dalších věcí. Ke kompilaci takového jádra je třeba úplný zdrojový balík.

Hlavně ve fázi ladění a testování modulu tyto zprávy dobře poslouží ke správnosti hodnot, se kterými se pracuje. Je to jednodušší a rychlejší, než bezradně tápat, když něco nefunguje.

Úrovní je ve skutečnosti ještě víc (např. místo KERN_INFO se často používá KERN_NOTICE) - o tom, jak s nimi nejlépe pracovat, se lze nejlépe dozvědět přímo ze zdrojových kódů jádra. V hlavičkových souborech jádra jsou také makra pr_err, pr_warn, pr_info a pr_debug. Lze je používat místo funkce printk(), význam je snad zřejmý. Zejména je vhodné makro pr_debug, protože pokud není definován symbol DEBUG, funkce printk() se vůbec nepoužije, a nedochází tak ke zbytečnému zdržování. Lze si vytvořit i vlastní makra s nějakým chováním (třeba v závislosti na definovaných symbolech), mnozí autoři modulů tak rádi činí.

Zejména u chybových hlášení využijeme ještě další makra, kterými lze snadno identifikovat místo, kde došlo k chybě. Nejdůležitější z nich jsou __FILE__, __LINE__ a __FUNCTION__. Používají se takto:

pr_err("Error %i in %s at %s:%i\n", err, __FUNCTION__, __FILE__, __LINE__);

Příklad ukazuje (s použitím makra pr_err), jak správně označit chybu v modulu. Identifikuje se funkce, řádek a soubor, kde k chybě došlo. Dále je v headeru asm-generic/bug.h definováno několik maker pro označení míst, kam se nemá nikdy zaběhnout, a podmínek, které nesmějí nastat. Doporučuji používat - je to užitečné. Ještě připomenu, že pokud je modul určen k obecnějšímu použití, měly by být veškeré zprávy v angličtině.

Zabývali jsme se jen takovými chybami, které lze nějak zjistit. Člověk ale často spáchá nějakou nepříjemnou chybu, která se přímo zjistit nedá. Mnohdy ji ale zjistí přímo jádro a dá to zřetelně najevo. V textu naleznete nejčastější chybové stavy.

Sledování systémových volání

Program strace, používaný ke sledování systémových volání a signálů, zná snad každý uživatel Linuxu. Výbornou službu odvede i při ladění jaderných modulů. S jeho pomocí můžeme sledovat, kde běh procesu vstupuje do jádra, jaká data se do a z jádra přenášejí, kolik času se uvnitř volání stráví atd. Protože v tuto chvíli nemáme vlastní modul schopný provádět nějaké souborové či jiné operace, podíváme se aspoň, jak by vypadal průběh jeho načtení:

[root@localhost]# strace -T insmod mymodule.ko
execve("/sbin/insmod", ["insmod", "mymodule.ko"], \\
[/* 26 vars */]) = 0 <0.000507>
...
open("mymodule.ko", O_RDONLY)           = 4 <0.000034>
read(4, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\1\0"..., 16384) \\
= 16384 <0.000079>
read(4, "\22\6\36\0\0\22\374\2\252)\0\0\3#\354"..., 16384) \\
= 16384 <0.000078>
read(4, "_start_pfn\0_pid\0screen_bitmap\0_s"..., 32768) \\
= 22166 <0.000085>
read(4, "", 10602)                      = 0 <0.000062>
close(4)                                = 0 <0.000023>
init_module("ELF", 0xd696)          = 0 <0.034796>
exit_group(0)                           = ?

Z celého procesu načtení modulu je zde uvedena jen část. Zajímavé je v tomto případě hlavně volání inicializace modulu. Jak je vidět z naměřených časů, samotná inicializace modulu je poměrně časově náročná záležitost. Kdybychom měřili čas uvnitř inicializační funkce, naměříme jen naprosto minimální čas - ten celý zbytek je režie jádra, které musí vytvořit potřebné datové struktury, vyhodnotit dostupnost symbolů atd. Podobná měření jsou důležitá, právě proto, že zdánlivě rychlá operace může být ve skutečnosti zatraceně zdlouhavá.

Chybová hlášení jádra

Kernel panic

Nastala tak vážná chyba jádra, že se z ní nelze zotavit a musí se použít tvrdý restart. Může se to stát třeba při špatném zacházení se zámky, při pokusu o uspání v zakázaném místě apod.

Oops

Nastala vážná chyba, ale systém běží dál - mohou však být poškozeny datové struktury jádra. Lze ještě uložit případná neuložená data a pak se systém musí restartovat. Oops se při tvorbě modulů objevuje velice často, obvykle např. při dereferenci neplatného ukazatele.

Warning

Něco je špatně, ale běh systému to neohrozí. Většinou není nutné podnikat žádné zvláštní kroky. Varování jádro vypíše např. při nalezení neinicializované systémové datové struktury.

Všechna tato hlášení obsahují informaci, kde nastal problém. Oops a Warning mohou obsahovat mj. obsah registrů, výpis zásobníku volání, ale hlavně informaci o tom, co se stalo. Na příklad takového výpisu tu bohužel není prostor, proto odkazuji na publikaci Linux Device Drivers, kde je tohle vše podrobně popsáno.

Pro ladění modulů lze použít i různé ladicí programy. Nejčastěji se používá klasický gdb a také speciální program kdb. Protože práce s nimi není triviální a podrobnější vysvětlování by vyžadovalo prostor, který tu není, vezmeme to jen stručně.

Ladicí programy

gdb

Je to obecný ladicí program, každý programátor aplikací pro Linux ho zná. Můžeme donutit gdb, aby místo programu pracoval s jádrem - prostě mu místo spustitelného souboru podstrčíme kód jádra (nekomprimovaný!) a místo běžícího procesu "běžící" jádro. Vypadalo by to kupř. takto:

Ladění samotným programem gdb (na běžném jádře) nepřináší příliš užitku. Hodnoty proměnných, které se velice rychle mění (a gdb je stejně cachuje), nám moc neřeknou. Pokud nepoužíváme kgdb (případně práci s jádrem v uživatelském prostoru), je tedy mnohem přínosnější hodnoty vypisovat do logu pomocí printk() nebo pr_debug.

gdb /usr/src/linux/vmlinux /proc/kcore

Předpokládá to samozřejmě, že zkompilované jádro bude na uvedené cestě. Takto lze ladit samotné jádro, ale už ne moduly. gdb o nich totiž nic neví (musely by se mu předat informace o sekcích modulu). Při ladění lze samozřejmě pouze zjišťovat hodnoty proměnných apod., ale nelze nic měnit a samozřejmě ani zastavovat běh nebo krokovat.

kdb

Normální jádro neobsahuje žádnou podporu pro ladění. Tu lze dodat právě pomocí patche s názvem kdb. Pak lze klientskou (aplikační) částí provádět téměř plnohodnotné ladění - tedy i včetně breakpointů a krokování (samozřejmě jen v kontextu procesu).

kgdb

Na jádro se aplikují zvláštní patche, umožňující používat gdb způsobem, jakým pracuje kdb. Jedná se o poměrně novou záležitost, podrobnosti najdete v dokumentaci jádra nebo na stránkách příslušného projektu.

Jádro běžící v uživatelském prostoru

Protože některé funkcionality nelze (ani při zvláštních úpravách jádra) dobře odladit, zbývá ještě možnost provozovat jádro jako normální proces. Případné chyby nemohou ovlivnit běh systému a k ladění lze použít normální gdb (s plnou podporou všech možností).

Takové jádro se bohužel nedostane k hardwaru, ale pro ladění takových věcí, jako jsou souborové systémy nebo nějaké obecné služby jádra, se ale běh jádra v uživatelském prostoru výborně hodí. Projekt, který se tím zabývá, je sice ještě v poměrně raném stádiu, ale už lze jeho výsledků úspěšně využívat. Informace můžete najít na příslušných stránkách (user-mode-linux.sourceforge.net).

Diskuze (0) Nahoru