Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wprowadzenie
Te wytyczne dotyczące opracowywania sterowników zostały opracowane przez wiele lat przez deweloperów sterowników w firmie Microsoft. Wraz z upływem czasu, kiedy kierowcy źle się zachowywali i wyciągano wnioski, wnioski te zostały uchwycone i ewoluowały, aby były to zestaw wskazówek. Te najlepsze rozwiązania są używane przez zespół microsoft Surface Hardware do opracowywania i obsługi kodu sterownika urządzenia, który obsługuje unikatowe środowiska sprzętowe surface.
Podobnie jak w przypadku każdego zestawu wytycznych, będą istnieć uzasadnione wyjątki i alternatywne podejścia, które będą równie ważne. Rozważ uwzględnienie tych wytycznych w standardach rozwoju lub użycie ich do tworzenia wytycznych specyficznych dla domeny dla środowiska programistycznego i twoich unikalnych wymagań.
Typowe błędy popełniane przez deweloperów sterowników
Obsługa operacji we/wy
- Uzyskiwanie dostępu do buforów otrzymanych z IOCTL bez sprawdzania długości. Zobacz Niepowodzenie sprawdzania rozmiaru buforów.
- Blokowanie we/wy w kontekście wątku użytkownika lub kontekstu losowego wątku. Zobacz Wprowadzenie do obiektów dyspozytora jądra.
- Wysyłanie synchronicznych operacji we/wy do innego sterownika bez przekroczenia limitu czasu. Zobacz Wysyłanie żądań wejścia/wyjścia synchronicznie.
- Korzystanie z IOCTL neither-io bez zrozumienia konsekwencji dla zabezpieczeń. Zobacz Używanie ani buforowanych, ani bezpośrednich operacji I/O.
- Niesprawdzanie stanu powrotu WdfRequestForwardToIoQueue lub niepoprawne obsługiwanie błędu, co skutkuje porzuceniem obiektów WDFREQUEST.
- Utrzymywanie elementu WDFREQUEST poza kolejką w stanie nienadającym się do anulowania. Zobacz Zarządzanie kolejkami we/wy, kończenie żądań we/wy i anulowanie żądań we/wy.
- Próba zarządzania anulowaniem przy użyciu funkcji Mark/UnmarkCancelable zamiast używania funkcji IoQueues. Zobacz Obiekty kolejki Frameworka.
- Nie znając różnicy między operacjami oczyszczania i zamykania plików. Zobacz Błędy obsługi operacji oczyszczania i zamykania.
- Pomijanie potencjalnych rekursji związanych z zakończeniem operacji we/wy oraz ponownym przesłaniem z procedury kończenia.
- Nie sprecyzowano atrybutów zarządzania energią dla WDFQUEUEs. Nie dokumentowanie wyboru zarządzania energią wyraźnie. Jest to główna przyczyna sprawdzenia błędu 0x9F: DRIVER_POWER_STATE_FAILURE w sterownikach WDF. Po usunięciu urządzenia platforma przeczyści we/wy z kolejki zarządzanej przez zasilanie i kolejki zarządzanej bez zasilania w różnych etapach procesu usuwania. Kolejki niezarządzane przez energię są czyszczane po ostatecznym poleceniu IRP_MN_REMOVE_DEVICE. Dlatego jeśli przetrzymujesz we/wy w kolejce zarządzanej bez zasilania, dobrym rozwiązaniem jest jawne przeczyszczanie operacji we/wy w kontekście EvtDeviceSelfManagedIoFlush, aby uniknąć zakleszczenia.
- Nieprzestrzeganie zasad obsługi IRP. Zobacz Błędy obsługi operacji oczyszczania i zamykania.
Synchronizacja
- Użycie blokad w kodzie, który nie potrzebuje ochrony. Nie należy trzymać blokady dla całej funkcji, gdy musi być chroniona tylko niewielka liczba operacji.
- Wzywanie kierowców z zablokowanymi pojazdami. Jest to główna przyczyna zakleszczeń.
- Używanie zakleszonych elementów pierwotnych w celu utworzenia schematu blokowania zamiast używania odpowiedniego systemu zapewniania pierwotnych blokad, takich jak mutex, semafor i spinlocks. Zobacz Wprowadzenie do obiektów Mutex, Wprowadzenie do obiektów semaforów i Wprowadzenie do blokad obrotowych.
- Użycie spinlocka, w którym jakiś typ pasywnej blokady byłby bardziej odpowiedni. Zobacz Fast Mutexes and Guarded Mutexes and Event Objects (Szybkie muteksy i chronione obiekty muteksów i obiektów zdarzeń). Aby uzyskać dodatkową perspektywę dotyczącą blokad, zapoznaj się z artykułem OSR — Stan synchronizacji.
- Wybranie modelu synchronizacji i poziomu wykonywania WDF bez pełnego zrozumienia implikacji. Zobacz Używanie blokad platformy. O ile sterownik nie jest monolitycznym sterownikiem najwyższego poziomu, który bezpośrednio współdziała ze sprzętem, unikaj korzystania z synchronizacji WDF, ponieważ może to prowadzić do zakleszczenia z powodu rekursji.
- Uzyskiwanie kodu KEVENT, semaphore, ERESOURCE, UnsafeFastMutex w kontekście wielu wątków bez wprowadzania regionu krytycznego. Może to prowadzić do ataku DOS, ponieważ wątek trzymający jedną z tych blokad może zostać zawieszony. Zobacz Wprowadzenie do obiektów dyspozytora jądra.
- Przydzielanie metody KEVENT na stosie wątku i powrót do elementu wywołującego, gdy zdarzenie jest nadal używane. Zazwyczaj wykonywane w przypadku użycia z IoBuildSynchronousFsdRequest lub IoBuildDeviceIoControlRequest. Podmiot wywołujący te połączenia powinien upewnić się, że nie zostaną zdjęte ze stosu, dopóki menedżer I/O nie zasygnalizuje zdarzenia po zakończeniu IRP.
- Oczekiwanie na czas nieokreślony w rutynach wysyłkowych. Ogólnie rzecz biorąc, wszelkiego rodzaju oczekiwania w procedurze wysyłania są złą praktyką.
- Nieodpowiednie sprawdzenie ważności obiektu (jeśli blah == NULL) przed jego usunięciem. Zwykle oznacza to, że autor nie ma pełnej wiedzy na temat kodu kontrolującego okres istnienia obiektu.
Zarządzanie obiektami
- Nie tworzenie jawnych relacji nadrzędnych z obiektami WDF. Zobacz Wprowadzenie do obiektów struktury.
- Nadawanie obiektu WDF jako nadrzędnego do WDFDRIVER, zamiast do obiektu, który lepiej zarządza okresem istnienia i optymalizuje użycie pamięci. Na przykład przypisanie elementu WDFREQUEST jako nadrzędnego do urządzenia WDFDEVICE zamiast do IOTARGET. Zobacz Korzystanie z ogólnych obiektów struktury, Cykl życia obiektów struktury i Podsumowanie obiektów struktury.
- Nie przeprowadza ochrony przed uruchomieniem zasobów pamięci udostępnionej, do których uzyskiwano dostęp między sterownikami. Zobacz funkcję ExInitializeRundownProtection.
- Błędnie kolejkowanie tego samego elementu roboczego, gdy poprzedni element znajduje się już w kolejce lub jest już uruchomiony. Może to być problem, jeśli klient przyjmuje założenie, że każdy element roboczy w kolejce zostanie wykonany. Zobacz Korzystanie z elementów roboczych struktury. Aby uzyskać więcej informacji na temat kolejkowania elementów WorkItems, zobacz moduł DMF_QueuedWorkitem w projekcie Driver Module Framework (DMF) — https://github.com/Microsoft/DMF.
- Kolejkowanie czasomierza przed wysłaniem wiadomości, którą czasomierz ma przetworzyć. Zobacz Korzystanie z czasomierzy.
- Wykonywanie operacji w elemecie roboczym, które może blokować lub trwać przez czas nieokreślony.
- Projektowanie rozwiązania, które powoduje napływ elementów roboczych do kolejki. Może to prowadzić do nieodpowiadania systemu lub ataku DOS, jeśli zły facet może kontrolować akcję (np. pompowanie we/wy do sterownika, który kolejkuje nowy element roboczy dla każdego we/wy). Zobacz Używanie elementów roboczych platformy.
- Nie oznacza to, że wywołania zwrotne DPC elementu roboczego zostały uruchomione do ukończenia przed usunięciem obiektu. Zobacz Wytyczne dotyczące pisania procedur DPC i funkcji WdfDpcCancel.
- Tworzenie wątków zamiast używania elementów roboczych dla zadań o krótkim czasie trwania lub niewymagających sondowania. Zobacz Wątki procesów roboczych systemu.
- Nie upewniono się, że wątki zostały uruchomione do ukończenia przed usunięciem lub zwolnieniem sterownika. Aby uzyskać więcej informacji na temat synchronizacji uruchamiania wątków, zapoznaj się z kodem skojarzonym z modułem DMF_Thread w projekcie Driver Module Framework (DMF) — https://github.com/Microsoft/DMF.
- Używanie jednego sterownika do zarządzania urządzeniami, które są różne, ale współzależne, oraz używanie zmiennych globalnych do udostępniania informacji.
Pamięć
- Nie oznaczaj kodu pasywnego wykonywania jako PAGEABLE, jeśli jest to możliwe. Stronicowanie kodu sterownika może zmniejszyć rozmiar kodu sterownika, co zwalnia miejsce systemowe do innych zastosowań. Należy zachować ostrożność podczas oznaczania kodu, który zgłasza IRQL >= DISPATCH_LEVEL lub może być wywoływany na podniesionym poziomie IRQL. Zobacz Kiedy kod i dane powinny być stronicowane, Co sprawia, że sterowniki można stronicować i Wykrywanie kodu, który może być stronicowalny.
- Należy deklarować duże struktury na stercie lub w puli, a nie na stosie. Zobacz Using the KernelStack and Allocating System-Space Memory (Korzystanie z biblioteki KernelStack i przydzielanie pamięci System-Space).
- Niepotrzebne zerowanie kontekstu obiektu WDF. Może to wskazywać na brak jasności co do tego, kiedy pamięć zostanie automatycznie wyzerowana.
Ogólne wytyczne dotyczące sterowników
- Mieszanie elementów pierwotnych WDM i WDF. Używanie elementów pierwotnych WDM, w których można używać elementów pierwotnych WDF. Korzystanie z elementów pierwotnych WDF chroni przed pułapkami, poprawia debugowanie i co ważniejsze sprawia, że sterownik może działać w trybie użytkownika.
- Nadawanie nazw FDO i tworzenie linków symbolicznych, nawet jeśli nie są potrzebne. Zobacz Zarządzanie kontrolą dostępu do sterowników.
- Kopiowanie i wklejanie identyfikatorów GUID oraz innych wartości stałych z przykładowych sterowników.
- Rozważ użycie kodu open source struktury sterowników (DMF) w projekcie sterownika. DMF to rozszerzenie usługi WDF, które umożliwia korzystanie z dodatkowych funkcji dla dewelopera sterowników WDF. Zobacz Wprowadzenie do struktury modułów sterowników.
- Używanie rejestru jako mechanizmu powiadamiania między procesami lub jako skrzynki pocztowej. Aby uzyskać alternatywę, zobacz moduły DMF_NotifyUserWithEvent i DMF_NotifyUserWithRequest dostępne w projekcie DMF — https://github.com/Microsoft/DMF.
- Zakładając, że wszystkie części rejestru będą dostępne do uzyskania dostępu w fazie wczesnego rozruchu systemu.
- Zależność od kolejności obciążenia innego sterownika lub usługi. Ponieważ kolejność ładowania może zostać zmieniona poza kontrolą sterownika, może to spowodować, że sterownik działa początkowo, ale później ulegnie awarii w nieprzewidywalnym wzorcu.
- Ponowne tworzenie bibliotek sterowników, które są już dostępne, takie jak te, które WDF zapewnia dla PnP, opisane w Obsługa PnP i zarządzania energią w twoim sterowniku lub te dostępne w interfejsie magistrali, zgodnie z opisem w artykule OSR Używanie interfejsów magistrali do komunikacji między sterownikami.
PnP/Power
- Interakcja z innym sterownikiem w sposób nieprzyjazny dla pnp - brak rejestracji powiadomień o zmianie urządzenia pnp. Zobacz Rejestrowanie powiadomienia o zmianie interfejsu urządzenia.
- Tworzenie węzłów ACPI w celu wyliczania urządzeń i tworzenia zależności zasilania między nimi zamiast używania sterownika magistrali lub systemu zapewnia interfejsy tworzenia urządzeń oprogramowania do pnP i zależności zasilania w elegancki sposób. Zobacz Obsługa plug and play (PnP) i zarządzania zasilaniem w sterownikach funkcyjnych.
- Oznaczenie urządzenia jako niemogącego zostać wyłączonym — wymuszenie ponownego uruchomienia podczas aktualizacji sterownika.
- Ukrywanie urządzenia w menedżerze urządzeń. Zobacz Ukrywanie urządzeń przed menedżerem urządzeń.
- Przyjmowanie założenia, że sterownik będzie używany tylko dla jednego wystąpienia urządzenia.
- Przy założeniu, że kierowca nigdy nie zostanie rozładowany. Zobacz Procedurę Zwalniania Sterownika PnP.
- Nie obsługuje fałszywego powiadomienia o nadejściu interfejsu. Może się to zdarzyć, a od kierowców oczekuje się, że będą potrafili bezpiecznie sobie z tym radzić.
- Nie implementowanie zasad zasilania bezczynności S0, które są ważne dla urządzeń, które są ograniczeniami DRIPS lub ich elementami podrzędnymi. Zobacz obsługa wyłączania zasilania w stanie bezczynności.
- Brak sprawdzania stanu zwrotu WdfDeviceStopIdle prowadzi do wycieku odniesienia do zasilania z powodu braku równowagi WdfDeviceStopIdle/ResumeIdle i ostatecznie kontroli błędu 9F.
- Nie wiedząc, że PrepareHardware/ReleaseHardware może być wywoływane więcej niż raz z powodu ponownego równoważenia zasobów. Te wywołania zwrotne powinny być ograniczone do inicjowania zasobów sprzętowych. Zobacz EVT_WDF_DEVICE_PREPARE_HARDWARE.
- Używanie narzędzia PrepareHardware/ReleaseHardware do przydzielania zasobów oprogramowania. Statyczna alokacja zasobów oprogramowania dla urządzenia powinna być wykonywana w funkcji AddDevice lub SelfManagedIoInit, jeśli wymagana jest interakcja ze sprzętem do alokacji zasobów. Zobacz EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.
Wytyczne dotyczące kodowania
- Nie używasz bezpiecznych funkcji dla ciągów znaków i liczb całkowitych. Zobacz Używanie bezpiecznych funkcji ciągów i Używanie bezpiecznych funkcji całkowitych.
- Nieużywanie typedefów do definiowania stałych.
- Używanie zmiennych globalnych i statycznych. Unikaj przechowywania kontekstu urządzenia w zmiennych globalnych. Globalne są przeznaczone do udostępniania informacji w wielu wystąpieniach urządzeń. Alternatywnie rozważ użycie kontekstu obiektu WDFDRIVER do udostępniania informacji między różnymi instancjami urządzeń.
- Nieużywaj nazw opisowych dla zmiennych.
- Brak spójności w zmiennych nazewnictwa — spójność wielkości liter. Nie przestrzegasz istniejącego stylu kodowania podczas wprowadzania aktualizacji do istniejącego kodu. Na przykład używanie różnych nazw zmiennych dla typowych struktur w różnych funkcjach.
- Nie komentując ważnych wyborów projektowych — zarządzanie energią, blokady, zarządzanie stanem, korzystanie z elementów roboczych, dpc, czasomierzy, globalne użycie zasobów, wstępne przydzielanie zasobów, złożone wyrażenia/instrukcje warunkowe.
- Komentowanie rzeczy, które są oczywiste z nazwy wywoływanego interfejsu API. Dodanie komentarza do nazwy funkcji w języku angielskim (na przykład napisanie komentarza "Create the Device Object" (Tworzenie obiektu urządzenia) podczas wywoływania funkcji WdfDeviceCreate).
- Nie twórz makr, które mają wywołanie zwrotne. Zobacz Funkcje (C++).
- Brak lub niekompletne adnotacje kodu źródłowego (SAL). Zobacz Adnotacje SAL 2.0 dla sterowników systemu Windows.
- Używanie makr zamiast funkcji wbudowanych.
- Używanie makr dla stałych zamiast constexpr podczas korzystania z języka C++
- Kompilowanie sterownika za pomocą kompilatora języka C, a nie kompilatora języka C++, aby zapewnić silne sprawdzanie typów.
Obsługa błędów
- Nieraportowanie krytycznych błędów sterowników i oznaczanie urządzenia jako niefunkcjonalnego w kontrolowany sposób.
- Brak zwracania odpowiedniego stanu błędu NT, co przekłada się na znaczący stan błędu WIN32. Zobacz Używanie wartości NTSTATUS.
- Nie używać makr NTSTATUS do sprawdzania zwróconego stanu funkcji systemowych.
- Brak sprawdzania zmiennych stanu lub flag tam, gdzie jest to konieczne.
- Sprawdź, czy wskaźnik jest prawidłowy przed uzyskaniem dostępu do niego w celu obejścia warunków wyścigu.
- Sprawdzanie dla wskaźników NULL. Jeśli spróbujesz użyć wskaźnika NULL w celu uzyskania dostępu do pamięci, system Windows sprawdzi usterkę. Parametry sprawdzania błędów zapewnią niezbędne informacje, aby naprawić wskaźnik o wartości null. W nadgodzinach, gdy wiele niepotrzebnych instrukcji ASSERT jest dodawanych do kodu, zużywają pamięć i spowalniają system.
- ASEROWANIE w wskaźniku kontekstu obiektu. Struktura sterowników gwarantuje, że obiekt będzie zawsze przydzielany z kontekstem.
Śledzenie
- Nie można definiować typów niestandardowych programu WPP i używać ich w wywołaniach śledzenia w celu uzyskania komunikatów śledzenia czytelnych dla człowieka. Zobacz Dodawanie funkcji śledzenia oprogramowania WPP do sterownika systemu Windows.
- Nie używasz śledzenia IFR. Zobacz Używanie rejestratora śledzenia w locie (IFR) w sterownikach KMDF i UMDF 2.
- Wywoływanie nazw funkcji w wywołaniach śledzenia WPP. Program WPP już śledzi nazwy funkcji i numery wierszy.
- Nieużywanie zdarzeń ETW do mierzenia wydajności i innego krytycznego środowiska użytkownika wpływającego na zdarzenia. Zobacz Dodawanie śledzenia zdarzeń do sterowników Kernel-Mode.
- Nietypowe zgłaszanie błędów krytycznych w dzienniku zdarzeń i delikatnie oznaczając urządzenie jako niefunkcjonalne.
Weryfikacja
- Nie uruchomiono weryfikatora sterowników z ustawieniami standardowymi i zaawansowanymi podczas programowania i testowania. Zobacz Weryfikator sterowników. W ustawieniach zaawansowanych zaleca się włączenie wszystkich reguł, z wyjątkiem reguł związanych z niską symulacją zasobów. Zaleca się uruchamianie testów symulacji niskiego poziomu zasobów w izolacji, aby ułatwić debugowanie problemów.
- Nie uruchomiono testu DevFund w sterowniku lub klasie urządzenia, do których należy sterownik z włączonymi zaawansowanymi ustawieniami weryfikatora. Zobacz Jak uruchomić testy DevFund za pomocą wiersza polecenia.
- Nie weryfikując, czy sterownik jest zgodny z standardem HVCI. Zobacz Implementacja kodu kompatybilnego z HVCI.
- Nie uruchomiono programu AppVerifier na WUDFhost.exe podczas programowania i testowania sterowników trybu użytkownika. Zobacz Weryfikator aplikacji.
- Nie sprawdzaj użycia pamięci przy użyciu rozszerzenia debugera !wdfpoolusage w czasie wykonywania, aby upewnić się, że obiekty WDF nie zostały porzucone. Pamięci, żądań i elementów roboczych są typowymi ofiarami tych problemów.
- Nie używasz rozszerzenia debugera !wdfkd do przeglądania drzewa obiektów, aby upewnić się, że obiekty są poprawnie powiązane, oraz sprawdzając atrybuty głównych obiektów, takich jak WDFDRIVER, WDFDEVICE, IO.