Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Введение
Эти рекомендации по разработке драйверов были разработаны на протяжении многих лет разработчиками драйверов в Корпорации Майкрософт. С течением времени, когда водители неправильно действовали и были извлечены уроки, эти уроки были захвачены и развивались таким образом, чтобы быть этим набором рекомендаций. Эти рекомендации используются командой microsoft Surface Hardware для разработки и поддержания кода драйвера устройства, поддерживающего уникальные возможности оборудования Surface.
Как и любой набор рекомендаций, будут допустимые исключения и альтернативные подходы, которые будут равными. Рассмотрите возможность включения этих рекомендаций в стандарты разработки или их использования для запуска конкретных доменных рекомендаций для вашей среды разработки и уникальных требований.
Распространенные ошибки, сделанные разработчиками драйверов
Обработка операций ввода-вывода
- Доступ к буферам, полученным из ioCTLs, без проверки длины. См. раздел "Сбой проверки размера буферов".
- Выполнение блокировки ввода-вывода в контексте пользовательского потока или контекста случайного потока. Введение в объекты диспетчера ядра.
- Отправка синхронного ввода-вывода другому драйверу без времени ожидания. См. синхронную отправку запросов ввода-вывода.
- Использование Ioctl 'Neither-io' без понимания их последствий для безопасности. См. Использование ни буферизованного, ни прямого ввода-вывода.
- Не проверяется состояние возврата WdfRequestForwardToIoQueue или ошибка не обрабатывается должным образом, что приводит к оставленным без внимания WDFREQUESTs.
- Удерживание WDFREQUEST вне очереди в неканцелируемом состоянии. См. инструкции по управлению очередями ввода-вывода, выполнению запросов ввода-вывода и отмене запросов ввода-вывода.
- Попытка управлять отменой с помощью функции Mark/UnmarkCancelable вместо использования IoQueues. См. Объекты очереди Framework.
- Не зная разницы между операциями очистки и закрытия файлов. См. ошибки в обработке операций очистки и закрытия.
- Не учитывая потенциальные рекурсии с завершением ввода-вывода и повторной отправкой из подпрограммы завершения.
- Отсутствие явности в атрибутах энергосбережения WDFQUEUEs. Отсутствие чёткого документирования выбора для управления питанием. Это основная причина проверки ошибок 0x9F: DRIVER_POWER_STATE_FAILURE в драйверах WDF. При удалении устройства платформа очищает операции ввода-вывода из управляемой очереди питания и неуправляемой очереди на разных этапах удаления. Очереди, не управляемые питанием, удаляются при получении окончательного IRP_MN_REMOVE_DEVICE. Так что если вы храните операции ввода-вывода в очереди, не управляющей энергопотреблением, рекомендуется явно очистить операции ввода-вывода в контексте EvtDeviceSelfManagedIoFlush, чтобы избежать взаимоблокировки.
- Не соблюдаются правила обработки IRP. См. ошибки в обработке операций очистки и закрытия.
Синхронизация
- Хранение блокировок для кода, который не нуждается в защите. Не удерживайте блокировку для всей функции, если необходимо защитить только небольшое количество операций.
- Вызов водителей, у которых удерживаются блокировки. Это основные причины взаимоблокировок.
- Использование примитивов взаимоблокировки для организации схемы блокировки вместо использования соответствующих системных примитивов блокировки, таких как мьютекс, семафор и спинлоки. Введение в объекты мьютексов, объекты семафоров и введение в спинлоки.
- Использование спинлока, где будет более подходящим для некоторых типов пассивной блокировки. См. быстрые мьютексы и защищенные мьютексы иобъекты событий. Дополнительные сведения о блокировках см. в статье OSR о состоянии синхронизации.
- Выбор участия в модели синхронизации и уровня выполнения WDF без полного осознания последствий. См. раздел "Использование блокировок платформы". Если драйвер не является монолитным драйвером верхнего уровня, напрямую взаимодействующим с оборудованием, избегайте синхронизации WDF, так как это может привести к взаимоблокировкам из-за рекурсии.
- Получение KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex в контексте многопоточности без входа в критическую область. Это может привести к атаке DOS, так как поток, удерживающий одну из этих блокировок, может быть приостановлен. Общие сведения об объектах диспетчера ядра.
- Выделение KEVENT в стеке потока и возврат вызывающему процессу, пока EVENT всё ещё используется. Обычно выполняется при использовании с IoBuildSyncronousFsdRequest или IoBuildDeviceIoControlRequest. Вызывающий должен убедиться, что не покидает стек до тех пор, пока диспетчер ввода-вывода не сигнализирует о событии после завершения IRP.
- Неопределенное ожидание в подпрограммах диспетчеризации. Как правило, любой вид ожидания в диспетчерской функции является плохой практикой.
- Неуместным образом проверяется допустимость объекта (если blah == NULL) перед удалением объекта. Обычно это означает, что автор не имеет полного понимания кода, который управляет временем существования объекта.
Управление объектами
- Не устанавливает явно связь "родитель-потомок" для объектов WDF. Общие сведения о объектах Framework.
- Присвоение родительского объекта WDF объекту WDFDRIVER, вместо объекта, который обеспечивает более эффективное управление временем существования и оптимизацию использования памяти. Например, установление родительского отношения между WDFREQUEST и WDFDEVICE вместо IOTARGET. Ознакомьтесь с общими объектами Framework, жизненным циклом объектов Framework и сводной частью объектов Платформы.
- Отсутствие защиты ресурсов общей памяти, к которым осуществляется доступ драйверами. См. функцию ExInitializeRundownProtection.
- Ошибочно ставить в очередь тот же элемент задачи, если предыдущий уже находится в очереди или выполняется. Это может быть проблема, если клиент делает предположение о том, что каждый рабочий элемент в очереди будет выполнен. См. статью Using Framework WorkItems. Дополнительные сведения о постановке в очередь WorkItems см. в модуле DMF_QueuedWorkitem в проекте Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
- Таймер очереди перед публикацией сообщения, как ожидается, будет обрабатывать таймер. См. раздел "Использование таймеров".
- Выполнение операции в рабочем сайте, которое может блокировать или занять неограниченное время.
- Проектирование решения, которое приводит к наплыву рабочих элементов, подлежащих постановке в очередь. Это может привести к неотзывчивой системе или атаке DOS, если злоумышленник может контролировать действие (например, перекачивая ввод-вывод в драйвер, который ставит в очередь новый рабочий элемент для каждого ввода-вывода). См. статью "Использование рабочих элементов Платформы".
- Отсутствие гарантии завершения выполнения обратных вызовов DPC рабочего элемента перед удалением объекта. Ознакомьтесь с рекомендациями по написанию подпрограмм DPC и функции WdfDpcCancel.
- Создание потоков вместо использования рабочих элементов для коротких задач или задач, не являющихся опросами. См. статью "Рабочие потоки системы".
- Не обеспечивается завершение выполнения потоков перед удалением или выгрузкой драйвера. Дополнительные сведения о синхронизации потока см. в коде, связанном с модулем DMF_Thread в проекте Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
- Использование одного драйвера для управления устройствами, которые отличаются, но взаимозависимыми и используют глобальные переменные для совместного использования информации.
Память
- Не отмечать код пассивного выполнения как PAGEABLE, если это возможно. Код драйвера с разбиением на страницы может уменьшить объем кода драйвера, освобождая системное пространство для других нужд. Будьте осторожны, помечая код доступным для выгрузки в память, который поднимает уровень IRQL >= DISPATCH_LEVEL или может вызываться при поднятом IRQL >. Узнайте , когда код и данные могут быть выгружаемыми в память,как сделать драйверы выгружаемыми в память, и как определить код, который может быть выгружаемым в память.
- Для объявления больших структур на стеке следует использовать кучу или пул. См. раздел "Использование ядраStack " и выделение System-Space памяти.
- Ненужное обнуление контекста объекта WDF. Это может указывать на отсутствие ясности о том, когда память будет очищена автоматически.
Общие рекомендации для водителей
- Сочетание примитивов WDM и WDF. Использование примитивов WDM, где можно использовать примитивы WDF. Использование примитивов WDF защищает вас от подводных камней, улучшает отладку и, что более важно, делает драйвер применимым в пользовательском режиме.
- Именование FDOs и создание символьных ссылок, когда это не требуется. См. раздел "Управление доступом к драйверу".
- Скопируйте вставку и использование идентификаторов GUID и других значений констант из примеров драйверов.
- Рассмотрите возможность использования в проекте драйвера с открытым исходным кодом (DMF) с открытым исходным кодом. DMF — это расширение для WDF, которое обеспечивает дополнительные функциональные возможности для разработчика драйвера WDF. Ознакомьтесь с разделом "Общие сведения о модуле драйвера".
- Использование реестра в качестве механизма уведомлений между процессами или в качестве почтового ящика. Дополнительные сведения см. в разделе DMF_NotifyUserWithEvent и DMF_NotifyUserWithRequest модулей, доступных в проекте DMF. https://github.com/Microsoft/DMF
- Предполагая, что все части реестра будут доступны для доступа во время раннего этапа загрузки системы.
- Зависимость от порядка загрузки другого драйвера или службы. Так как порядок загрузки может быть изменен за пределами управления драйвером, это может привести к тому, что драйвер работает изначально, но позже завершается сбоем в непредсказуемом шаблоне.
- Повторное создание библиотек драйверов, которые уже доступны, например WDF, предоставляет PnP, описанные в разделе "Поддержка PnP и управление питанием в драйвере" или те, которые предоставлены в интерфейсе шины, как описано в статье OSR с использованием интерфейсов шины для связи драйвера с драйвером.
PnP/Power
- Взаимодействие с другим драйвером в понятном режиме, отличном от pnp, не регистрируется для уведомлений об изменении устройства pnp. См. раздел "Регистрация для уведомления об изменении интерфейса устройства".
- Создание узлов ACPI для перечисления устройств и создания зависимостей питания среди них вместо использования драйверов шины или системных интерфейсов создания программного обеспечения для PNP и зависимостей питания в элегантном виде. См. раздел поддержки PnP и управления питанием в драйверах функций.
- Маркировка устройства как неотключаемого, вынуждающая перезагрузку при обновлении драйвера.
- Скрытие устройства в диспетчере устройств. См. Скрытие устройств в Диспетчере устройств.
- Предполагая, что драйвер будет использоваться только для одного экземпляра устройства.
- Предполагая, что водитель никогда не будет выгружен. См. процедуру выгрузки драйвера PnP.
- Не обрабатывается ложное уведомление о появлении интерфейса. Это может произойти, и водители должны безопасно справляться с этой ситуацией.
- Не реализуется политика энергопотребления S0 Бездействия, которая важна для устройств, работающих в режиме DRIPS, или для их дочерних устройств. См. Поддержка режима ожидания в условиях пониженного энергопотребления.
- Не проверка состояния возврата WdfDeviceStopIdle приводит к утечке ссылок на питание из-за дисбаланса WdfDeviceStopIdle/ResumeIdle и в конечном итоге проверки ошибки 9F.
- Не подозревая о том, что PrepareHardware/ReleaseHardware можно вызывать несколько раз из-за перебалансировки ресурсов. Эти обратные вызовы должны быть ограничены инициализацией аппаратных ресурсов. См. EVT_WDF_DEVICE_PREPARE_HARDWARE.
- Использование PrepareHardware/ReleaseHardware для выделения ресурсов программного обеспечения. Статическое выделение ресурсов программного обеспечения для устройства должно выполняться в AddDevice или в SelfManagedIoInit, если требуется выделение ресурсов для взаимодействия с оборудованием. См. EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.
Рекомендации по программированию
- Не используется безопасные функции строк и целых чисел. См. статью "Использование безопасных строковых функций " и использование безопасных целых чисел.
- Не используется typedefs для определения констант.
- Использование глобальных и статических переменных. Избегайте хранения контекста устройства в глобальных переменных. Глобальные платформы предназначены для совместного доступа к информации в нескольких экземплярах устройств. В качестве альтернативы рекомендуется использовать контекст объекта WDFDRIVER для совместного доступа к данным в нескольких экземплярах устройств.
- Не используется описательные имена для переменных.
- Несогласованность в именовании переменных — согласованность в использовании регистра. Не следует использовать существующий стиль написания кода при внесении обновлений в существующий код. Например, использование разных имен переменных для общих структур в разных функциях.
- Не комментировать важные варианты проектирования: управление питанием, блокировки, управление состоянием, использование рабочих элементов, DPCs, таймеров, глобальное использование ресурсов, предварительное выделение ресурсов, сложные выражения и условные операторы.
- Комментирование аспектов, очевидных из имени вызываемого API. При вызове WdfDeviceCreate ваш комментарий на английском языке должен быть эквивалентен названию функции (например, комментарий "Создать объект устройства").
- Не создавайте макросы с возвращаемым вызовом. См. раздел "Функции" (C++).
- Нет или неполных заметок исходного кода (SAL). См. аннотации SAL 2.0 для драйверов Windows.
- Использование макросов вместо встроенных функций.
- Использование макросов для констант вместо constexpr при использовании C++
- Компиляция вашего драйвера с компилятором C вместо компилятора C++, чтобы обеспечить строгую проверку типов.
Обработка ошибок
- Не сообщать о критических ошибках драйвера и корректно помечать устройство нефункциональным.
- Не возвращается соответствующее состояние ошибки NT, которое преобразуется в понятное состояние ошибки WIN32. См. статью "Использование значений NTSTATUS".
- Не используйте макросы NTSTATUS для проверки возвращаемого состояния системных функций.
- Не утверждать переменные состояния или флаги, когда это необходимо.
- Проверьте, является ли указатель допустимым перед доступом к нему, чтобы обойти условия гонки.
- УТВЕРЖДЕНИЕ на указателях NULL. Если вы попытаетесь использовать указатель NULL для доступа к памяти, Windows вызовет проверку сбоев. Параметры проверки ошибок предоставляют необходимые сведения для исправления указателя NULL. Со временем, когда в код добавляются многие ненужные инструкции ASSERT, они потребляют память и замедляют работу системы.
- ASSERTING в указателе контекста объекта. Платформа драйверов гарантирует, что объект всегда будет выделен контекстом.
Отслеживание
- Не определять пользовательские типы WPP и использовать его в вызовах трассировки для получения сообщений трассировки, доступных для чтения. См. статью "Добавление трассировки программного обеспечения WPP в драйвер Windows".
- Не используется трассировка IFR. Ознакомьтесь с тем, как использовать средство записи трассировки в полёте (IFR) в драйверах KMDF и UMDF 2.
- Вызов имен функций в вызовах трассировки WPP. WPP уже отслеживает имена функций и номера строк.
- Не используйте события ETW для измерения производительности и других критически важных аспектов, влияющих на пользовательский опыт. См . статью "Добавление трассировки событий в драйверы Kernel-Mode".
- Отсутствие сообщения о критических ошибках в системном журнале событий, что позволяет корректно пометить устройство как нефункциональное.
Проверка
- Не запускается средство проверки драйверов с использованием стандартных и расширенных параметров в процессе разработки и тестирования. См. средство проверки драйверов. В расширенных параметрах рекомендуется включить все правила, за исключением тех правил, которые связаны с моделированием низких ресурсов. Рекомендуется запускать тесты симуляции с низким уровнем ресурсов в изоляции, чтобы упростить отладку проблем.
- Не выполняется тест DevFund для драйвера или класса устройств, частью которого является драйвер, с включёнными расширенными параметрами проверки. Узнайте , как запустить тесты DevFund с помощью командной строки.
- Отсутствие проверки соответствия драйвера HVCI. См. раздел "Реализация кода compatibile HVCI".
- AppVerifier не запускается на WUDFhost.exe во время разработки и тестирования драйверов пользовательского режима. См. средство проверки приложений.
- Не проверяется использование памяти с помощью расширения отладчика !wdfpoolusage во время выполнения, чтобы убедиться, что объекты WDF не остаются необработанными. Память, запросы и рабочие элементы часто становятся жертвами этих проблем.
- Не используется расширение отладчика !wdfkd для проверки дерева объектов, чтобы убедиться, что объекты правильно установлены в иерархии, а также проверки атрибутов основных объектов, таких как WDFDRIVER, WDFDEVICE, IO.