Milé děti,
Rád bych tímto článkem volně navázal na předchozí články a debugerech a ladění. Jedním z největších problémů pro vývojáře kernelu a driverů v Linuxu je jejich efektivní ladění. Kernel nemůže být jednoduše spuštěn v debugeru a trasován. Chyby v něm mohou být těžko reprodukovatelné a také mohou způsobit totální totální pád systému a tím zničit o sobě všechny důkazy.
V současné době se používají zejména tyto způsoby ladění
- ladící výpisy
- dotazování
- pozorování
- analýza Oops výpisů
- debugery
Ladící výpisy
V současnosti pravděpodobně nejpoužívanější metoda nejen pro ladění kernelu. K výpisům se používá funkce printk, která má stejné parametry jako dobře známá funkce printf. Jedním z rozdílů oproti printk je možnost specifikovat důležitost vypisované zprávy a následně ji zobrazit na konzole nebo potlačit. V souboru linux/kernel.h je definováno 8 úrovní důležitosti
KERN_EMERG používá se pro naléhavé zprávy. Obvykle pro ty, po nichž následuje kernel panic.
KERN_ALERT situace vyžadující okamžitou reakci
KERN_CRIT kritický stav, často způsobený hardwarovou nebo softwarovou chybou
KERN_ERR chybový stav, drivery často používají tuto úroveň pro zobrazování chybového stavu hardwaru
KERN_WARNING varování o problematickém stavu, který sám o sobě nezpůsobuje problémy se systémem
KERN_NOTICE situace, která je normální, ale pořád stojí za zaznamenání. Mnoho bezpečnostních hlášení je zobrazováno v této úrovni
KERN_INFO informační zprávy. Mnoho driverů zobrazuje v této úrovni informace o nalezeném hardwaru.
KERN_DEBUG používá se pro ladící informace
Dotazování
Časté používání funkce printk může vést ke značnému zpomalení celého systému. Proto se někdy také používá technika tzv. dotazování, tj. zaslání požadavku na ladící informace pouze tehdy, když je skutečně potřebujeme. Každý UNIXový systém již má několik takových nástrojů (ps, netstat, vmstat atd.). V linuxových driverech můžeme buďto vytvořit speciální soubor v /proc filesystému, ze kterého můžeme pouze číst popř. i zapisovat do něj, nebo můžeme vytvořit speciální ioctl volání.
Pozorování
Někdy lze chybu v driveru nalézt pouhým pozorováním aplikace v user space. Jsou různé metody, jak „pozorovat“ user space program. Můžeme ho trasovat v debugeru, přidat ladící výpisy nebo použít dobře známý program strace, který vypisuje všechna systémová volání včetně parametrů v symbolické podobě. strace získává informace od kernelu, tzn. že nezáleží na tom, zda byl driver přeložen s ladícími informacemi (option -g pro gcc), a nebo jestli byl „stripován“.
Analýza Oops výpisů
I přes veškerou snahu a použití všech výše uvedených ladících metod se stává, že v driveru zůstane chyba a v běžícím systému nastane výjimka. Když se to stane, je důležité sesbírat co nejvíce informací pro odhalení problému. Většina takovýchto chyb pramení z přístupu do neplatné paměťové oblasti (např. přes NULL pointer). V případě takovéto chyby je zobrazen tzv. Oops výpis na konzoli. Začátečníkům se může zdát trochu nepřehledný, ale „procesor dump“ je plný užitečných informací o stavu procesoru, jeho registrů, umístění page descriptor tabulek a o obsahu zásobníku v okamžiku vykonání neplatné instrukce. Výpis je generován ve fault handleru (arch/*/kernel/traps.c) pomocí volání funkce printk. Nejdůležitější informací ve výpisu je hodnpota registru ukazatele instrukce (EIP na architektuře i386), což je virtuální adresa instrukce, jež vyvolala výjimku.
Hlavní problém při použití těchto výpisů je, že adresy jsou uváděny jako hexadecimální čísla; aby byly pro nás užitečné, musíme je nějakým způsobem převést na symboly. Naštěstí jsou zde utility klogd(8) a ksymoops(8), které provedou tuto práci za nás.
Použití debugerů
Další možností pro debugovaní driveru je použití debugeru. Ten nám umožní krokování funkcemi, sledování hodnot proměnných a registrů procesoru. Toto však není tak jednoduché, jako je debugování user space programů, někdy však i přesto detailní pohled do kódu skrz debugger bývá neocenitelný.
gdb
Starý dobrý gdb bývá neocenitelný při pozorování vnitřních proměnných kernelu, jestliže jej použijeme v režimu core dump analýzy. V tomto režimu však mnoho možností poskytovaných gdb nelze použít – modifikace dat, krokování, nastavování breakpointů a watchpoinů.
Při spouštění debugeru jako první parametr uvedeme aplikaci, tj. nekomprimovaný kernel (nikoli zImage or bzImage), a druhým parametrem je core dump soubor – /proc/kcore
gdb /usr/src/linux/vmlinux /proc/kcore
Nyní můžeme vypisovat proměnné kernelu standardními příkazy gdb. Napřp jiffies vypíše aktuální hodnotu proměnné jiffies, která se periodicky inkrementuje. Pro snadnější práci doporučuji kompilovat kernel s ladícími informacemi (-g parametr pro gcc). Když analyzujeme data v gdb, kernel stále běží a data mohou mít v různých okamžicích jiné hodnoty, ale gdb optimalizuje přístup k core file pomocí své vnitřní cache. Jestliže se pokusíme vypsat znovu hodnotu proměnné jiffies, dostaneme stejnou hodnotu jako v předchozím výpisu. Tato technika je výhodná pro „standardní“ core dumpy, ale při „dynamickém“ core dumpu nám způsobuje výše uvedený problém. Naštěstí gdb poskytuje řešení pomocí příkazu core-file, který znovu načte core dump a vyprázdní interní gdb cache.
kdb
Můžete se divit, proč oficiální kernel neobsahuje v sobě již nějaký debuger. Odpověď je jednoduchá: Linus nedůvěřuje interaktivním debugerům. Říká, že mohou vést k odstranění následku, nikoli však pravé příčiny problému. Naštěstí linuxový kód je licencován GPL licencí a jiní vývojáři chtějí používat takovýto typ debugeru, proto vznikl kdb. Je distribuován formou neoficiálního patche a lze jej stáhnout z oss.sgi.com. Po aplikaci patche pro vaši verzi kernelu a jeho překompilování a spuštění lze debuger aktivovat několika způsoby. Buďto stiskem Pause (Break) klávesy na konzoli, nebo při dosažení breakpointu. kdb je také aktivován při oops výpisu. Je třeba si uvědomit, že úplně vše, co celý systém dělá, je při aktivaci kdb zastaveno. Jestliže se chcete vyhnout problémům, používejte kdb v jednouživatelském režimu.
V kdb můžeme vkládat breakpointy pro zápis/čtení/exekuci, trasovat jednotlivé instrukce (nikoli řádky C programu), dělat stack backtrace, vypisovat a modifikovat proměnné a registry procesoru. Jestliže jste úspěšně aplikovali kdb patch, nalézá se plná dokumentace v adresáři Documentation/kdb.
kgdb
kgdb je patch, který dovoluje použití gdb pro ladění linuxového kernelu se všemi jeho vymoženostmi pro user space programy (breakpointy, trasování, výpis a změna proměnných, stack backtrace …).
Vyžadovaná hardwarová konfigurace jsou dva počítače spojené třížilovým sériovým kabelem (null modem). Na jednom z nich (target) běží laděný kernel a na druhém (host) gdb. Pro úplnost lze dodat, že oba počítače nemusí mít stejnou architekturu. Typický případ je křížový vývoj, kdy se kernel překládá na PC, pak se nahraje na target, který může být např. PowerPC, a ladí se pomocí gdb spuštěného na PC. Tento gdb není standardně dodáván v linuxových distribucích, ale je speciálně zkompilován právě pro tuto konfiguraci (host=i686-pc-linux-gnu, target=powerpc-linux-gnu).
Pro architekturu x86 je kgdb je distribuován ve formě patche pro konkrétní verzi kernelu. Pro architektury PowerPC a MIPS je již tento kernelový debuger integrován v jádře. Dále se budu zabývat verzí pro x86, která se v detailech liší.
kgdb přidává do kernelu následující prvky
- gdb stub – toto je srdce debugeru. Obsluhuje požadavky přicházející z gdb spuštěného na host počítači. Řídí všechny procesory na target počítači, když se kernel právě nachází v kódu debugeru.
- pozměnění fault handlerů – řízení je předáno debugeru, jestliže se vyskytne výjimka
- sériová komunikace – tato část využívá driveru sériového portu a poskytuje interface pro gdb stub. Má na starost vysílání a příjem znaků ze sériového portu včetně obsluhy Ctrl+C zaslané od gdb.
Příprava kernelu
- aplikace kgdb patche na váš kernel
- povolení kgdb v konfiguraci kernelu (kernel hacking → Remote (serial) kernel debugging with gdb). Toto povolí následující volby:
-
- Thread analysis – gdb může vypisovat informace o kernel threadech
- Console messages through gdb – všechny výpisy na konzoli target počítače budou přesměrovány na host. Užitečné zejména v případě, kdy target nemá VGA adaptér a obsahuje pouze jeden sériový port.
- Enable kernel asserts – povolí kernel asserty. Kernel assert je podmínka, která je za normálních okolností pravdivá. Pokud je nepravdivá, zpravidla po nějaké chybě, předá se kontrola debugeru. Na některých místech kernelu jsou umístěty tyto asserty. Mohou se také volně používat v uživatelských driverech.
- kompilace jádra. Pokud možno všechny drivery zakompilujte přímo do kernelu. Jedinou výjimkou je vámi vyvíjený driver, který může být jako modul. Vysvětlení přijde později.
- nahrání jádra na target.
- konfigurace zavaděče Linuxu (např. LILO). KGDB interpretuje následující parametry na příkazové řádce Linuxu:
-
- gdb – kernel čeká při startu na připojení gdb
- gdbttyS=N – číslo sériového portu pro kgdb
- gdbbaud=N – komunikační rychlost (9600 – 115200Bd)
Připojení k target počítači
Na target počítači nabootujte kernel s podporou kgdb. Jestliže jste uvedli parametr gdb na příkazové řádce kernelu, pak by se měla objevit tato zpráva:
Waiting for connection from remote gdb...
V tomto okamžiku kernel čeká na připojení gdb. Pokud chceme zastavit kernel v jiném okamžiku, je zde možnost použít funkce breakpoint() na vámi vybraném místě.
Na host počítači spusťte gdb v adresáři, kde jsou umístěny zdrojové kódy překompilovaného kernelu. Jako partametr uveďte vmlinux, což je ELF soubor kernelu včetně ladících informací. Pak nastavíme rychlost sériového portu na host počítači příkazem set remotebaud N, kde N je číslo uvedené jako parametr pro gdbbaud. Pro připojení k target počítači napíšeme target remote serial_device, kde serial_device je cesta k ovladači sériové linky (např. /dev/ttyS0). Nyní je gdb propojen s kgdb stubem a má plnou kontrolu nad target počítačem. Nyní můžete vkládat breakpointy, trasovat kernel, vypisovat a měnit proměnné atd. Pokračování v běhu kernelu zajistíme příkazem continue.
Ladění modulů
Ladění modulů není tak jednoduché jako kódu, který je zakompilován přímo v kernelu. Jelikož gdb nic neví o principu dynamického nahrávání modulů, musíme mu nějak sdělit, že aktuálně laděný program je rozšířen o další kód. K tomu slouží příkaz add-symbol-file. Tento příkaz má jako parametry adresy jednotlivých sekcí v modulu. Skript loadmodule.sh může být použit pro automatizaci tohoto procesu. Skript předpokládá, že kromě propojení pomocí sériového portu jsou oba počítača také propojeny sítí. Skript stáhněte na host počítač a změňte jméno target počítače, které je uloženo v proměnné TESTMACHINE. Dále skript generuje další skript, který do gdb nahraje patřičné hodnoty symbolů z vašeho modulu. Adresář, kam je skript generován, je uložen v proměnné GDBSCRIPTS. Jako parametr skriptu uvedeme jméno modulu, který bude nahrán na target počítač. Váš modul by měl být opět přeložen s ladícími informacemi (přepínač -g ).
Skript loadmodule.sh dále vkládá do kernelu na target počítači váš modul. K tomu je potřeba mít oprávnění pro root remote shell z host počítače pro váš účet. Toho dosáhneme vytvořením souboru .rhosts v adresáři /root na target počítači. Tento soubor musí být čitelný pouze pro vlastníka.
root@target /root]# ls -l .rhosts -rw------- 1 root root 28 Jan 1 2004 .rhosts [root@target /root]# cat .rhosts host your_account_at_host [root@target /root]#
Po provedení skriptu si můžeme ověřit, zda je modul vložen v kernelu, pomocí následujícího příkazu na host počítači:
[your...@host /]$ rsh -l root target /sbin/lsmod Module Size Used by hello 7504 0 (unused) [your_account_at_host@host /]$
Spustíme gdb a připojíme se k target počítači stejným způsobem popsaným v minulé kapitole. Po úspěšném připojení pokračujeme v běhu kernelu příkazem continue a po nabootování systému ho opět zastavíme v gdb pomocí stisku Ctrl+C, pak můžeme provést na jiné konzoli skript loadmodule.sh, který vygeneruje v adresáři specifikovaném v proměnné GDBSCRIPTS skript pro gdb obsahující příkaz add-symbol-file s příslušnými parametry, a tento script v gdb spustíme příkazem:
(gdb) source your_script
V tomto okamžiku vložíme breakpoint na požadovaná místa v modulu a pokračujeme příkazem „continue“. Po dosažení tohoto breakpointu můžeme modul trasovat, vypisovat a modifikovat proměnné atd.
Závěr
V tomto článku byl uveden stručný přehled dostupných ladících prostředků pro ladění kernelu a modulů. Použití konkrétního způsobu závisí na dané situaci. Další podrobnosti o výše uvedených způsobech se lze dozvědět na následujících místech:
Allesandro Rubini, Jonathan Corbet: Linux device drivers, 2nd edition
KGDB home page
ELJonline: Remote Debugging of Loadable Kernel Modules with kgdb
BDI2000
KDB
Inside the kernel debugger (KDB)