Анализ проблем верификации драйверов Windows
3
по информации, генерируемой штатными средствами верификации,
получить достаточное количество данных, а иногда и просто выявить
опасные участки кода.
В качестве первого примера работы штатных средств верифика-
ции ОС Windows рассмотрим спин-блокировки, которые являются
о
дним из основных средств взаимоисключения в ядре.
Спин-блокировки используются в режиме активного ожидания.
Это означает, что поток, ожидающий освобождения спин-
блокировки, постоянно проверяет, не освободилась ли она, и тратит
на эту проверку процессорное время. Спин-блокировки используют-
ся в тех кодах ядра, которые не могут быть заблокированы, напри-
мер, в обработчиках прерываний. Спин-блокировки в ядре реализую-
ся с помощью двух функций: KeAsquireSpinLock(), обеспечивающей
захват блокировки, и KeReleaseSpinLock(), освобождающей блоки-
ровку. Спин-блокировки никак не контролируются операционной си-
стемной.
Для реализации взаимоисключения с помощью спин-блокировок
характерно два вида ошибок:
•
спин-блокировкам в ядре назначен уровень IRQL DPC/dispatch,
который блокирует все действия, так как на этом уровне заблокиро-
ваны действия по диспетчеризации потоков, поэтому код, устано-
вивший блокировку, может привести к краху системы, если попыта-
ется заставить планировщик выполнить операцию диспетчеризации
или вызовет ошибку страницы [2];
•
несоблюдение иерархии захвата спин-блокировок, из-за чего
происходит повторный ошибочный захват, в результате которого по-
ток войдет в цикл активного ожидания процессом, что приводит к
зависанию системы и неизбежному краху.
Для анализа способов диагностики приведенных ошибок был
разработан простейший драйвер режима ядра.
Проведенный анализ показал, что ошибки, как правило, надо
искать не по всему коду драйвера. Они обычно концентрируются в
некоторых специфических процедурах, таких как NTSTATUS De-
viceControlRoutine (IN PDEVICE_OBJECT fdo, IN PIRP Irp), которая
распознает IRP (I/O Request Packet — пакеты запросов ввода-
вывода).
В функции на рис. 1 в неперемещаемой памяти создается объект
типа KSPIN_LOCK с именем Qlock.
Функция сохраняет предыдущий уровень приоритета, определяя
его до вызова блокировки и перехода процесса на уровень
DPC/dispatch. Чтобы организовать ошибку чтения страницы, создается
указатель на страницу в области памяти. Специальная переменная data
создается с квалификатором volatile, который указывает компилятору