Рекомендации по разработке surface Team Driver

Введение

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

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

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

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

  1. Доступ к буферам, полученным из ioCTLs, без проверки длины. См . раздел "Сбой проверки размера буферов".
  2. Выполнение блокировки ввода-вывода в контексте пользовательского потока или контекста случайного потока. Общие сведения о объектах диспетчера ядра.
  3. Отправка синхронного ввода-вывода другому драйверу без времени ожидания. См . синхронную отправку запросов ввода-вывода.
  4. Использование не ioCTLs без понимания последствий безопасности. См . раздел "Использование буферизованного и прямого ввода-вывода".
  5. Не проверка состояние возврата WdfRequestForwardToIoQueue или не обрабатывает ошибки правильно и приводит к отказу WDFREQUESTs.
  6. Сохранение WDFREQUEST вне очереди в состоянии без отмены. См. инструкции по управлению очередями ввода-вывода, выполнению запросов ввода-вывода и отмене запросов ввода-вывода.
  7. Попытка управлять отменой с помощью функции Mark/UnmarkCancelable вместо использования IoQueues. См. статью "Объекты очереди платформы".
  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 в проекте https://github.com/Microsoft/DMFDriver Module Framework (DMF).
  5. Таймер очереди перед публикацией сообщения, как ожидается, будет обрабатывать таймер. См. раздел "Использование таймеров".
  6. Выполнение операции в рабочем сайте, которое может блокировать или занять неограниченное время.
  7. Проектирование решения, которое приводит к переполнению рабочих элементов, которые должны быть поставлены в очередь. Это может привести к неответственной системе или атаке DOS, если плохой парень может контролировать действие (например, перекачивая ввод-вывод в драйвер, который очереди нового рабочего элемента для каждого ввода-вывода). См. статью "Использование рабочих элементов Платформы".
  8. После этого обратные вызовы DPC рабочего элемента выполняются до завершения перед удалением объекта. Ознакомьтесь с рекомендациями по написанию подпрограмм DPC и функции WdfDpcCancel.
  9. Создание потоков вместо использования рабочих элементов для коротких задач или задач, не являющихся опросами. См. статью "Рабочие потоки системы".
  10. Не гарантирует, что потоки выполняются до завершения перед удалением или выгрузом драйвера. Дополнительные сведения о синхронизации запуска потока см. в коде, связанном с кодом, связанным с модулем DMF_Thread в проекте https://github.com/Microsoft/DMFDriver Module Framework (DMF).
  11. Использование одного драйвера для управления устройствами, которые отличаются, но взаимозависимыми и используют глобальные переменные для совместного использования информации.

Память

  1. Если это возможно, не помечает код пассивного выполнения как PAGEABLE. Код драйвера на разбиение на страницы может уменьшить размер следа кода драйвера, что позволяет освободить системное пространство для других видов использования. Будьте осторожны помечайте код, который вызывает IRQL = DISPATCH_LEVEL или может вызываться при поднятом IRQL >. Узнайте , когда код и данные должны быть страницы и сделать драйверы страницыиобнаруживать код, который может быть страницообразуемым.
  2. Объявление больших структур в стеке должно использовать кучу или пул. См. статью "Использование ядраStack " и выделение памяти системного пространства.
  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 или дочерними элементами. См . раздел "Поддержка бездействия" в power-down.
  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 при вызове 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. Ознакомьтесь с использованием средства записи трассировки inflight (IFR) в драйверах KMDF и UMDF 2.
  3. Вызов имен функций в вызовах трассировки WPP. WPP уже отслеживает имена функций и номера строк.
  4. Не используйте события ETW для измерения производительности и других критически важных событий пользователей, влияющих на события. См. статью "Добавление трассировки событий в драйверы режима ядра".
  5. Не сообщая критические ошибки в журнале событий и корректно помечая устройство нефункциональным.

Проверка

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