načítání...
nákupní košík
Košík

je prázdný
a
b

Kniha: Jádro systému Windows -- Kompletní průvodce programátora - Martin Dráb

Jádro systému Windows -- Kompletní průvodce programátora
-15%
sleva

Kniha: Jádro systému Windows -- Kompletní průvodce programátora
Autor:

Zajímá vás, jak operační systémy fungují? Potřebujete znát principy programování ovladačů? Chcete umět samostatně zkoumat vnitřní mechanismy jádra? Průvodce z pera českého ... (celý popis)
Titul doručujeme za 3 pracovní dny
Vaše cena s DPH:  790 Kč 672
+
-
rozbalKdy zboží dostanu
22,4
bo za nákup
rozbalVýhodné poštovné: 29Kč
rozbalOsobní odběr zdarma

ukázka z knihy ukázka

Titul je dostupný ve formě:
tištěná forma elektronická forma

hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%hodnoceni - 0%   celkové hodnocení
0 hodnocení + 0 recenzí

Specifikace
Nakladatelství: » Computer press
Médium / forma: Tištěná kniha
Rok vydání: 2011
Počet stran: 472
Rozměr: 167 x 225 mm
Úprava: ilustrace
Vydání: Vyd. 1.
Vazba: vázaná s laminovaným potahem
Datum vydání: 31. 8. 2011
Nakladatelské údaje: Brno, Computer Press, 2011
ISBN: 9788025127315
EAN: 9788025127315
Ukázka: » zobrazit ukázku
Popis / resumé

Kniha je určena pro zkušenější programátory. Věnuje se různým aspektům jádra operačních systémů Windows NT a snaží se je uvést do širších souvislostí teorie operačních systémů.

Popis nakladatele

Zajímá vás, jak operační systémy fungují? Potřebujete znát principy programování ovladačů? Chcete umět samostatně zkoumat vnitřní mechanismy jádra? Průvodce z pera českého odborníka vám objasní fungování jádra a jeho algoritmů. Dozvíte se vše podstatné o základních datových strukturách, ladění ovladačů jádra, procesech, vláknech, efektivní správě paměti, registru a dalších součástech jádra, jejichž pochopení je pro programátora nezbytné. Snadno se tak zorientujete v zákonitostech jádra Windows. Součástí výkladu je výuka základních principů programování vlastních ovladačů a popis využití užitečných nástrojů. Kapitoly jsou zpravidla rozděleny do dvou částí: v první získáte návody pro průzkum jádra v obecné rovině a druhá ukazuje, jaké poznatky a algoritmy se vývojáři a návrháři jádra Windows rozhodli použít přímo v praxi. Autor se v knize věnuje mimo jiné těmto tématům: - Fronta, pole, spojové seznamy, hašovací tabulky - Vývoj ovladačů jádra - Synchronizační primitiva - Řízení přístupu více aplikací ke sdíleným prostředkům - Způsoby přenosu dat mezi aplikacemi a ovladači - Výjimky, přerušení a systémová volání - Uvolnění alokovaných stránek pomocí rutin - Formáty dat a souborové systémy FAT a NTFS Na adrese http://www.jadro-windows.cz naleznete okomentované zdrojové kódy ovladačů jádra, materiály vhodné pro další rozšiřování znalostí a užitečné nástroje. O autorovi: Martin Dráb se o vnitřní fungování operačních systémů Microsoft Windows začal zajímat již na střední škole. Samotnému jádru se věnuje od prvních let studia na MFF UK. Svůj výzkum zaměřuje především na implementaci ochrany proti útokům malware a v posledních dvou letech spolupracoval na zakázkách pro několik velkých společností na poli počítačové bezpečnosti. Aktuálně se zabývá způsoby ochrany proti škodlivým kódům na platformě x64 a příležitostně píše články a komentáře na serveru secit.sk. (kompletní průvodce programátora)

Předmětná hesla
Microsoft Windows
jádro operačního systému
Programování
Windows
Kniha je zařazena v kategoriích
Martin Dráb - další tituly autora:
 (e-book)
Jádro systému Windows Jádro systému Windows
 
Recenze a komentáře k titulu
Zatím žádné recenze.


Ukázka / obsah
Přepis ukázky

3

Vývoj ovladačů

jádra

OKAPITOLA 3

Vývoj ovladačů jádra

3 3

Cílem této knihy není pouze poskytnout čtenářům teoretické informace o vnitřním uspořádání

a fungování jádra operačních systémů rodiny NT, ale také ukázat, jak těchto znalostí prakticky vyu

žít. Z tohoto důvodu v sobě některé kapitoly zahrnují i ukázkové zdrojové kódy ovladačů jádra.

Tato kapitola si klade za cíl poskytnout takové informační pozadí, abyste ukázkovým zdrojovým

kódům rozuměli, uměli si je přeložit do binární podoby a vyzkoušet. Dozvíte se podrobnější infor

m a c e o t o m , j a k s e o v l a d a č e l i š í o d b ě ž n ý c h a p l ikací, jaké prostředí pro jejich programování Micro

soft nabízí a jak probíhá komunikace mezi ovladačem a kódem běžícím v uživatelském režimu.

Tato kapitola podává informace neformálním způsobem. Širší souvislosti se dozvíte v dalších

částech knihy.

V závěru naleznete ukázkový zdrojový kód jednoduchého ovladače jádra, který demonstruje

praktickou aplikaci znalostí, jež kapitola obsahuje.

Co je to ovladač

Již víte, že ovladače se na disku nacházejí ve spustitelných souborech formátu PE, většinou

s příponou .sys. Na rozdíl od běžných aplikací, kód ovladačů není vykonáván v separátních

virtuálních adresových prostorech, ale sdílí prostor společně s jádrem operačního systému.

Přesněji, tento adresový prostor je namapován do prostoru každého běžícího procesu. Situaci

vidíte na obrázku 3.1.

Obrázek 3.1: Zjednodušená struktura virtuálního adresového prostoru procesu

Privátní část adresového prostoru procesu (tedy oblast, kam ostatní procesy nemají přímý pří

stup) se nachází mezi adresami 0x00000000 a 0x7FFFFFFF. Na adresy 2 GB a vyšší je do všech


Kapitola 3 Vývoj ovladačů jádra 66 adresových prostorů namapována oblast jádra, která však z uživatelského režimu není přístupná. Naopak privátní části adresových prostorů z režimu jádra přístupné jsou. Obrázek 3.2 ukazuje mapování několika virtuálních adresových prostorů do fyzické paměti. Vidíte na něm, že horní část adresových prostorů je pokaždé namapována na stejné fyzické adresy. Obrázek 3.2: Mapování několika virtuálních adresových prostorů do fyzické paměti

Upozornění: Zde načrtnutá struktura adresového prostoru procesu platí pro většinu 32bitových kon

figurací a má pouze ilustrační charakter. Na některých konfiguracích je počátek prostoru jádra posu

nut až na adresu 3 GB (0xC0000000) a u 64bitových verzí operačního systému vypadá struktura

celého prostoru úplně jinak. Bližší informace naleznete v kapitole 9, která se věnuje správě paměti. Ovladače tedy nemají k dispozici žádný speciální kontext procesu, kterému by systém předával řízení, když by potřeboval vykonat jejich kód. Ten vykonává vlákno, které je zrovna naplánováno na procesoru. Jejich kód a data (proměnné) jsou namapovány ve virtuálním adresovém prostoru každého procesu na stejném místě. Z tohoto důvodu na konkrétním kontextu procesu (a vlákna) nezáleží. Existuje však několik pravidel, pomocí kterých dokážete určit, v kterém kontextu kód ovladače poběží:

Přechod z uživatelského režimu do režimu jádra v rámci systémového volání nemění

kontext vlákna ani procesu.

Kód vykonávaný některým z pracovních vláken (jedná se většinou o asynchronní zpra

cování odpovědi hardware na dřívější požadavek) běží vždy v kontextu procesu System.

Obsluha výjimek a přerušení probíhá v kontextu vlákna, které se nacházelo na procesoru

v okamžiku, kdy k příslušné události došlo.

Ovladačům většinou nezáleží na tom, v kontextu jakého procesu se jejich kód vykonává. Ve výjimečných případech se však mohou přepnout do libovolného běžícího procesu. Slouží k tomu funkce KeAttachProcess, KeStackAttachProcess, KeUnstackDetachProcess a KeDetachProcess exportované hlavním modulem jádra (ntoskrnl.exe). Tyto rutiny mění pouze kontext procesu; aktuální vlákno si při volání KeAttachProcess či KeStackAttachProcess „přivlastní“ zadaný proces

Co je to ovladač

67

3

Vývoj ovladačů

jádra

a ke svému pravému vlastníkovi se vlákno vrátí přes KeDetachProcess či KeUnstackDetach Process. KeAttachProcess se od své zásobníkové varianty (KeStackAttachProcess) liší v tom, že pokud vlákno „navštívilo“ cizí proces, musí se před návštěvou dalšího cizího procesu vrátit do kontextu svého vlastníka. Jinými slovy, mezi dvěma voláními KeAttachProcess se musí nacházet volání KeDetachProcess. Pro druhou dvojici funkcí toto neplatí. Volání však musí být párová – na každé volání KeAttachProcess (resp. KeStackAttachProcess) musí existovat volání KeDetachProcess (resp. KeUnstackDetachProcess).

Poznámka: Jak uvádí druhá kapitola, pro ochranu prostředí ovladačů slouží proces System. Jak ale

vyplývá z odrážek výše, ovladače nemohou běžet pouze v kontextu tohoto procesu.

Další rozdíl mezi ovladači a běžnými aplikacemi skrývá práce s pamětí. Aplikace vidí všechnu paměť virtuálně – přistupují na různé virtuální adresy a je na operačním systému, aby zajistil existenci potřebných mapování do fyzické paměti. Pokud například program přistupuje do stránky, která se nachází ve stránkovacím souboru na disku, systém tuto stránku nahraje do fyzické paměti a vytvoří potřebné mapování. Tyto operace „v zákulisí“ jsou pro aplikaci (až na případné malé časové zpoždění) neviditelné. Ovladače také pracují téměř výhradně s virtuální pamětí. Jejich programátoři si ale musí uvědomovat, co se za virtuálními adresami skrývá. Za určitých okolností si totiž výpadek stránky (nastává, pokud požadovaná stránka virtuální paměti není mapována do fyzické paměti) nelze dovolit. K takové situaci dochází například při obsluze přerušení. Představte si, že při obsluze přerušení disku dojde k výpadku stránky. Procesor vyvolá nové přerušení s číslem 0xE, které informuje systém, že nastal výpadek stránky. Operační systém se pokusí chybějící stránku nahrát ze stránkovacího souboru. Protože se stránkovací soubor nachází na pevném disku, jádro toto zařízení požádá o načtení příslušných dat. Jakmile disk splní požadavek, vyvolá další přerušení. Nezapomeňte, že v našem příkladu došlo k výpadku stránky právě při obsluze přerušení disku. Takovýmto způsobem by mohlo snadno dojít k zatuhnutí celého systému. Proto Windows obsahují bezpečnostní opatření, která jejich běh preventivně ukončí, pokud nějaký ovladač udělá akci, jejíž provedení by za daných podmínek mohlo ohrozit chod celého systému a způsobit i poškození hardware. Takovou akcí je třeba výpadek stránky při obsluze drtivé většiny přerušení. Z předchozích odstavců vyplývá, že za určitých okolností si programátor ovladače musí být jist, že k výpadu stránky nemůže dojít. Proto ovladače mohou alokovat a používat paměť dvou druhů:

Paměť stránkovaného fondu (PagedPool) sestává ze stránek virtuální paměti, které mo

hou být uloženy na disk do stránkovacího souboru. Tento druh paměti se tedy chová

úplně stejně jako privátní část adresového prostoru procesu uživatelského režimu.

Paměť nestránkovaného fondu (NonPagedPool) sestává ze stránek virtuální paměti, které

operační systém za žádných okolností neodloží do stránkovacího souboru na disk. Při

přístupu na tyto adresy k výpadku stránky nemůže dojít. Paměť z nestránkovaného fon

du byste měli používat s rozvahou. Využití velkého množství fyzické paměti pro účely

tohoto fondu může negativně ovlivnit výkon celého systému, protože narušuje mecha

nismy virtuální paměti – ubírá fyzickou paměť, kterou je možné využít k načtení bloků

ze stránkovacího souboru. Kapitola 3 Vývoj ovladačů jádra 68

Upozornění: Výpadek stránky nastává, kdykoliv procesor není schopen určit, na jakou fyzickou ad

resu má požadovanou virtuální adresu přeložit. Tedy i v případě přístupu na adresu, na které není

alokována žádná paměť. Před takovými výpadky stránky vás neuchrání ani nestránkovaný fond.

Přístup na neplatnou virtuální adresu svědčí o tom, že někde v programu je chyba. U odladěného

kódu by k takovému chování docházet nemělo. Pokud se váš ovladač chová správně, k výpadku

stránky při přístupu do oblasti nestránkovaného fondu dojít opravdu nemůže.

Tip: Windows umožňují alokovat paměť i z dalších oblastí. Dokumentovány jsou fondy PagedPool

MustSucceed a NonPagedPoolMustSucceed, pro které systém vyhrazuje velmi malou oblast

virtuální paměti (například 64 kilobajtů). Charakteristickým znakem těchto fondů je, že každá alokace

musí proběhnout úspěšně. Pokud se tak nestane – například proto, že ve fondu není dostatek volné

paměti – běh systému skončí modrou obrazovkou smrti. Ovladače a jádro by měly používat paměť

z těchto zdrojů, pouze pokud je neúspěch alokace neslučitelný s dalším během systému. Posledním velkým rozdílem mezi ovladači a aplikacemi běžícími v uživatelském režimu je moc, kterou nad systémem zástupci těchto kategorií mají. Na rozdíl od normálního programu ovladač může:

Přímo komunikovat s periferními zařízeními.

Ovlivňovat chování systému jako celku a dočasně narušovat základní mechanismy, mezi

které patří plánování vláken na procesoru a fungování bezpečnostního modelu.

Na druhou stranu chyba v kódu ovladače způsobí pád celého operačního systému, kdežto chyba, kterou uděláte při programování běžné aplikace, většinou skončí násilným ukončením příslušného procesu ze strany Windows. Programování ovladačů je v tomto ohledu mnohem náročnější a dostupných nástrojů pro jejich pohodlné ladění také není mnoho. Prostředí pro programování Microsoft pro vývoj ovladačů poskytuje zdarma balík Windows Driver Kit (WDK), ve kterém najdete překladač, potřebnou dokumentaci a řadu užitečných nástrojů. WDK neobsahuje žádné sofistikované vývojové prostředí, ale pro kompilaci ovladačů z příkazové řádky, kterýžto způsob bude používán i v této knize, plně postačuje. Ovladače můžete vytvářet i v příjemném prostředí Microsoft Visual Studia. I pak budete ale potřebovat WDK kvůli knihovnám a hlavičkovým souborům, kde jsou deklarovány konstanty a rutiny, jež ovladače jádra mohou používat. Jak přeložit ovladač Krom zdrojového kódu je k překladu potřeba dalších dvou souborů. Soubor MAKEFILE obsahuje obecné informace pro překladač a jeho obsah odkazuje na výchozí soubor s těmito údaji. Tento odkaz je zapsán následovně: !INCLUDE $(NTMAKEENV)makefile.def Všechny informace týkající se přímo vašeho ovladače, jako například seznam souborů se zdrojovým kódem či seznam knihoven, se nachází v souboru SOURCES, jehož formát vidíte na výpisu 3.1.

Prostředí pro programování

69

3

Vývoj ovladačů

jádra

Výpis 3.1: Formát souboru SOURCES

TARGETNAME=<jméno>

TARGETTYPE=<typ_výsledného_souboru>

SOURCES= <seznam_souborů_se_zdrojovým_kódem>

INCLUDES= <adresáře_s_hlavičkovými_soubory>

Jedná se v podstatě jen o přiřazení hodnot do proměnných prostředí, které se pak používají při

překladu. Výpis 3.1 obsahuje pouze ty nejdůležitější z nich. Jejich význam a formát hodnot shr

nuje tabulka 3.1.

Tabulka 3.1: Formát a význam několika základních proměnných v souboru SOURCES

Proměnná Formát hodnoty Význam pro překladač

TARGETNAME Název výsledného

souboru (bez přípony)

Udává název souboru (bez přípony), který lin

ker vytvoří z přeloženého zdrojového kódu

TARGETTYPE DRIVER, LIBRARY

nebo PROGRAM

Určuje typ výsledného spustitelného souboru.

Pro ovladače platí hodnota DRIVER a pro apli

kace běžící v uživatelském režimu PROGRAM.

Pokud chcete vytvořit knihovnu DLL, použijte

hodnotu LIBRARY.

SOURCES Seznam souborů. Oddělova

čem je mezera. Pokud se

znam zasahuje do více řádků,

musí na konci každého z nich

být zpětné lomítko „“

Seznam všech souborů, které obsahují zdrojo

vý kód ovladače.

INCLUDES Seznam adresářů. Formát je

stejný jako u proměnné

SOURCES.

Seznam adresářů, ve kterých bude překladač vy

hledávat hlavičkové soubory. Adresáře s hlavičko

vými soubory WDK se prohledávají automaticky.

Spuštění prostředí překladače a následný překlad zdrojových kódů ovladače můžete provést ná

sledovně:

V nabídce Start v sekci Programy (ve Windows Vista a Windows 7 se tato položka jme

nuje Všechny programy) vyberte položku Windows Driver Kits.

Zobrazí se seznam všech verzí WDK, které máte nainstalovány. Vyberte tu nejaktuálněj

ší – v době psaní této kapitoly se jednalo o WDK 7600.16385.

Prostředí překladače se skrývá pod volbou Build Environments.

Nyní vyberte operační systém, pro který chcete ovladač zkompilovat. Novější verze OS

umožňují ovladačům využívat nové funkce. Na druhou stranu i ovladač přeložený pro

Windows 7 můžete bez problémů rozchodit na Windows XP, pokud nevyužívá specifik

novější verze.

Pokud chcete přeložit ovladač například pro Windows 7, vyberte položku Windows 7.

Nyní je třeba vybrat architekturu procesoru. Překladač WDK dokáže kompilovat do in

strukční sady procesorů x86, AMD64 (x64) a Ithanium (ia64). Architektura x86 impli

kuje 32bitový operační systém. Pokud chcete ovladač používat na 64bitových Windows,

zvolte x64.

Kapitola 3 Vývoj ovladačů jádra

70

Dále se musíte rozhodnout, zda chcete svůj ovladač přeložit v prostředí tzv. Free build,

nebo Checked build. Prostředí Free build je ekvivalentní modu Release z aplikace

Microsoft Visual Studio; překladač provádí všechny dostupné optimalizace kódu a ignoruje

makra jako ASSERT, KdPrint či KdPrintEx. Prostředí Checked build slouží pro testo

vání. Překladač neprovádí optimalizace, které činí kód binárky méně čitelným pro disas

semblery, a makra ASSERT, KdPrint, KdPrintEx a další provádějí svoji normální činnost:

ASSERT v případě vyhodnocení zadaného výrazu na FALSE pošle zprávu debuggeru

s informacemi o tom, na jakém řádku jakého souboru zdrojového kódu k selhání došlo,

KdPrint a KdPrintEx se překládají na volání DbgPrint a DbgPrintEx. Prostředí dále

definuje symbol DBG na hodnotu 1, čehož lze využít při podmíněném překladu.

Tip: možnostem ladění ovladačů včetně použití maker ASSERT, KdPrint, KdPrintEx,

DbgPrint a DbgPrintEx se věnuje část „Několik poznámek k ladění ovladačů“ níže v této

kapitole.

Pokud ovladač chcete testovat, použijte nastavení Checked build. Pokud si myslíte, že

již může být nasazen do „ostrého provozu“, přeložte jej pomocí Free build.

Rozhodnete-li se například pro architekturu x64 a chcete-li do ovladače přilinkovat

i dodatečné ladicí informace, zvolte položku x64 Checked Build Environment.

Dosavadní postup vidíte na obrázku 3.3.

Spustí se konzole Příkazového řádku nastavená pro účely překladače WDK. Nyní se

pomocí standardních příkazů přesuňte do adresáře, kde se nachází soubory MAKEFILE

a SOURCES vašeho ovladače.

Příkazem build zahájíte překlad a následné linkování. Překladač a linker vypisují prů

běžný stav do konzole. Chování příkazu lze ovlivnit mnoha parametry příkazové řádky.

Některé možnosti ukazuje tabulka 3.2.

Tabulka 3.2: Ovlivnění chování příkazu build pomocí příkazové řádky

Příkaz Popis /c Před zahájením překladu odstraní všechny existující objektové soubory. /C Před zahájením překladu odstraní soubory statických knihoven .lib. /g Při výpisu varování, chyb a výsledků překladu jednotlivé aspekty odlišuje

barvami. Chyby jsou tisknuty červeně, varování žlutě a počet vytvořených

spustitelných souborů zeleně.

/h Nevypisuje podrobné informace o překladu do konzole. /w Do konzole jsou zobrazována i varování. /T Zobrazí úplný strom závislostí. /$ Zobrazí úplný strom závislostí hierarchicky.

Prostředí pro programování

71

3

Vývoj ovladačů

jádra

Pokud překlad a linkování proběhly úspěšně, vytvoří se ve složce se souborem MAKE

FILE nový adresář, v němž najdete i spustitelný soubor vašeho ovladače. Úspěšný pře

klad a linkování vidíte na obrázku 3.4.

V případě, že se při překladu či linkování vyskytly problémy (viz obrázek 3.5), překladač

(resp. linker) vypíše hlášení o neúspěchu a vytvoří dva nové textové soubory. Soubor

s příponou .wrn obsahuje varování. Ta nebrání v úspěšném dokončení překladu a lin

kování, ale zvlášť při programování ovladačů je žádoucí, aby se žádná neobjevovala. Do

souboru s příponou .err překladač (resp. linker) vypíše informace o nalezených chy

bách. Jedná se například o špatnou syntaxi jazykových konstrukcí či hlášení o použití

neexistující proměnné či konstanty.

Obrázek 3.3: Výběr nastavení prostředí překladače WDK

Obrázek 3.4: Výstup při úspěšné kompilaci Poznámka: Balík Windows Driver Kit se za dob, kdy nejnovější verze operačního systému nesla název Windows Server 2003, nesl jméno Driver Development Kit (DDK). Kapitola 3 Vývoj ovladačů jádra 72 Obrázek 3.5: Během kompilace (linkování) byly nalezeny chyby

Tip: U ovladačů, které tvoří součást malware, se občas stává, že uniknou jejich verze určené pro la

dění – tedy obsahují odkaz na soubor .pdb s ladicími informacemi. Tento odkaz je uložen ve for

mě absolutní cesty a může poskytnout cenné vodítko nejen k identifikaci typu malware (podle

jména souboru ovladače to často není možné, protože může být generováno náhodně), ale i ke

zjištění základních informací o autorovi. Vše závisí na tom, jaká jména autor volil pro složky, které

se v odkazu nachází. Načtení ovladače do jádra Spustitelný soubor ovladače není možné (za běžného nastavení) načíst do jádra systému klasickým poklepáním, jak tomu je v případě běžné aplikace. Na rozdíl od spuštění programu se může jednat o relativně složitou operaci a existuje více cest, jak ji provést. Čistý a oficiální způsob Nejjednodušší způsob načtení ovladače do jádra spočívá ve vytvoření služby, která daný ovladač popisuje (pamatujte, Windows interně reprezentují ovladače jako služby) a jejím následném spuštění. Ukázkový kód vidíte na výpisech 3.2 a 3.3. Jedná se o techniku plně dokumentovanou a „zdvořilou“ k operačnímu systému. Pravděpodobně nenajdete mnoho legitimních důvodů, proč ji nepoužít. Pro vytvoření nové služby je nejprve nutné získat potřebná oprávnění k databázi služeb, kterou spravuje správce služeb (SCM). K tomuto účelu slouží funkce OpenSCManagerW, která jako své argumenty bere název počítače, k jehož databázi služeb chcete získat přístup, název konkrétní databáze a přístupová práva, která chcete získat. Pokud jako první dva parametry dostane hodnotu NULL, rutina se pokusí zajistit požadovaná oprávnění k právě používané databázi na lokálním počítači. Pro instalaci služby je nutné oprávnění SC_MANAGER_CREATE_SERVICE. Pokud se podaří

Načtení ovladače do jádra

73

3

Vývoj ovladačů

jádra

přístup získat, funkce vrátí handle databáze, kterým se bude proces prokazovat při volání dalších

rutin pro komunikaci se SCM.

Po úspěšném získání oprávnění následuje vlastní vytvoření služby – volání funkce Create

ServiceW. Tato rutina má mnoho parametrů, protože pokrývá veškeré možnosti nastavení všech

typů služeb. Pro ovladače má význam druhý parametr (interní jméno), pátý parametr, který určuje

typ služby (pro ovladač je rezervována hodnota SERVICE_KERNEL_DRIVER) a šestý a osmý pa

rametr. Šestým parametrem ovlivňujete, kdy může být služba spuštěna. V ukázkovém kódu se pře

dává hodnota SERVICE_DEMAND_START, která určuje, že ovladač popsaný touto službou bude

načten do jádra „na požádání“ libovolného programu s dostatečným oprávněním. Osmý parametr

specifikuje jméno souboru ovladače.

Pokud volání CreateServiceW uspěje, službu ovladače se podařilo úspěšně nainstalovat. Nyní

je třeba uklidit prostředky alokované jak při vlastní instalaci, tak při získávání přístupu k data

bázi služeb. Tento úkol patří rutině CloseServiceHandle, která je zodpovědná za uvolnění

prostředků spojených s libovolným handle od služby (která vrací například CreateServiceW)

či databáze služeb (které vzniká při úspěšném volání OpenSCManagerW).

Výpis 3.2: Vytvoření služby ovladače

BOOL scmInstallDriver (PWCHAR DriverName, PWCHAR FileName)

{

BOOL ret = FALSE;

SC_HANDLE hservice = NULL;

hservice = CreateServiceW(hmanager_inst, DriverName, NULL,

SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,

SERVICE_ERROR_NORMAL, FileName, NULL, NULL, NULL, NULL, NULL);

ret = hservice!= NULL;

if (ret)

CloseServiceHandle(hservice);

return ret;

}

Výpis 3.3 obsahuje zdrojový kód rutiny scmLoadDriver, která jako parametr vezme interní

jméno ovladače a pokusí se jej načíst do jádra systému.

Pro načtení ovladače do jádra je třeba získat oprávnění pro spuštění služby, která jej reprezentu

je. Aby mohla získat dané oprávnění, musí nejprve funkce scmLoadDriver získat přístup do

databáze služeb. Tentokrát postačí oprávnění SC_MANAGER_CONNECT, které umožňuje

pracovat s jednotlivými službami.

Po úspěšném připojení k databázi služeb se scmLoadDriver pokusí získat ke službě ovladače prá

vo SERVICE_START pomocí volání OpenServiceW. Bez tohoto oprávnění není možné službu

spustit.

Spuštění služby ovladače se projeví načtením příslušného souboru s příponou .sys do jádra.

Samotnou akci provádí rutina StartServiceW. Tato funkce krom handle cílové služby bere

i další dva argumenty, jež umožňují spouštěné entitě předat parametry obdobně jako se předá

vají parametry příkazového řádku při vytváření nového procesu. Ovladače tento mechanismus

nepodporují, a proto na hodnotách těchto argumentů nezáleží.

Kapitola 3 Vývoj ovladačů jádra

74

Výpis 3.3: Načtení ovladače do jádra

BOOL scmLoadDriver (PWCHAR DriverName)

{

SC_HANDLE hservice = NULL;

BOOL ret = FALSE;

hservice = OpenServiceW(hmanager_connect, DriverName, SERVICE_START);

ret = hservice!= NULL;

if (ret) {

ret = StartServiceW(hservice, 0, NULL);

CloseServiceHandle(hservice);

}

return ret;

}

Pokud již ovladač není v jádře potřeba, může být z paměti odstraněn. Postup, který demonstruje

rutina scmUnloadDriver na výpisu 3.4 se velmi podobá předchozím; scmUnloadDriver nej

prve získá přístup k databázi služeb, následně k službě se zadaným interním jménem a pošle jí

řídící příkaz SERVICE_CONTROL_STOP, kterým požaduje okamžité zastavení činnosti.

K vyslání tohoto příkazu je nutné získat k cílové entitě oprávnění SERVICE_STOP.

Výpis 3.4: Uvolnění ovladače z jádra

BOOL scmUnloadDriver (PWCHAR DriverName)

{

SERVICE_STATUS ss;

SC_HANDLE hservice = NULL;

BOOL ret = FALSE;

hservice = OpenServiceW(hmanager_connect, DriverName, SERVICE_STOP);

ret = hservice!= NULL;

if (ret) {

ret = ControlService(hservice, SERVICE_CONTROL_STOP, &ss);

CloseServiceHandle(hservice);

}

return ret;

}

Protože služby reprezentují vysoce privilegované aplikace a ovladače, není pro ně příkaz k za

stavení činnosti závazný. Za určitých okolností by zastavení nějaké služby mohlo znamenat

ohrožení pro celý systém. Jak se toto „odmítnutí“ implementuje u ovladačů, najdete na příkladu

Hello World dále v této kapitole.

Odinstalování služby (funkce scmUninstallDriver na výpisu 3.5) spočívá pouze v získání

dostatečných oprávnění k dané službě a k volání rutiny DeleteService.

Načtení ovladače do jádra

75

3

Vývoj ovladačů

jádra

Výpis 3.5: Smazání služby ovladače BOOL scmUninstallDriver (PWCHAR DriverName) { SC_HANDLE hservice = NULL; BOOL ret = FALSE; hservice = OpenServiceW(hmanager_connect, DriverName, DELETE); ret = hservice!= NULL; if (ret) { ret = DeleteService(hservice); CloseServiceHandle(hservice); } return ret; }

Upozornění: Správce služeb umožňuje odstranit ze systému i službu, která je právě aktivní. Funk

ce DeleteService pouze smaže klíče registru, které vytvořilo volání CreateService při in

stalaci. Služba nedostane žádnou informaci o tom, že již v systému není nainstalována a může

běžet až do restartu počítače. Zdrojové kódy rutin uvedených na výpisech 3.2 až 3.5 naleznete na internetových stránkách knihy v projektu drv v souboru scmDrivers.c.

Web: www.jadro-windows.cz/projekty/drv Méně známý způsob (nativní funkce NtLoadDriver) Mezi méně známé způsoby načítání ovladače do jádra systému patří použití nativní funkce NtLoadDriver. Před seznámením s podrobnostmi této metody je třeba uvést několik základních fakt o nativních funkcích Windows API. Již víte, že nativní funkce exportuje knihovna ntdll.dll, která má na starost komunikaci s jádrem. Na rozdíl od dokumentovaných rutin Windows API na vyšších vrstvách, které vrací nenulovou hodnotu v případě úspěchu a nulu, když daná operace selže, nativní funkce většinou pracují s návratovou hodnotou typu NTSTATUS. Jedná se o celé číslo bez znaménka, jehož význam vysvětluje tabulka 3.3.

Upozornění: Mnoho funkcí dostupných ovladačům jádra též vrací hodnotu NTSTATUS. Při zkou

mání jejich významu si pečlivě přečtěte, jaké návratové kódy mohou vrátit. Rozdělení návratových

hodnot může být ošidné. I když funkce vrátí hodnotu spadající do kategorie „úspěch“, nemusí to

znamenat, že daná operace byla úspěšně provedena. Úspěch operace v obecném případě garan

tuje pouze návratová hodnota STATUS_SUCCESS. Kapitola 3 Vývoj ovladačů jádra 76 Tabulka 3.3: Význam hodnot NTSTATUS

Hodnota či rozsah hodnot Význam

0x00000000 – 0x3FFFFFFF

(úspěch)

Operace byla úspěšně provedena. Ideální případ nastává při ná

vratové hodnotě 0, které odpovídá konstanta STATUS_ SUCCESS.

0x40000000 – 0x7FFFFFFF

(informace)

Během provádění operace došlo k události, která její úspěch vý

znamně neovlivnila, ale přesto stojí za povšimnutí.

0x80000000 – 0xBFFFFFFF

(varování)

Během provádění operace se vyskytl problém, který nepřímo

brání jejímu dokončení. To nastává například v případě, že vý

sledky operace nelze zapsat do bufferu předávaného v paramet

ru volání, protože je příliš malý.

0xC0000001 – 0xFFFFFFFF

(chyba)

Při pokusu o provedení operace došlo k chybě.

Poznámka: Pro pohodlnější testování, do které kategorie určitá hodnota NTSTATUS patří, můžete

využít některá z následujících maker:

NT_SUCCESS – vyhodnotí se jako TRUE, pokud zadaná hodnota patří do kategorie „úspěch“ či

„informace“. V ostatních případech vrátí FALSE.

NT_INFORMATION – nabývá hodnoty TRUE právě tehdy, když zadaná hodnota patří do kate

gorie „informace“.

NT_WARNING – pokud hodnota spadá do kategorie „varování“, vrátí TRUE. V jiném případě

vrací FALSE.

NT_ERROR – vyhodnotí se na TRUE pouze v případě hodnoty z oblasti „chyba“.

Další rozdíl mezi standardními rutinami Windows API a nativními funkcemi spočívá v práci s řetězci. Standardní Windows API pracuje s tzv. nulou ukončenými řetězci. Takové řetězce jsou tvořeny posloupností znaků ukončenou znakem s hodnotou nula a jejich výhoda spočívá v tom, že není nutné si explicitně pamatovat jejich délku (v případě potřeby se vypočítá jako rozdíl adresy počátku řetězce a adresy koncového nulového znaku). Nativní funkce pracují s řetězci reprezentovanými strukturami UNICODE_STRING a ANSI_STRING. Tyto struktury v sobě uchovávají nejenom obsah celého řetězce, ale i jeho délku v bajtech. Protože jádro Windows pracuje téměř výhradně s řetězci ve formátu Unicode, používají se v drtivé většině případů struktury UNICODE_STRING. Načtení ovladače pomocí volání NtLoadDriver lze rozdělit do několika kroků. Nejprve je třeba v registru manuálně vytvořit klíč, který bude reprezentovat službu našeho ovladače a vyplnit hodnoty Start, Type a ImagePath. Tyto operace dělá funkce ntldInstallDriver z výpisu 3.6. Odinstalování ovladače spočívá ve smazání klíče služby z registru (rutina ntldUninstallDriver). Výpis 3.6: Načtení a uvolnění ovladače z jádra BOOL ntldInstallDriver (PWCHAR DriverName, PWCHAR FileName) { DWORD start = SERVICE_DEMAND_START;

Načtení ovladače do jádra

77

3

Vývoj ovladačů

jádra

DWORD type = SERVICE_KERNEL_DRIVER;

BOOL ret = FALSE;

HKEY driverkey = NULL;

LONG res = 0;

UNICODE_STRING uFullFileName;

DWORD FileNameSize = (DWORD) (wcslen(FileName) + 1) * sizeof(WCHAR);

res = RegCreateKeyExW(serviceskey, DriverName, 0, NULL, 0, KEY_ALL_ACCESS,

NULL, &driverkey, NULL);

ret = res == ERROR_SUCCESS;

if (ret) {

_PrepareFullName(FileName, L"\??\", &uFullFileName);

if (ret) {

res = RegSetValueExW(driverkey, L"ImagePath", 0, REG_SZ,

(PVOID)uFullFileName.Buffer,

uFullFileName.Length + sizeof(WCHAR));

ret = res == ERROR_SUCCESS;

if (ret) {

res = RegSetValueExW(driverkey, L"Start", 0, REG_DWORD, (PVOID)&start,

sizeof(start));

ret = res == ERROR_SUCCESS;

if (ret) {

res = RegSetValueExW(driverkey, L"Type", 0, REG_DWORD, (PVOID)&type,

sizeof(type));

ret = res == ERROR_SUCCESS;

if (!ret)

RegDeleteKeyW(serviceskey, DriverName);

} else RegDeleteKeyW(serviceskey, DriverName);

} else RegDeleteKeyW(serviceskey, DriverName);

_FreeFullName(&uFullFileName);

}

RegCloseKey(driverkey);

}

return ret;

}

BOOL ntldUninstallDriver (PWCHAR DriverName)

{

BOOL ret = FALSE;

LONG res = 0;

ret = DeleteRegistryKey(serviceskey, DriverName);

return ret;

}

Kapitola 3 Vývoj ovladačů jádra

78

Po jeho úspěšném vytvoření stačí název klíče předat jako parametr nativní funkci NtLoad

Driver. Protože tato rutina akceptuje řetězec ve formátu UNICODE_STRING, nejprve je nut

né nulou ukončený řetězec Unicode (typ PWCHAR) do této podoby převést. Konverze se provádí

pomocí procedury RtlInitUnicodeString, která pro zadaný nulou ukončený řetězec vytvoří

strukturu UNICODE_STRING, jež jej popisuje. Potřebné definice vidíte ve výpisu 3.7. Výpis 3.8

ukazuje přímé použití NtLoadDriver k načtení ovladače do jádra a NtUnloadDriver pro je

ho uvolnění.

Výpis 3.7: Definice datových typů a konstant používaných nativními funkcemi Windows API

#define STATUS_SUCCESS 0x00000000L

#define STATUS_UNSUCCESSFUL 0xC0000001L

typedef struct _UNICODE_STRING {

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

typedef VOID (NTAPI *RTLINITUNICODESTRING)(PUNICODE_STRING UnicodeString,

PWCHAR WideString);

typedef NTSTATUS (NTAPI *NTLOADDRIVER)(PUNICODE_STRING DriverName);

typedef NTSTATUS (NTAPI *NTUNLOADDRIVER)(PUNICODE_STRING DriverName);

Rutina NtUnloadDriver je párová k NtLoadDriver. Provádí přesně opačnou operaci. Za pa

rametr též bere název klíče služby ovladače. Obě nativní funkce indikují úspěch či neúspěch celé

operace hodnotou NTSTATUS.

Výpis 3.8: Načtení a uvolnění ovladače

BOOL ntldLoadDriver (PWCHAR DriverName)

{

BOOL ret = FALSE;

UNICODE_STRING uFullName;

NTSTATUS status = STATUS_UNSUCCESSFUL;

ret = _PrepareFullName(DriverName, NTLD_NAME_PREFIX, &uFullName);

if (ret) {

status = _NtLoadDriver(&uFullName);

ret = status == STATUS_SUCCESS;

_FreeFullName(&uFullName);

}

return ret;

}

BOOL ntldUnloadDriver (PWCHAR DriverName)

{

BOOL ret = FALSE;

Načtení ovladače do jádra

79

3

Vývoj ovladačů

jádra

UNICODE_STRING uFullName; NTSTATUS status = STATUS_UNSUCCESSFUL; ret = _PrepareFullName(DriverName, NTLD_NAME_PREFIX, &uFullName); if (ret) { status = _NtUnloadDriver(&uFullName); ret = status == STATUS_SUCCESS; _FreeFullName(&uFullName); } return ret; } Funkce pro práci s ovladači pomocí nativních funkcí NtLoadDriver a NtUnloadDriver naleznete na internetových stránkách knihy v projektu drv v souboru ntregDrivers.c.

Web: http://www.jadro-windows.cz/projekty/drv

Poznámka: Zde ukázaný postup je téměř ekvivalentní k práci s ovladačem pomocí SCM. Správce

služeb při instalaci služby ovladače vytvoří potřebné klíče a její spuštění provádí též přes volání

NtLoadDriver. Jediný rozdíl mezi oběma postupy tkví v tom, že při manuálním vytvoření po

třebných klíčů registru se SCM o nové službě nedozví. Seznam služeb se totiž nachází v paměti

procesu services.exe a registr slouží pouze jako jeho trvalé úložiště. SCM nekontroluje, zda

nějaká aplikace manuálně přidala klíče a hodnoty odpovídající instalaci nové služby. Z tohoto dů

vodu je načtení ovladače jádra přímo pomocí volání NtLoadDriver méně viditelné a často tento

postup najdete ve škodlivých programech.

Tip: Pro instalaci ovladače musí být v klíči služby přítomné buď hodnoty Type, Start a ImagePath,

nebo Name, Type a Start. Pokud není položka ImagePath přítomna, systém předpokládá, že se

soubor ovladače nachází v systémovém adresáři pod jménem <Obsah_položky_Name>.sys. Méně známý způsob (nativní funkce NtSetSystemInformation) Nativní funkce NtSetSystemInformation slouží k úpravě různých aspektů systému. Mezi takto konfigurovatelná „nastavení“ patří i načtení nového ovladače do jádra. Odstranění ovladače z paměti tato rutina neumožňuje. Definici funkce vidíte na výpisu 3.9, ukázku volání pak na výpisu 3.10. Výpis 3.9: Definice nativní funkce Windows API NtSetSystemInformation typedef enum _SYSTEM_INFORMATION_CLASS { SystemBasicInformation, SystemProcessorInformation, SystemPerformanceInformation, Kapitola 3 Vývoj ovladačů jádra 80 SystemTimeOfDayInformation, ... SystemExtendServiceTableInformation, // 38 ... SystemMemoryListInformation, SystemFileCacheInformationEx, MaxSystemInfoClass } SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS; typedef NTSTATUS (NTAPI *NTSETSYSTEMINFORMATION) (SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID Buffer, ULONG Length); NtSetSystemInformation akceptuje tři parametry. První parametr určuje, jaké nastavení systému chce volající změnit. Druhý parametr obsahuje adresu bloku paměti s novými hodnotami a třetí parametr udává délku tohoto bloku. Pro načtení ovladače do jádra slouží hodnota prvního parametru 36 známá též jako System LoadAndCallImage. Systém předpokládá jako druhý parametr strukturu SYSTEM_LOAD_ AND_CALL_IMAGE, která se skládá pouze z řetězce Unicode reprezentovaného pomocí struktury UNICODE_STRING udávajícího název a umístění souboru ovladače. Úspěch operace se dozvíte z návratové hodnoty NTSTATUS. Výpis 3.10: Načtení ovladače pomocí volání NtSetSystemInformation BOOL ntssiLoadDriver(PWCHAR DriverName) { BOOL ret = FALSE; UNICODE_STRING uFullName; NTSTATUS status = STATUS_UNSUCCESSFUL; ret = _PrepareFullName(DriverName, NTSSI_NAME_PREFIX, &uFullName); if (ret) { status = _NtSetSystemInformation(SystemExtendServiceTableInformation, &uFullName,

sizeof(UNICODE_STRING));

ret = status == STATUS_SUCCESS; _FreeFullName(&uFullName); } return ret; }

Upozornění: Ovladač načtený tímto způsobem se celý nachází ve stránkované paměti a nemůže

standardními cestami provádět některé běžné úkony jako například vytváření virtuálních zařízení

za účelem komunikace s aplikacemi běžícími v uživatelském režimu.

Jednoduchý příklad: Klasické „Hello World!“

81

3

Vývoj ovladačů

jádra

Práce s ovladači pomocí utility drv

Výše popsané metody práce s ovladači jádra naleznete na internetových stránkách knihy imple

mentované v konzolové aplikaci drv. Pomocí tohoto programu a příkazového řádku Windows

můžete snadno instalovat vlastní ovladače a načítat je do jádra systému. Program se ovládá násle

dujícími příkazy:

drv –scm install <jmeno_sluzby>

drv –scm load <jmeno_sluzby>

drv –scm unload <jmeno_sluzby>

drv –scm uninstall <jmeno_sluzby>

drv –ntld install <jmeno_sluzby>

drv –ntld load <jmeno_sluzby>

drv –ntld unload <jmeno_sluzby>

drv –ntld uninstall <jmeno_sluzby>

drv –ntssi load <jmeno_souboru>

První parametr udává, kterou metodu má program použít (scm – správce služeb, ntld – nativní funkci

NtLoadDriver, ntssi – nativní funkci NtSetSystemInformation). Podle hodnoty druhého se

utilita rozhoduje, jakou akci provést. Nativní API funkce NtSetSystemInformation podporuje pou

ze načtení ovladače do jádra, ostatní způsoby vyžadují před vlastním načtením vytvoření služby, která

ovladač popíše.

Web: http://www.jadro-windows.cz/projekty/drv Jednoduchý příklad: Klasické „Hello World!“ V mnoha publikacích narazíte na ukázkové příklady, pro které se vžilo označení „Hello World!“ Cílem ukázek tohoto typu je většinou nějakým zajímavým způsobem vypsat ono anglické sousloví na obrazovku. Například zobrazením textu v dialogovém okně. O tento typ ukázky nebudete ochuzeni ani v této knize, protože dobře poslouží k ilustraci základní architektury ovladače jádra.

Web: http://www.jadro-windows.cz/projekty/hello Ovladač hello.sys se skládá ze tří částí – souboru MAKEFILE se standardním obsahem, souboru SOURCES s instrukcemi pro překladač balíku WDK (výpis 3.11) a souboru hello.c, který obsahuje vlastní zdrojový kód (viz výpis 3.12). Překladač se ze souboru SOURCES dozví, že má přeložit zdrojový kód v souboru hello.c (řádek 3) jako ovladač (řádek 2) se jménem hello (řádek 1). Linker ovladačům automaticky do jména souboru doplní příponu .sys. Kapitola 3 Vývoj ovladačů jádra 82 Výpis 3.11: Soubor SOURCES TARGETNAME=hello TARGETTYPE=DRIVER SOURCES= hello.c Soubor hello.c obsahuje dvě rutiny – DriverEntry a DriverUnload. DriverEntry je analogií k funkcím main a WinMain známých z prostředí programování běžných aplikací. Systém této funkci předá řízení během načítání ovladače do jádra. Úkolem podprogramu je provést nezbytnou inicializaci, aby po svém načtení do jádra mohl ovladač okamžitě fungovat. Výpis 3.12: Soubor hello.c #include <ntddk.h> VOID DriverUnload(PDRIVER_OBJECT DriverObject) { DbgPrint ("Good Bye"); return; } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NTSTATUS status = STATUS_UNSUCCESSFUL; DbgPrint("HELLO WORLD!"); DriverObject->DriverUnload = DriverUnload; status = STATUS_SUCCESS; return status; } Inicializace nemusí vždy proběhnout úspěšně. Proto pokud DriverEntry vrátí hodnotu NTSTATUS odpovídající chybě, načtení ovladače do jádra skončí nezdarem. DriverEntry akceptuje dva parametry – adresu struktury DRIVER_OBJECT, která reprezentuje ovladač jako entitu v jádře a obsahuje veškeré informace a nastavení, a úplný název klíče služby, jež ovladač reprezentuje.

Poznámka: Pokud ovladač dostanete do jádra pomocí výše popsané nativní funkce NtSet

SystemInformation, rutina DriverEntry obdrží oba parametry nastavené na hodnotu

NULL. Takto zavedený ovladač nemá žádnou strukturu DRIVER_OBJECT, která by jej v jádře repre

zentovala, a ani službu, jež by jej zastupovala ve správci služeb. Navíc je celý soubor ovladače tvo

řen stránkovanou pamětí, jak uvádí poznámka výše. Krom ladicího výpisu „HELLO WORLD!“ provádí rutina DriverEntry velmi důležitou věc – umožňuje ovladač uvolnit z paměti za běhu operačního systému. Při povídání o službách jste se dozvěděli, že okolí jim může zasílat různé požadavky, kterým služby ale nemusí vyhovět. Něco podobného platí i pro ovladače. Nemusí dovolit své odstranění z paměti jádra.

Několik poznámek k ladění ovladačů

83

3

Vývoj ovladačů

jádra

Ovladač lze dynamicky uvolnit z jádra právě tehdy, když má nastavenou proceduru, které sys

tém předá řízení během odstraňování z paměti. Tato rutina se často nazývá DriverUnload

a její úkol je opačný k úkolu funkce DriverEntry – uvolnit všechny prostředky, jenž ovladač

používal během své přítomnosti v jádře. Adresa této rutiny se musí nastavit do položky

DriverUnload struktury DRIVER_OBJECT. Na rozdíl od DriverEntry, která má přímý vliv

na úspěch zavedení ovladače do jádra, tato „uklízecí“ procedura nemůže nijak ovlivnit úspěch či

neúspěch jeho odstranění.

Protože inicializace ovladače hello.sys spočívá pouze v nastavení „uklízecí“ procedury a ve vypsá

ní známého anglického sousloví, DriverUnload nemá co uklízet a pouze vypíše řetězec „Good Bye“.

Ovladače nemohou snadno zobrazovat textové řetězce na obrazovku například pomocí dialogo

vých oken. Nejjednodušším způsobem je použití ladicích funkcí, mezi které patří DbgPrint, jež

pošle zadaný řetězec debuggeru jádra, je-li v systému přítomen.

Volání DbgPrint lze monitorovat i s pomocí jednoduché aplikace DebugView, kterou naleznete

na serveru www.sysinternals.com, nebo internetových stránkách této knihy. Pro zachytávání

volání DbgPrint je nutné program spustit s administrátorskými právy a v menu View zaškrtnout

položky Capture Kernel (zachytávat události jádra) a Enable Ve r b o s e Kernel Output (povolit čitelné

zobrazování ladicích výpisů jádra). Po načtení ovladače hello.sys do jádra a jeho následném od

stranění uvidíte v bílém poli programu něco podobného obrázku 3.6. K práci s ovladačem můžete

využít program drv.exe popsaném na konci předchozího oddílu. Ukázku zavedení ovladače do

jádra a jeho následného odstranění pomocí této utility vidíte na obrázku 3.7.

Obrázek 3.6: Projevy ovladače hello.sys v programu DebugView

Několik poznámek k ladění ovladačů

K ladění běžných aplikací většinou stačí program zvaný debugger, který umožňuje na specific

kých místech běh aplikace zastavit, krokovat či prohlížet aktuální hodnoty proměnných, což

velmi pomáhá při odhalování nejrůznějších chyb. Debugger je aplikace jako každá jiná – běží

v uživatelském režimu a pouze využívá podpory pro ladění, kterou systém Windows disponuje.

Pro ladění ovladačů obyčejný debugger nestačí, protože ovladače běží v režimu jádra a některé

akce, které provádějí, snadno kontrolovat nelze. Metody na odhalování chyb ale existují.

Mezi nejúčinnější metody patří prevence. Než vámi vytvořený ovladač pustíte do jádra operač

ního systému, pečlivě si pročtěte celý zdrojový kód a přemýšlejte, za jakých okolností bude sys

tém jeho jednotlivé části vykonávat a jestli nemůže dojít k uváznutí (deadlock) či špatné

manipulaci se sdílenými daty. Nedělejte unáhlené předpoklady, že některé kusy kódu fungují

správně, protože jsou krátké a provádějí pouze jednoduché operace. Chyba se často vyskytuje

právě v nich. Ačkoliv se tato technika může jevit jako velmi zdlouhavá a otravná, překvapivě

ušetří spoustu času a vede k odhalení řady malých chyb.

Kapitola 3 Vývoj ovladačů jádra

84

Obrázek 3.7: Použití drv.exe k manipulaci s ovladačem hello.sys

Další způsob, jak se vyhnout dlouhému ladění, spočívá v přesunutí složitosti řešeného problému

z režimu jádra do uživatelské aplikace. Struktura ovladače se tak velmi zjednoduší a ladění apli

kace lze provést standardními postupy. Ovladač pak funguje pouze jako prodloužená ruka apli

kace – umožňuje jí provádět jinak zakázané operace.

Prevence není nikdy stoprocentně účinná. Nemusí odhalit problémy složitějšího charakteru,

které se projeví jen za velmi specifických podmínek. Jedná se například o chyby, jež se objeví

pouze při specifickém pořadí plánování vláken na procesoru. Pro jejich odhalení je potřeba po

užít prostředky umožňující chování ovladače sledovat v reálném čase.

DbgPrint

Na rozdíl od obyčejných programů ovladače nemohou jednoduše zobrazovat dialogová okna se

zprávami, aby oznamovaly svůj aktuální stav. Mohou ale využívat rutiny DbgPrint, která (podob

ně jako funkce OutputDebugString z rozhraní Windows API) pošle zadaný text debuggeru.

Debuggery jádra umožňují při ladění ovladačů používat podobné postupy jako při ladění apli

kací. Dokáží umisťovat breakpointy a prohlížet obsah paměti. Jejich ovládání je méně přívětivé,

protože nemohou využívat většiny služeb jádra. Musí totiž běžet na nižší úrovni než většina

ovladačů, aby mohly kontrolovat jejich běh. Proto některé mechanismy jádra obcházejí a činí

operační systém méně stabilním.

Pro monitorování volání funkce DbgPrint nepotřebujete plnohodnotný debugger jádra. Postaču

jící práci odvede i utilita DebugView, která pouze zobrazuje výstupy z funkcí, jako je právě

DbgPrint či OutputDebugString a jejíž grafické uživatelské rozhraní jste viděli na obrázku 3.6.

DbgPrintEx

Tato rutina plní stejnou úlohu jako DbgPrint, navíc dovoluje volajícímu určit, jaký typ danou

zprávu posílá a jak je tato zpráva závažná; zda jde o pouhou informaci, nebo došlo k závažnému

problému. Deklaraci rutiny vidíte na výpisu 3.13.

Několik poznámek k ladění ovladačů

85

3

Vývoj ovladačů

jádra

Výpis 3.13: Deklarace funkce DbgPrintEx NTSTATUS __cdecl DbgPrintEx( ULONG ComponentId, ULONG Level, PCSTR Format, ... arguments); Hodnota parametru ComponentId určuje typ ovladače, který zprávu zasílá. Možné hodnoty vidíte v tabulce 3.4. Parametr Level určuje závažnost zprávy. Povoleny jsou libovolné hodnoty, doporučuje se ale používat pouze následující:

DPFLTR_ERROR_LEVEL (0) – závažná chyba,

DPFLTR_WARNING_LEVEL (1) – varování,

DPFLTR_TRACE_LEVEL (2) – oznámení o vykonávání určité části kódu (například

určité funkce),

DPFLTR_INFO_LEVEL (3) – nezávažné oznámení jiného druhu.

Ostatní parametry mají stejný význam jako v případě funkce DbgPrint či jiných rutin určených pro formátování řetězců (například printf). Funkce indikuje úspěch vrácením hodnoty typu NTSTATUS. Tabulka 3.4: Hodnoty parametru ComponentId funkce DbgPrintEx a jejich význam

Konstanta Název

komponenty

Popis

DPFLTR_IHVVIDEO_ID IHVVIDEO Ovladač videa.

DPFLTR_IHVAUDIO_ID IHVAUDIO Ovladač zvuku.

DPFLTR_IHVNETWORK_ID IHVNETWORK Síťový ovladač.

DPFLTR_IHVSTREAMING_ID IHVSTREAMING Ovladač pracující s proudem dat (napří

klad dekódující proud zvukových dat).

DPFLTR_IHVBUS_ID IHVBUS Ovladač sběrnice.

DPFLTR_IHVDRIVER_ID IHVDRIVER Jiný typ ovladače. Windows dovolují jednotlivé zprávy na základě hodnot parametrů ComponentId a Level filtrovat; debuggeru doručují jen zprávy s určitými kombinacemi těchto hodnot. Konkrétní nastavení filtrování lze provést buď přímo v debuggerem modifikací daného nastavení přímo v paměti jádra, nebo změnou hodnot klíče HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerDebug Print Filter. Tento klíč obsahuje hodnoty typu DWORD. Název hodnoty odpovídá názvu komponenty (viz druhý sloupeček tabulky 3.4), data jsou interpretována jako bitová maska určující, jak závažné zprávy od daného typu ovladače (závažnost zprávy se posuzuje podle hodnoty parametru Level) bude systém zasílat. Změna tohoto klíče se do nastavení filtrování promítne až po restartu počítače, protože si operační systém tento klíč čte pouze během svého startu. Změna přímo v paměti jádra se projeví okamžitě. Kapitola 3 Vývoj ovladačů jádra 86 Rozhodování, zda bude zpráva zaslána debuggeru, probíhá následovně:

Z hodnoty parametru Level systém vypočítá bitovou masku. Hodnota mezi 0 a 31

včetně odpovídá masce s pouze jedním bitem nastaveným na jedničku. Jedná se o danou

mocninu dvojky. Například hodnota 5 znamená masku 0x00000020 = 32 = 2

5

. Ostatní

hodnoty parametru Level nejsou nijak transformovány a systém je interpretuje přímo

jako bitovou masku.

Následně jádro provede logický součin (operace AND) masky z předchozího kroku

s maskou nastavení filtrování zpráv příslušné komponenty logickým součtem (operace

OR) zkombinovanou s maskou WIN2000. Maska příslušné komponenty je při startu sys

tému načtena z registru z výše jmenovaného klíče a může být později přímo upravena debu

ggerem jádra. Pokud je výsledkem nenulové číslo, zpráva je debuggeru poslána. V opačném

případě zaniká. Pokud bitová maska pro danou komponentu neexistuje, jádra její hodnotu

uvažuje jako nulovou a po operaci logického součtu nabude hodnoty masky WIN2000.

Poznámka: Maska WIN2000 má ve výchozím natavení hodnotu 0x1. Protože je vždy zkombinová

na s maskou natavení filtrování určité komponenty, zajišťuje, že nejnižší bit odpovídající úrovni

DPLFLTR_ERROR_LEVEL bude vždy nastaven na jedničku, a tak hlášení o závažných chybách bu

dou odeslány debuggeru i v případě, že nastavení filtrování pro příslušnou komponentu by takové

jejich odesílání nepovolovalo.

Od Windows Vista se volání DbgPrint chová stejně jako volání DbgPrintEx s parametrem ComponentId DFLTR_DEFAULT_ID a hodnotou Level DPLFLTR_INFO_LEVEL. Na starších verzích operačního systému DbgPrint vždy posílá zprávy debuggeru.

Tip: Program DebugView, který naleznete na stránce www.sysinternals.com, zobrazuje zprávy

nehledě na nastavení filtrování v operačním systému. Při zaškrtnuté volbě Pass Through v menu

Options všechny obdržené zprávy přeposílá dále debuggeru jádra a k filtrování vůbec nedochází.

ASSERT Makro ASSERT slouží testování invariantů a různých podmínek během vykonávání kódu ovladače. Jeho syntaxe je následující: ASSERT(Vyraz) Vyhodnotí-li se obsah výrazu Vyraz na TRUE, makro nedělá nic. V opačném případě pošle zprávu debuggeru jádra. V této zprávě najdete název souboru zdrojového kódu a číslo řádky, kde se dané makro nachází, a také text výrazu, který se vyhodnotil na FALSE. Makro slouží k ověření, že při vykonávání daného kódu platí ještě další podmínky, které ale nejsou testovány pomocí konstrukcí if, protože platnost těchto podmínek je dána implicitně – jinak řečeno, ve finální verzi ovladače takové podmínky platí vždy. Ve finální verzi ovladače se tedy všechna použití makra ASSERT vyhodnotí na TRUE, takže nedochází k odesílání zpráv debuggeru. Makro má výše popsanou sémantiku pouze v případě překladu ovladače v prostředí Checked build, kdy má symbol DBG hodnotu 1. V prostředí Free build, kde symbol DBG má hodnotu 0, nebo není vůbec definován, se makra ASSERT nezahr

Několik poznámek k ladění ovladačů

87

3

Vývoj ovladačů

jádra

nují do vygenerovaného binárního souboru .sys. Překladač se chová tak, jako by ve zdrojovém

kódu vůbec nebyla napsána.

KdPrint a KdPrintEx

Tato makra se v případě překladu zdrojového kódu v prostředí Checked build chovají jako

rutiny DbgPrint a DbgPrintEx. Pokud ovladač překládáte v prostředí Free build, překla

dač tato makra ignoruje. Chová se tedy podobně jako v případě makra ASSERT. Na rozdíl od

volání ostatních funkcí a maker, seznam parametrů pro KdPrint a KdPrintEx musíte uvádět

ve dvojitých kulatých závorkách, například takto:

KdPrint(("Testovaci ladici vypis"));

Důvodem této nutnosti je implementace těchto maker v hlavičkových souborech balíku Win

dows Driver Kit.

WinDbg

Pokud výpisy z DbgPrint k identifikaci problému nestačí, můžete zkusit plnohodnotný debug

ger jádra. Jedním z nich je program WinDbg, který naleznete v balíku Debugging Tools For

Windows, jenž se standardně instaluje společně s WDK. Tento debugger umožňuje za běhu

prohlížet a upravovat obsah paměti jádra. Krokování a breakpointy v režimu jádra však nepod

poruje lokálně. Abyste mohli ovladač krokovat, musíte jej pustit na jiném (třeba i virtuálním)

počítači, na který se potom například pomocí pojmenované roury či sériového portu připojíte.

Protože plně nepodporuje lokální ladění jádra, nemusí tato aplikace ani obcházet mechanismy,

čímž nesnižuje stabilitu celého systému. Grafické uživatelské rozhraní debuggeru vidíte na ob

rázku 3.8.

Pokud hledaná chyba ovladače způsobuje modrou obrazovku smrti, systém většinou na disk

ukládá obsah paměti jádra v okamžiku zjištění problému – tzv. crash dump. WinDbg dokáže ty

to výpisy paměti při selhání analyzovat, a tak můžete vědět přesně, kde chyba nastala, aniž byste

potřebovali druhý stroj, i když třeba jen virtuální.

Obsluha WinDbg není nejpříjemnější – program se ovládá podobně jako příkazová řádka. Pro

diagnostiku většiny problémů s ovladači ale vystačíte jen s několika málo příkazy, které popisují

následující odstavce.

WinDbg umí pracovat s údaji v souborech .pdb, které obsahují například názvy a umístění

proměnných, datových typů a podprogramů. Tyto informace jsou dostupné i pro hlavní modu

ly jádra (ntoskrnl.exe, hal.dll, win32k.sys) a nachází se na stránkách Microsoftu.

Pro korektní nastavení ladicích symbolů stačí v menu File zvolit položku Symbol Path... a do

textového pole vyplnit adresu serveru, která zní

srv*DownstreamStore*http://msdl.microsoft.com/download/symbols

WinDbg z tohoto serveru stahuje ladicí informace k těm souborům, jež zrovna potřebuje. Po

správné konfiguraci symbolů můžete začít s vlastní analýzou souboru crash dump. Kapitola 3 Vývoj ovladačů jádra 88 Obrázek 3.8: Uživatelské rozhraní programu WinDbg

Poznámka: Pokud váš počítač není během práce s debuggerem připojen k Internetu, můžete si

ladicí informace k většině ovladačů od Microsoftu stáhnout na disk a do textového pole ve WinD

bg vyplnit jejich umístění.

Po otev



       
Knihkupectví Knihy.ABZ.cz - online prodej | ABZ Knihy, a.s.
ABZ knihy, a.s.
 
 
 

Knihy.ABZ.cz - knihkupectví online -  © 2004-2019 - ABZ ABZ knihy, a.s. TOPlist