Поделиться через


Рекомендации по разработке драйверов командой для Surface

Введение

Эти рекомендации по разработке драйверов были разработаны на протяжении многих лет разработчиками драйверов в Корпорации Майкрософт. С течением времени, когда водители неправильно действовали и были извлечены уроки, эти уроки были захвачены и развивались таким образом, чтобы быть этим набором рекомендаций. Эти рекомендации используются командой microsoft Surface Hardware для разработки и поддержания кода драйвера устройства, поддерживающего уникальные возможности оборудования Surface.

Как и любой набор рекомендаций, будут допустимые исключения и альтернативные подходы, которые будут равными. Рассмотрите возможность включения этих рекомендаций в стандарты разработки или их использования для запуска конкретных доменных рекомендаций для вашей среды разработки и уникальных требований.

Распространенные ошибки, сделанные разработчиками драйверов

Обработка операций ввода-вывода

  1. Доступ к буферам, полученным из ioCTLs, без проверки длины. См. раздел "Сбой проверки размера буферов".
  2. Выполнение блокировки ввода-вывода в контексте пользовательского потока или контекста случайного потока. Введение в объекты диспетчера ядра.
  3. Отправка синхронного ввода-вывода другому драйверу без времени ожидания. См. синхронную отправку запросов ввода-вывода.
  4. Использование Ioctl 'Neither-io' без понимания их последствий для безопасности. См. Использование ни буферизованного, ни прямого ввода-вывода.
  5. Не проверяется состояние возврата WdfRequestForwardToIoQueue или ошибка не обрабатывается должным образом, что приводит к оставленным без внимания WDFREQUESTs.
  6. Удерживание WDFREQUEST вне очереди в неканцелируемом состоянии. См. инструкции по управлению очередями ввода-вывода, выполнению запросов ввода-вывода и отмене запросов ввода-вывода.
  7. Попытка управлять отменой с помощью функции Mark/UnmarkCancelable вместо использования IoQueues. См. Объекты очереди Framework.
  8. Не зная разницы между операциями очистки и закрытия файлов. См. ошибки в обработке операций очистки и закрытия.
  9. Не учитывая потенциальные рекурсии с завершением ввода-вывода и повторной отправкой из подпрограммы завершения.
  10. Отсутствие явности в атрибутах энергосбережения WDFQUEUEs. Отсутствие чёткого документирования выбора для управления питанием. Это основная причина проверки ошибок 0x9F: DRIVER_POWER_STATE_FAILURE в драйверах WDF. При удалении устройства платформа очищает операции ввода-вывода из управляемой очереди питания и неуправляемой очереди на разных этапах удаления. Очереди, не управляемые питанием, удаляются при получении окончательного IRP_MN_REMOVE_DEVICE. Так что если вы храните операции ввода-вывода в очереди, не управляющей энергопотреблением, рекомендуется явно очистить операции ввода-вывода в контексте EvtDeviceSelfManagedIoFlush, чтобы избежать взаимоблокировки.
  11. Не соблюдаются правила обработки IRP. См. ошибки в обработке операций очистки и закрытия.

Синхронизация

  1. Хранение блокировок для кода, который не нуждается в защите. Не удерживайте блокировку для всей функции, если необходимо защитить только небольшое количество операций.
  2. Вызов водителей, у которых удерживаются блокировки. Это основные причины взаимоблокировок.
  3. Использование примитивов взаимоблокировки для организации схемы блокировки вместо использования соответствующих системных примитивов блокировки, таких как мьютекс, семафор и спинлоки. Введение в объекты мьютексов, объекты семафоров и введение в спинлоки.
  4. Использование спинлока, где будет более подходящим для некоторых типов пассивной блокировки. См. быстрые мьютексы и защищенные мьютексы иобъекты событий. Дополнительные сведения о блокировках см. в статье OSR о состоянии синхронизации.
  5. Выбор участия в модели синхронизации и уровня выполнения WDF без полного осознания последствий. См. раздел "Использование блокировок платформы". Если драйвер не является монолитным драйвером верхнего уровня, напрямую взаимодействующим с оборудованием, избегайте синхронизации WDF, так как это может привести к взаимоблокировкам из-за рекурсии.
  6. Получение KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex в контексте многопоточности без входа в критическую область. Это может привести к атаке DOS, так как поток, удерживающий одну из этих блокировок, может быть приостановлен. Общие сведения об объектах диспетчера ядра.
  7. Выделение KEVENT в стеке потока и возврат вызывающему процессу, пока EVENT всё ещё используется. Обычно выполняется при использовании с IoBuildSyncronousFsdRequest или IoBuildDeviceIoControlRequest. Вызывающий должен убедиться, что не покидает стек до тех пор, пока диспетчер ввода-вывода не сигнализирует о событии после завершения IRP.
  8. Неопределенное ожидание в подпрограммах диспетчеризации. Как правило, любой вид ожидания в диспетчерской функции является плохой практикой.
  9. Неуместным образом проверяется допустимость объекта (если blah == NULL) перед удалением объекта. Обычно это означает, что автор не имеет полного понимания кода, который управляет временем существования объекта.

Управление объектами

  1. Не устанавливает явно связь "родитель-потомок" для объектов WDF. Общие сведения о объектах Framework.
  2. Присвоение родительского объекта WDF объекту WDFDRIVER, вместо объекта, который обеспечивает более эффективное управление временем существования и оптимизацию использования памяти. Например, установление родительского отношения между WDFREQUEST и WDFDEVICE вместо IOTARGET. Ознакомьтесь с общими объектами Framework, жизненным циклом объектов Framework и сводной частью объектов Платформы.
  3. Отсутствие защиты ресурсов общей памяти, к которым осуществляется доступ драйверами. См. функцию ExInitializeRundownProtection.
  4. Ошибочно ставить в очередь тот же элемент задачи, если предыдущий уже находится в очереди или выполняется. Это может быть проблема, если клиент делает предположение о том, что каждый рабочий элемент в очереди будет выполнен. См. статью Using Framework WorkItems. Дополнительные сведения о постановке в очередь WorkItems см. в модуле DMF_QueuedWorkitem в проекте Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
  5. Таймер очереди перед публикацией сообщения, как ожидается, будет обрабатывать таймер. См. раздел "Использование таймеров".
  6. Выполнение операции в рабочем сайте, которое может блокировать или занять неограниченное время.
  7. Проектирование решения, которое приводит к наплыву рабочих элементов, подлежащих постановке в очередь. Это может привести к неотзывчивой системе или атаке DOS, если злоумышленник может контролировать действие (например, перекачивая ввод-вывод в драйвер, который ставит в очередь новый рабочий элемент для каждого ввода-вывода). См. статью "Использование рабочих элементов Платформы".
  8. Отсутствие гарантии завершения выполнения обратных вызовов DPC рабочего элемента перед удалением объекта. Ознакомьтесь с рекомендациями по написанию подпрограмм DPC и функции WdfDpcCancel.
  9. Создание потоков вместо использования рабочих элементов для коротких задач или задач, не являющихся опросами. См. статью "Рабочие потоки системы".
  10. Не обеспечивается завершение выполнения потоков перед удалением или выгрузкой драйвера. Дополнительные сведения о синхронизации потока см. в коде, связанном с модулем DMF_Thread в проекте Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
  11. Использование одного драйвера для управления устройствами, которые отличаются, но взаимозависимыми и используют глобальные переменные для совместного использования информации.

Память

  1. Не отмечать код пассивного выполнения как PAGEABLE, если это возможно. Код драйвера с разбиением на страницы может уменьшить объем кода драйвера, освобождая системное пространство для других нужд. Будьте осторожны, помечая код доступным для выгрузки в память, который поднимает уровень IRQL >= DISPATCH_LEVEL или может вызываться при поднятом IRQL >. Узнайте , когда код и данные могут быть выгружаемыми в память,как сделать драйверы выгружаемыми в память, и как определить код, который может быть выгружаемым в память.
  2. Для объявления больших структур на стеке следует использовать кучу или пул. См. раздел "Использование ядраStack " и выделение System-Space памяти.
  3. Ненужное обнуление контекста объекта WDF. Это может указывать на отсутствие ясности о том, когда память будет очищена автоматически.

Общие рекомендации для водителей

  1. Сочетание примитивов WDM и WDF. Использование примитивов WDM, где можно использовать примитивы WDF. Использование примитивов WDF защищает вас от подводных камней, улучшает отладку и, что более важно, делает драйвер применимым в пользовательском режиме.
  2. Именование FDOs и создание символьных ссылок, когда это не требуется. См. раздел "Управление доступом к драйверу".
  3. Скопируйте вставку и использование идентификаторов GUID и других значений констант из примеров драйверов.
  4. Рассмотрите возможность использования в проекте драйвера с открытым исходным кодом (DMF) с открытым исходным кодом. DMF — это расширение для WDF, которое обеспечивает дополнительные функциональные возможности для разработчика драйвера WDF. Ознакомьтесь с разделом "Общие сведения о модуле драйвера".
  5. Использование реестра в качестве механизма уведомлений между процессами или в качестве почтового ящика. Дополнительные сведения см. в разделе DMF_NotifyUserWithEvent и DMF_NotifyUserWithRequest модулей, доступных в проекте DMF. https://github.com/Microsoft/DMF
  6. Предполагая, что все части реестра будут доступны для доступа во время раннего этапа загрузки системы.
  7. Зависимость от порядка загрузки другого драйвера или службы. Так как порядок загрузки может быть изменен за пределами управления драйвером, это может привести к тому, что драйвер работает изначально, но позже завершается сбоем в непредсказуемом шаблоне.
  8. Повторное создание библиотек драйверов, которые уже доступны, например WDF, предоставляет PnP, описанные в разделе "Поддержка PnP и управление питанием в драйвере" или те, которые предоставлены в интерфейсе шины, как описано в статье OSR с использованием интерфейсов шины для связи драйвера с драйвером.

PnP/Power

  1. Взаимодействие с другим драйвером в понятном режиме, отличном от pnp, не регистрируется для уведомлений об изменении устройства pnp. См. раздел "Регистрация для уведомления об изменении интерфейса устройства".
  2. Создание узлов ACPI для перечисления устройств и создания зависимостей питания среди них вместо использования драйверов шины или системных интерфейсов создания программного обеспечения для PNP и зависимостей питания в элегантном виде. См. раздел поддержки PnP и управления питанием в драйверах функций.
  3. Маркировка устройства как неотключаемого, вынуждающая перезагрузку при обновлении драйвера.
  4. Скрытие устройства в диспетчере устройств. См. Скрытие устройств в Диспетчере устройств.
  5. Предполагая, что драйвер будет использоваться только для одного экземпляра устройства.
  6. Предполагая, что водитель никогда не будет выгружен. См. процедуру выгрузки драйвера PnP.
  7. Не обрабатывается ложное уведомление о появлении интерфейса. Это может произойти, и водители должны безопасно справляться с этой ситуацией.
  8. Не реализуется политика энергопотребления S0 Бездействия, которая важна для устройств, работающих в режиме DRIPS, или для их дочерних устройств. См. Поддержка режима ожидания в условиях пониженного энергопотребления.
  9. Не проверка состояния возврата WdfDeviceStopIdle приводит к утечке ссылок на питание из-за дисбаланса WdfDeviceStopIdle/ResumeIdle и в конечном итоге проверки ошибки 9F.
  10. Не подозревая о том, что PrepareHardware/ReleaseHardware можно вызывать несколько раз из-за перебалансировки ресурсов. Эти обратные вызовы должны быть ограничены инициализацией аппаратных ресурсов. См. EVT_WDF_DEVICE_PREPARE_HARDWARE.
  11. Использование PrepareHardware/ReleaseHardware для выделения ресурсов программного обеспечения. Статическое выделение ресурсов программного обеспечения для устройства должно выполняться в AddDevice или в SelfManagedIoInit, если требуется выделение ресурсов для взаимодействия с оборудованием. См. EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.

Рекомендации по программированию

  1. Не используется безопасные функции строк и целых чисел. См. статью "Использование безопасных строковых функций " и использование безопасных целых чисел.
  2. Не используется typedefs для определения констант.
  3. Использование глобальных и статических переменных. Избегайте хранения контекста устройства в глобальных переменных. Глобальные платформы предназначены для совместного доступа к информации в нескольких экземплярах устройств. В качестве альтернативы рекомендуется использовать контекст объекта WDFDRIVER для совместного доступа к данным в нескольких экземплярах устройств.
  4. Не используется описательные имена для переменных.
  5. Несогласованность в именовании переменных — согласованность в использовании регистра. Не следует использовать существующий стиль написания кода при внесении обновлений в существующий код. Например, использование разных имен переменных для общих структур в разных функциях.
  6. Не комментировать важные варианты проектирования: управление питанием, блокировки, управление состоянием, использование рабочих элементов, DPCs, таймеров, глобальное использование ресурсов, предварительное выделение ресурсов, сложные выражения и условные операторы.
  7. Комментирование аспектов, очевидных из имени вызываемого API. При вызове WdfDeviceCreate ваш комментарий на английском языке должен быть эквивалентен названию функции (например, комментарий "Создать объект устройства").
  8. Не создавайте макросы с возвращаемым вызовом. См. раздел "Функции" (C++).
  9. Нет или неполных заметок исходного кода (SAL). См. аннотации SAL 2.0 для драйверов Windows.
  10. Использование макросов вместо встроенных функций.
  11. Использование макросов для констант вместо constexpr при использовании C++
  12. Компиляция вашего драйвера с компилятором C вместо компилятора C++, чтобы обеспечить строгую проверку типов.

Обработка ошибок

  1. Не сообщать о критических ошибках драйвера и корректно помечать устройство нефункциональным.
  2. Не возвращается соответствующее состояние ошибки NT, которое преобразуется в понятное состояние ошибки WIN32. См. статью "Использование значений NTSTATUS".
  3. Не используйте макросы NTSTATUS для проверки возвращаемого состояния системных функций.
  4. Не утверждать переменные состояния или флаги, когда это необходимо.
  5. Проверьте, является ли указатель допустимым перед доступом к нему, чтобы обойти условия гонки.
  6. УТВЕРЖДЕНИЕ на указателях NULL. Если вы попытаетесь использовать указатель NULL для доступа к памяти, Windows вызовет проверку сбоев. Параметры проверки ошибок предоставляют необходимые сведения для исправления указателя NULL. Со временем, когда в код добавляются многие ненужные инструкции ASSERT, они потребляют память и замедляют работу системы.
  7. ASSERTING в указателе контекста объекта. Платформа драйверов гарантирует, что объект всегда будет выделен контекстом.

Отслеживание

  1. Не определять пользовательские типы WPP и использовать его в вызовах трассировки для получения сообщений трассировки, доступных для чтения. См. статью "Добавление трассировки программного обеспечения WPP в драйвер Windows".
  2. Не используется трассировка IFR. Ознакомьтесь с тем, как использовать средство записи трассировки в полёте (IFR) в драйверах KMDF и UMDF 2.
  3. Вызов имен функций в вызовах трассировки WPP. WPP уже отслеживает имена функций и номера строк.
  4. Не используйте события ETW для измерения производительности и других критически важных аспектов, влияющих на пользовательский опыт. См . статью "Добавление трассировки событий в драйверы Kernel-Mode".
  5. Отсутствие сообщения о критических ошибках в системном журнале событий, что позволяет корректно пометить устройство как нефункциональное.

Проверка

  1. Не запускается средство проверки драйверов с использованием стандартных и расширенных параметров в процессе разработки и тестирования. См. средство проверки драйверов. В расширенных параметрах рекомендуется включить все правила, за исключением тех правил, которые связаны с моделированием низких ресурсов. Рекомендуется запускать тесты симуляции с низким уровнем ресурсов в изоляции, чтобы упростить отладку проблем.
  2. Не выполняется тест DevFund для драйвера или класса устройств, частью которого является драйвер, с включёнными расширенными параметрами проверки. Узнайте , как запустить тесты DevFund с помощью командной строки.
  3. Отсутствие проверки соответствия драйвера HVCI. См. раздел "Реализация кода compatibile HVCI".
  4. AppVerifier не запускается на WUDFhost.exe во время разработки и тестирования драйверов пользовательского режима. См. средство проверки приложений.
  5. Не проверяется использование памяти с помощью расширения отладчика !wdfpoolusage во время выполнения, чтобы убедиться, что объекты WDF не остаются необработанными. Память, запросы и рабочие элементы часто становятся жертвами этих проблем.
  6. Не используется расширение отладчика !wdfkd для проверки дерева объектов, чтобы убедиться, что объекты правильно установлены в иерархии, а также проверки атрибутов основных объектов, таких как WDFDRIVER, WDFDEVICE, IO.