Ошибка irp фильтра unknown irp windows 7

Драйверы режима ядра: Часть 15 : Жизненный цикл IRP — Архив WASM.RU

В этой и следующей статье мы рассмотрим принципы фильтрации (перехвата) пакетов запроса в/в (IRP). Для чего нужно перехватывать чужие IRP? Применений этому много. Например, захотелось нам посмотреть, к каким файлам обращается та или иная программа. Что мы сделаем в первую очередь? Правильно — запустим FileMon ( sysinternals.com ), который установит драйвер-фильтр на файловую систему. А поскольку обращение к файлам — это фактически формирование соответствующих IRP (быстрый в/в, при котором формирования IRP не происходит, не в счет) и посылка их драйверам файловой системы, то прежде чем добраться до адресата, IRP попадет в фильтр и FileMon зафиксирует это обращение, после чего перешлет его адресату. При этом воздействовать на перехватываемые пакеты FileMon не может. Его задача — только регистрировать факт посылки IRP. Другой пример. Допустим, вам понадобилось скрыть, например, от ваших ближайших родственников или коллег по работе, наличие некоторых файлов фривольного содержания. Недолго думая, вы наберете в google что-то вроде «Hide Files And Folders» и тут же найдете кучку программ, позволяющих скрывать отдельные файлы и каталоги. Это возможно благодаря тому же самому механизму фильтрации IRP. Получая доступ к пакету, драйвер-фильтр имеет возможность модифицировать передаваемые в нём данные, как на пути к файловой системе, так и обратно. Разумеется, фильтровать можно не только IRP передающиеся в файловую систему, но и любые другие. Фильтрация IRP — это общий и универсальный механизм. Антивирусные мониторы, файерволы, на лету компрессоры/декопрессоры крипторы/декрипторы и т.д. и т.п. используют механизм фильтрации IRP. Фильтр, который мы напишем в следующий раз, будет отслеживать IRP, связанные с клавиатурным вводом.

Фильтрация пакетов запроса в/в — достаточно сложная тема. Поэтому, прежде чем перейти к практической реализации потребуется хотя бы минимальная теоретическая подготовка. Как минимум, надо четко представлять себе жизненный цикл IRP от «рождения до смерти». В этой статье мы, в основном, и будем заниматься исследованием этого вопроса. Поскольку драйверы, обслуживающие клавиатуру, в полной мере поддерживают механизм Plug And Play, то придется, в минимальном объеме, осветить и этот вопрос. При этом наш фильтр не будет драйвером Plug And Play. Это будет по-прежнему унаследованный (legacy), в терминологии Microsoft, драйвер, но подключать мы его будем к Plug And Play драйверу.

Ввиду сложности темы, мне вряд ли удастся осветить этот вопрос со всех сторон. Много дополнительной информации можно получить из раздела DDK «Handling IRPs». В Installable File System Kit (IFS KIT), являющийся надмножеством обычного DDK, имеется также раздел «OSR Technical Articles» куда вошли статьи подготовленные командой Open System Resources ( http://www.osr.com/ ). Если в вашем распоряжении только обычный DDK, то большую часть этих статей, если не все, а также много дополнительной информации можно найти в онлайновом журнале «The NT Insider» ( http://www.osronline.com/ ).

Общая классификация драйверов WDM

Все Plug And Play драйверы должны соответствовать модели драйверов Windows (Windows Driver Model, WDM). В соответствии с этой моделью драйверы подразделяются на три типа:

  • Драйверы шин (Bus Drivers). Управляют логическими или физическими шинами. Отвечают за распознавание устройств, подключение их к управляемой ими шине и оповещение о них диспетчера PnP.
  • Функциональные драйверы (Function Drivers). Управляют конкретным типом устройств. Экспортируют рабочий интерфейс устройства операционной системе.
  • Драйверы фильтров (Filter Drivers). Занимая более высокий логический уровень, чем функциональные драйверы, добавляют функциональность или изменяют поведение устройства либо другого драйвера. Этот тип драйверов не обязателен для нормальной работы устройства.

    Драйверы фильтров, в свою очередь, подразделяются на:

    • Драйверы фильтров шин (Bus Filter Drivers).
    • Низкоуровневые драйверы фильтров (Lower-Level Filter Drivers).
    • Высокоуровневые драйверы фильтров (Upper-Level Filter Drivers).

Как вы знаете, каждый драйвер должен создать, как минимум, один объект «устройство», которым он будет управлять. Объекты «устройство» WDM также делит на типы:

  • Объект «физическое устройство» (Physical Device Object, PDO) — Создается драйвером шины по заданию диспетчера PnP, когда драйвер шины, перечисляя устройства на своей шине, сообщает о наличии какого-либо устройства. PDO представляет физический интерфейс устройства.
  • Объект «функциональное устройство» (Functional Device Object, FDO) — Создается функциональным драйвером, который загружается диспетчером PnP для управления обнаруженным устройством. FDO представляет логический интерфейс устройства.
  • Необязательная группа объектов «устройство-фильтр» (Filter Device Object, FiDO). Одна группа таких объектов размещается между PDO и FDO (эти объекты создаются драйверами фильтров шин), вторая — между первой группой FiDO и FDO (эти объекты создаются низкоуровневыми драйверами фильтров), а третья — над FDO (эти объекты создаются высокоуровневыми драйверами фильтров).

Дерево устройств

Имея вышеозначенную классификацию, начнем с того, что определимся, каким образом система, точнее говоря, диспетчер PnP (PnP Manager) — компонент операционной системы, предназначенный для автоматического распознавания установленных устройств, узнает, какие драйверы необходимы для того или иного устройства. Процесс распознавания включает в себя перечисление устройств при загрузке и обнаружение их добавления или удаления во время работы системы.

Во время загрузки системы диспетчер PnP начинает перечисление устройств с виртуальной шины под именем Root. В качестве виртуального драйвера, обслуживающего эту шину, выступает сама система. Логически, всё устройства (физические и виртуальные) подключены к этой шине. Виртуальный драйвер корневой шины (и драйверы других шин тоже) извлекает необходимую информацию из реестра. В реестр сведения об оборудовании заносятся ещё на этапе установки операционной системы. Программа установки обнаруживает установленные устройства и, используя информационные файлы (INF Files), заполняет соответствующие разделы реестра. Перечисляя устройства на корневой шине, её виртуальный драйвер обнаруживает другие шины (физические и виртуальные), например, физическую шину PCI. На основе данных реестра диспетчер PnP определяет, установлен ли в системе драйвер, способный управлять обнаруженным устройством. Если такой драйвер установлен, диспетчер PnP указывает диспетчеру ввода-вывода (I/O Manager) загрузить его. Если подходящий драйвер не установлен, диспетчер PnP пытается его установить. При этом если не обнаружится соответствующего информационного файла или других необходимых файлов, диспетчер PnP взаимодействует с пользователем, который должен указать месторасположение необходимых компонентов. Будучи загруженным, драйвер, обслуживающий обнаруженную шину, перечисляет подключенные к ней устройства. При этом он может обнаружить другие дополнительные шины. Если для работы устройства, обнаруженного на шине, необходим драйвер, он загружается. Такой рекурсивный процесс — перечисление устройств, загрузка драйвера, дальнейшее перечисление — продолжается до тех пор, пока не будут обнаружены и сконфигурированы все устройства в системе. Диспетчер PnP способен обнаруживать добавление/удаление нового устройства и во время работы системы. В результате перечисления образуется так называемое дерево устройств (Device Tree), отражающее иерархические взаимосвязи между всеми установленными в системе устройствами.

Дерево устройств можно просмотреть с помощью диспетчера устройств (Device Manager). Как выглядит дерево устройств на моём компьютере (в меню «Вид» я выбрал «Устройства по подключению» и отметил «Показать скрытые устройства».) показано на Рис. 15.1.

Рис. 15-1. Дерево устройств.

На рисунке вы можете обнаружить некоторые, созданные нами ранее виртуальные устройства, например, ProcessMon (Process creation/destruction monitor), подключенные (также виртуально) к корневой шине. В Windows 2000 диспетчер устройств показывает все установленные ранее виртуальные устройства, а в Windows XP (и в Windows 2003 Server, наверное, тоже) только активные в данный момент. Информация о виртуальных устройствах извлекается диспетчером устройств из разделов реестра HKEY_LOCAL_MACHINESYSTEMCurrentControlSetEnumRootLEGACY_XXX.

Узлы дерева устройств называются узлами устройств (device nodes или devnodes). Каждый узел обслуживается одним или несколькими драйверами. Каким образом система узнает, какие драйверы, какой узел обслуживают?

Все устройства, обнаруженные в процессе установки системы (а также установленные позже), регистрируются в подразделах реестра HKEY_LOCAL_MACHINESYSTEMCurrentControlSetEnum<enumerator><deviceID><instanceID>. Где enumerator — драйвер шины, перечисляющий устройства на шине, deviceID — уникальный идентификатор устройств данного типа, instanceID — уникальный идентификатор экземпляра устройства данного типа (по нему можно различать несколько одинаковых устройств).

В процессе перечисления драйвер шины сообщает диспетчеру PnP идентификаторы обнаруженных устройств: deviceID и instanceID. Используя эту информацию, диспетчер PnP находит в реестре драйверы нужные для узла данного устройства.

Пример подраздела Enum для клавиатуры показан на рис 15-2.

Рис. 15-2. Подраздел реестра ветви Enum для клавиатуры.

Как видно из рисунка, перечислителем является ACPI, идентификатор устройства — PNP0303, а идентификатор экземпляра устройства — 3&13c0b0c5&0. Если заглянуть в %SystemRoot%infkeyboard.inf, то можно обнаружить, что информация в реестр попадает именно из этого информационного файла. К вашей машине, разумеется, может быть подключена клавиатура другого типа.

Функциональный драйвер задается параметром Service. В данном случае это i8042prt. Параметр ClassGUID (Globally Unique Identifier of Class) определяет подраздел класса устройства HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlClass. Этот подраздел содержит сведения о драйвере класса устройства. Драйвер класса определяет общую функциональность для всех устройств данного типа. Он ничего не знает о том, как управлять конкретным устройством, но, используя стандартизованные сервисы, взаимодействует с функциональным драйвером, который, в свою очередь, знает, как управляет конкретным типом устройств. В данном случае драйвером класса является kbdclass. Он исполняет роль своего рода буфера между функциональным драйвером i8042prt и подсистемой Win32 (подробнее в следующей статье). Пример подраздела Class для клавиатуры показан на рис 15-3.

Рис. 15-3. Подраздел реестра ветви Class для клавиатуры.

Содержимое этих двух разделов дает диспетчеру PnP всю информацию необходимую для загрузки драйверов для узла данного устройства. Имена драйверов указывают на подразделы реестра HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices<drivername>.

Загрузка драйверов для узла устройства происходит в следующем порядке:

  • Низкоуровневые драйверы фильтров, указанные в параметрах LowerFilters ветвей реестра Enum и Class.
  • Функциональный драйвер, заданный в параметре Service ветви реестра Enum.
  • Высокоуровневые драйверы фильтров, указанные в параметрах UpperFilters ветвей реестра Enum и Class.

Стек объектов «устройство»

Всё, вышесказанное не имеет прямого отношения к материалу статьи. Используемый в ней драйвер не является PnP-драйвером, а по-прежнему относится к унаследованным драйверам (legacy drivers). Общее понимание механизма перечисления и знание того, что представляет собой дерево устройств необходимо для ввода следующего, уже непосредственно важного для нас, понятия.

Загружая каждый PnP драйвер, диспетчер PnP вызывает стандартную процедуру драйвера AddDevice. В параметре PhysicalDeviceObject передается указатель на объект «физическое устройство», созданный драйвером шины. Загруженный драйвер, в свою очередь, создает свой объект «устройство» и подключает его к объекту «физическое устройство», вызовом функции IoAttachDeviceToDeviceStack. В эту функцию он передает два указателя: переданный ему диспетчером Pnp указатель на объект «физическое устройство» и указатель на созданный им объект «устройство». При этом новый объект всегда подключается к самому верхнему объекту в этой цепочке, вне зависимости от того, имеется ли над PDO другие объекты или нет. Указатель на объект «физическое устройство», при подключении нового объекта, используется как указатель на цепочку объектов, к которой происходит подключение, а не указатель на конкретный объект «устройство». Функция IoAttachDeviceToDeviceStack сама находит самый верхний объект.

Получившаяся конструкция состоит, как минимум, из двух объектов: объект «физическое устройство», созданный драйвером шины, и объект «функциональное устройство», созданный функциональным драйвером, и называется стеком объектов «устройство» (device stack) или просто стеком. Т.о. каждый узел в дереве устройств представлен своим стеком.

Учитывая всё вышесказанное, и имея содержимое разделов реестра Enum и Class, мы можем предсказать, из каких объектов будет состоять стек для узла устройства «клавиатура» (объекты перечисляются снизу вверх):

  • объект «физическое устройство», созданный драйвером шины ACPI.
  • объект «функциональное устройство», созданный функциональным драйвером i8042prt.
  • объект «устройство-фильтр», созданный высокоуровневым драйвером фильтра nmfilter (NTICE Support File).
  • объект «устройство-фильтр», созданный высокоуровневым драйвером фильтра kbdclass.

Оба объекта «устройство-фильтр» созданы высокоуровневыми драйверами фильтров, а драйверов фильтров шины и низкоуровневых драйверов фильтров в данном случае нет.

Просмотреть стеки устройств можно с помощью программы Devide Tree ( osr.com или osronline.com ). Но я избегаю пользоваться этой утилитой, т.к. её работа на трех моих машинах с разными версиями системы неизбежно приводит к появлению «синего экрана смерти» (по крайней мере, в режиме PnP). Удивительно, что эта утилита входит в DDK. Мы воспользуемся более надежной командой !devstack отладчика Kernel Debugger.

Рис. 15-4. Стеки объектов «устройство» для клавиатуры.

На этой машине активна система Terminal Server и у клавиатуры имеется не один, а два стека. Как видите, наши предположения о составе устройств подтвердились. На вашей машине его состав, естественно, может отличаться. Далее мы будем рассматривать классический состав стека для клавиатуры, а именно: Kbdclass сверху, i8042prt посередине, ACPI внизу.

В общем случае стек объектов «устройство» может выглядеть так (см. классификацию драйверов и объектов в WDM выше):

Рис. 15-5. Стек объектов «устройство» для узла устройства (общая схема).

Поскольку каждым объектом «устройство» в стеке управляет драйвер, то очень часто наряду с понятием «стек устройств» употребляют «стек драйверов». Это не совсем верно, но о чём идет речь, надеюсь, понятно. Далее по ходу статьи я тоже буду иногда говорить «стек устройств», и иногда «стек драйверов».

IRP формируется диспетчером в/в или драйвером не принадлежащим стеку и направляется на вершину стека. Если для обработки запроса драйверу требуется помощь нижестоящего драйвера, он перенаправляет IRP ниже по стеку и т.д. IRP всегда идет по стеку сверху вниз. Решение об окончании обработки IRP может быть принято на любом уровне. Мало того, любой драйвер в стеке может сформировать дополнительные IRP (например, разбить запрос чтения из файла на несколько запросов) и разслать его необходимым драйверам. Любой драйвер может отклонить запрос или может модифицировать передаваемые в нем данные. В общем случае, если драйвер получил IRP, то может делать с ним всё что угодно.

Язык с за три минуты

Мне придется использовать исходные коды некоторых системных функций, т.к. по-настоящему разобраться с обработкой IRP без анализа исходного кода, по-моему, невозможно. Эти фрагменты, конечно, не будут истинным кодом операционной системы и будут урезаны, порой весьма значительно. Также опущена вся обработка ошибок: проверки указателей, входных данных и возвращаемых функциями значений, убраны обработчики SEH. Оставлена только самая суть. Для упрощения анализа кода я буду использовать c-подобный псевдоязык (почти чистый с). Вполне допускаю, что вы можете и не знать этого языка, т.к. мы всё же занимаемся разработкой драйверов на ассемблере. Поэтому тезисно приведу базовые конструкции, без которых не обойтись.

На ассемблере место под инициализированную переменную отводится так:

В языке с глобальные и локальные инициализированные переменные определяются так:

Если надо передать адрес переменной в функцию (используя макрос invoke) мы делаем это так:

Программист на с делает это так:

Обратная операция — запись в переменную значения по указателю на переменную — в ассемблере это выглядит так (pwd — указатель на переменную размером в двойное слово):

В с это несколько проще:

Если у нас есть структура FILE_OBJECT, то мы можем записать в её поле DeviceObject указатель на объект «устройство» таким образом:

  1.  pDeviceObject  PDEVICE_OBJECT ?

  2.  mov FileObject.DeviceObject, eax

Программист на с делает это так:

  1.  PDEVICE_OBJECT  pDeviceObject;

  2.  FileObject.DeviceObject = pDeviceObject;

Если же, вместо структуры, в нашем распоряжении указатель на неё, то вышеозначенную операцию нам придется делать примерно так:

  1.  pFileObject PFILE_OBJECT ?

  2.  pDeviceObject  PDEVICE_OBJECT ?

  3.  mov (FILE_OBJECT PTR [ecx]).DeviceObject, eax

Программисту на с, как всегда, немного проще:

  1.  PFILE_OBJECT    pFileObject;

  2.  PDEVICE_OBJECT  pDeviceObject;

  3.  pFileObject->DeviceObject = pDeviceObject;

Если с-программисту надо увеличить значение переменной на единицу, то он может сделать это, как минимум тремя способами, самый очевидный из которых следующий:

Для уменьшения числа IRP циркулирующих в системе можно сделать это и так:

Такой же трюк можно проделать и с другими тремя математическими операциями. Логические операции тоже можно записывать в такой форме. Например:

Эквивилентно

Но, скажу вам по секрету, есть ещё один способ, которым обычно пользуются только гуру или самые ленивые с-программисты для приращения переменных на единицу:

Такой же трюк можно проделывать и с операцией вычитания.

В дальнейшие подробности вдаваться не будем, этого минимума должно хватить. Также имейте в виду, что в DDK есть полный исходный код драйверов kbdclass и i8042prt. Правда, в разных DDK он немного отличается. Соответственно, отличаются и эти драйверы в разных версиях системы.

Мы уже много раз получали IRP, но ещё ни разу не создавали его сами. Поскольку мы собираемся рассмотреть весь жизненный цикл пакета запроса в/в, то без его создания нам никак не обойтись. С этого и начнем.

Допустим, у нас есть имя объекта «устройство», скажем, DeviceKeyboardClass0. Судя по названию, этот объект имеет какое-то отношение к обслуживанию физического устройства «клавиатура». Для чего этот объект нужен и какова его роль, мы подробнее поговорим в следующей статье. Пока нас интересует только одно: у нас есть имя устройства и мы хотим послать ему какой-нибудь IRP. Это можно сделать вызовом функции IoCallDriver, прототип которой выглядит так:

  1.      IN PDEVICE_OBJECT  DeviceObject,

Несмотря на название функции, первым аргументом является указатель на объект «устройство», а не «драйвер», которому адресован IRP. Обрабатывать же IRP будет, естественно, драйвер, это устройство создавший. Второй параметр — указатель на сам пакет запроса в/в.

  1.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  2.  ;                                  I N C L U D E   F I L E S                                        

  3.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  4.  include masm32includew2kntstatus.inc

  5.  include masm32includew2kntddk.inc

  6.  include masm32includew2kntoskrnl.inc

  7.  includelib masm32libw2kntoskrnl.lib

  8.  include masm32MacrosStrings.mac

  9.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  10.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  11.  CCOUNTED_UNICODE_STRING «DeviceKeyboardClass0», g_usTargetDeviceName, 4

  12.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  13.  ;                              D I S C A R D A B L E   C O D E                                      

  14.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  15.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  16.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  17.  IrpComplete proc uses esi edi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP, pContext:PVOID

  18.      assume edi:ptr IO_STATUS_BLOCK

  19.      mov eax, [esi].IoStatus.Status

  20.      mov eax, [esi].IoStatus.Information

  21.      mov [edi].Information, eax

  22.      .if [esi].PendingReturned

  23.          invoke KeSetEvent, pContext, 0, FALSE

  24.      mov eax, STATUS_MORE_PROCESSING_REQUIRED

  25.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  26.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  27.  QueryPnpDeviceState proc uses esi edi ebx pDeviceObject:PDEVICE_OBJECT

  28.  local iosb:IO_STATUS_BLOCK

  29.      mov status, STATUS_NOT_SUPPORTED

  30.      assume esi:ptr DEVICE_OBJECT

  31.      .if ( esi != NULL  &&  [esi]._Type == IO_TYPE_DEVICE )

  32.          movzx eax, [esi].StackSize

  33.          invoke IoAllocateIrp, eax, FALSE

  34.              mov [edi].IoStatus.Status, STATUS_NOT_SUPPORTED

  35.              and [edi].IoStatus.Information, 0

  36.              mov iosb.Status, STATUS_NOT_SUPPORTED

  37.              IoGetNextIrpStackLocation edi

  38.              assume ebx:ptr IO_STACK_LOCATION

  39.              mov [ebx].MajorFunction, IRP_MJ_PNP

  40.              mov [ebx].MinorFunction, IRP_MN_QUERY_PNP_DEVICE_STATE

  41.              invoke KeInitializeEvent, addr keEvent, NotificationEvent, FALSE

  42.              IoSetCompletionRoutine edi, IrpComplete, addr keEvent, TRUE, TRUE, TRUE

  43.              invoke IoCallDriver, esi, edi

  44.              .if eax == STATUS_PENDING

  45.                  invoke DbgPrint, $CTA0(«QueryPnpDeviceState: Request pended. Waiting…n»)

  46.                  invoke KeWaitForSingleObject, addr keEvent, Executive, KernelMode, FALSE, NULL

  47.              .if status == STATUS_SUCCESS

  48.                  invoke DbgPrint, $CTA0(«QueryPnpDeviceState: Device State: %08Xn»), iosb.Information

  49.              mov status, STATUS_INSUFFICIENT_RESOURCES

  50.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  51.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  52.  DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

  53.  local pTargetDeviceObject:PDEVICE_OBJECT

  54.  local pTargetFileObject:PFILE_OBJECT

  55.      invoke IoGetDeviceObjectPointer, addr g_usTargetDeviceName, FILE_READ_DATA,

  56.                                       addr pTargetFileObject, addr pTargetDeviceObject

  57.      .if eax == STATUS_SUCCESS

  58.          invoke QueryPnpDeviceState, pTargetDeviceObject

  59.          invoke ObDereferenceObject, pTargetFileObject

  60.      mov eax, STATUS_DEVICE_CONFIGURATION_ERROR

  61.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  62.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  63.  set drv=QueryPnpDeviceState

  64.  masm32binml /nologo /c /coff %drv%.bat

  65.  masm32binlink /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj

Получить указатель на нужное нам устройство по его имени мы можем с помощью IoGetDeviceObjectPointer. В случае успеха, эта функция вернёт даже два указателя: один — собственно указатель на нужное нам устройство в переменной pTargetDeviceObject, а второй — указатель на объект «файл» ассоциированный с этим устройством в переменной pTargetFileObject. Откуда взялся объект «файл»? Заглянем внутрь функции IoGetDeviceObjectPointer, а также двух других, которые она вызывает.

  1.      IN PDEVICE_OBJECT pDeviceObject

  2.      while pDeviceObject->AttachedDevice

  3.          pDeviceObject = pDeviceObject->AttachedDevice

  4.    IoGetRelatedDeviceObject(

  5.      IN PFILE_OBJECT pFileObject

  6.      PDEVICE_OBJECT pDeviceObject

  7.      pDeviceObject = pFileObject->Vpb->DeviceObject

  8.      pDeviceObject = pFileObject->DeviceObject->Vpb->DeviceObject

  9.      pDeviceObject = pFileObject->DeviceObject

  10.      if pDeviceObject->AttachedDevice != NULL

  11.          pDeviceObject = IoGetAttachedDevice( pDeviceObject )

  12.    IoGetDeviceObjectPointer(

  13.      IN PUNICODE_STRING  pusObjectName,

  14.      IN ACCESS_MASK      DesiredAccess,

  15.      OUT PFILE_OBJECT    *out_pFileObject,

  16.      OUT PDEVICE_OBJECT  *out_pDeviceObject

  17.      InitializeObjectAttributes( &oa, pusObjectName, … )

  18.      ZwOpenFile( &hFile, DesiredAccess, &oa, … )

  19.      ObReferenceObjectByHandle( hFile, 0, IoFileObjectType, KernelMode, &pFileObject, NULL )

  20.      *out_pFileObject   = pFileObject

  21.      *out_pDeviceObject = IoGetRelatedDeviceObject( pFileObject )

Первым делом, функция IoGetDeviceObjectPointer получает описатель объекта «файл» (представлен структурой FILE_OBJECT).

Вспомните, как в программе управления драйвером мы получаем описатель для взаимодействия с его устройством. Мы вызываем функцию CreateFile, которая создает объект «файл», представляющий не собственно файл на диске, а виртуальное устройство (структура DEVICE_OBJECT), созданное драйвером. Т.е. на самом деле, описатель файла используется для ввода-вывода в устройство. Такая схема нужна, во-первых, для разграничения прав доступа, т.к. в структуре DEVICE_OBJECT нет, например, полей WriteAccess и SharedRead, а в FILE_OBJECT такие поля есть, во-вторых, в объекте «файл» можно хранить некоторые другие атрибуты операции ввода-вывода. Адрес истинного получателя пакета запроса в/в, в нашем случае, находится в поле FILE_OBJECT.DeviceObject. Итак, вызов ZwOpenFile, так же как и CreateFile, приводит к созданию объекта «файл», а значит формированию IRP типа IRP_MJ_CREATE и посылке его целевому устройству (в нашем случае устройству DeviceKeyboardClass0). Этот пакет, как вы понимаете, попадает в драйвер обслуживающий это устройство (устройство DeviceKeyboardClass0 обслуживает драйвер kbdclass). Т.е. решение об удовлетворении запроса — вызове IoCompleteRequest со статусом STATUS_SUCCESS — принимает обслуживающий драйвер.

Вот фрагмент функции KeyboardClassCreate драйвера kbdclass:

  1.     PIO_STACK_LOCATION   pStack;

  2.     pStack = IoGetCurrentIrpStackLocation( pIrp )

  3.     if  pIrp->RequestorMode == UserMode

  4.         pStack->Parameters.Create.SecurityContext->DesiredAccess & FILE_READ_DATA  {

  5.         status = STATUS_ACCESS_DENIED

  6.         goto KeyboardClassCreateEnd

Как видите, kbdclass отклоняет попытку режима пользователя получить доступ к его устройствам для чтения.

Кстати, раз уж мы так подробно собрались во всем разбираться, посмотрим на внутренности макроса IoGetCurrentIrpStackLocation, который мы сами уже много раз использовали (полная версия в ntddk.inc).

  1.  IoGetCurrentIrpStackLocation MACRO pIrp:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

Марос IoGetCurrentIrpStackLocation просто извлекает указатель на текущий блок стека из поля CurrentStackLocation.

Получив описатель объекта «файл», функция IoGetDeviceObjectPointer дополнительно увеличивает счетчик ссылок в объекте «файл», вызовом ObReferenceObjectByHandle. Затем IoGetDeviceObjectPointer пытается получить указатель на целевое устройство, сопоставленное с объектом «файл», вызывая IoGetRelatedDeviceObject. В зависимости от принадлежности объекта «файл» тому или иному типу устройств, IoGetRelatedDeviceObject может извлечь необходимый указатель из разных мест (в нашем случае из поля pFileObject->DeviceObject). Далее, обратите на это особое внимание, если к целевому устройству прикреплено ещё одно устройство (об этом говорит ненулевое значение в поле pDeviceObject->AttachedDevice), функция IoGetAttachedDevice «поднимается» по стеку устройств до самого верха и возвращает указатель на устройство, находящееся на вершине стека. Если же прикрепленных устройств нет, то возвращается указатель на само целевое устройство, т.е. то, имя которого было передано в IoGetDeviceObjectPointer. Запомните: Функция IoGetAttachedDevice всегда возвращает указатель на объект «устройство», находящийся на вершине стека.

После получения указателя IoGetDeviceObjectPointer закрывает описатель объекта «файл» и в этот момент счетчик описателей становится равным нулю, что приводит к формированию и посылке драйверу kbdclass IRP типа IRP_MJ_CLEANUP. Т.о. функция IoGetDeviceObjectPointer вернет указатели на два объекта: «файл» и «устройство». Причем в объекте «устройство» значение счетчиков указателей и описателей не меняется, а в объекте «файл» равно 1 и 0, соответственно. Единичное значение счетчика указателей достигается благодаря дополнительному вызову ObReferenceObjectByHandle. До тех пор, пока существует объект «файл», объект «устройство», с которым он связан, не будет удален и соответственно драйвер, управляющий устройством, также не может быть выгружен, т.к. в управляемом им объекте «устройство», будет установлен соответствующий флаг и при попытке выгрузить такой драйвер он отмечается как ожидающий выгрузки, а процедура DriverUnload просто не будет вызвана. Только после того, как будут удалены все управляемые драйвером устройства, драйвер сможет отработать DriverUnload.

Т.о. в случае с IoGetDeviceObjectPointer схема точно такая же, какой пользуется режим пользователя, получая описатель объекта «файл» и таким образом блокируя связанный с ним объект. При этом сам объект «файл» относится к любому источнику или приемнику ввода-вывода (собственно файлу или каталогу, именованному каналу, почтовому ящику и др.), который рассматривается как файл. При таком механизме все считываемые или записываемые данные представляются простыми потоками байтов, направляемыми в виртуальные файлы. По окончании работы, программа режима пользователя закрывает описатель файла, а мы должны будем удалить ссылку, вызовом ObDereferenceObject. При этом счетчик указателей в объекте «файл» обнулится, и это приведет к формированию и посылке драйверу kbdclass IRP типа IRP_MJ_CLOSE. Только после этого объект «файл» будет удален.

Вернемся к исходному коду нашего драйвера.

  1.          invoke QueryPnpDeviceState, pTargetDeviceObject

Теперь у нас есть адресат для посылки IRP. Осталось только сформировать сам пакет.

IRP состоит из тела или заголовка (собственно структура IRP) и одного или нескольких блоков стека (stack locations). Тело IRP хранит общую информацию о запросе ввода-вывода: указатели на буферы, данные о состоянии и др. Блоки стека содержат информацию специфичную для конкретного этапа обработки IRP. Передавая IRP на обработку драйверу, диспетчер в/в (или драйвер самостоятельно создающий IRP, как мы в этом примере) заполняет верхний блок стека. Если драйвер, получивший IRP, решает отправить его на дальнейшую обработку нижестоящему драйверу, он заполняет следующий блок стека (т.к. это стек, то в памяти следующий блок стека находится по меньшему адресу — подробнее об этом чуть позже) и передает IRP ниже и т.д. Т.о. блоки стека — по одному на каждый вызываемый драйвер — хранят информацию, необходимую каждому драйверу для обработки своей части запроса.

  1.      assume esi:ptr DEVICE_OBJECT

  2.      .if ( esi != NULL  &&  [esi]._Type == IO_TYPE_DEVICE )

  3.          movzx eax, [esi].StackSize

  4.          invoke IoAllocateIrp, eax, FALSE

Создать IRP можно одной из четырех функций: IoBuildSynchronousFsdRequest, IoBuildDeviceIoControlRequest, IoBuildAsynchronousFsdRequest и IoAllocateIrp. Если быть совсем точным, то можно сделать IRP вообще вручную, выделив память из пула или ассоциативного списка, но тогда все его поля придется заполнять самим. Мы воспользуемся самой универсальной из четырех вышеперечисленных функций — IoAllocateIrp. В отличие от трех остальных, с её помощью можно создавать IRP любого типа.

По соображениям лучшей производительности, память под IRP выделяется в одном из двух ассоциативных списков, индивидуальных для каждого процессора (структуры управляющие списками хранятся в специфичной для каждого процессора структуре KPRCB). Если нужен IRP с одним блоком стека, то используется ассоциативный список малых IRP. Если IRP должен содержать более одного блока стека — используется ассоциативный список больших IRP. Такие IRP содержат 8 блоков стека (эта цифра хранится в переменной ядра IopLargeIrpStackLocations). В Windows NT4 эта цифра равнялась 4, но с приходом PnP глубина стеков увеличилась. Если же IRP требует более 8 блоков стека или ассоциативный список пуст, то диспетчеру в/в ничего другого не остается, как выделить память под IRP из неподкачиваемого пула. Перед тем как вернуть управление, IoAllocateIrp обнуляет весь IRP и инициализирует некоторые его поля.

  1.     Irp.Size                              = sizeof(IRP) + StackSize * sizeof(IO_STACK_LOCATION)

  2.     Irp.AllocationFlags                   = <some flags>

  3.     Irp.StackCount                        = StackSize

  4.     Irp.CurrentLocation                   = StackSize + 1

  5.     Irp.Tail.Overlay.CurrentStackLocation = &Irp + sizeof(IRP) + StackSize * sizeof(IO_STACK_LOCATION)

Самые важные для нас на данный момент поля это:

  • Irp.StackCount — максимально необходимое количество блоков стека в IRP. Это поле будет равно значению первого параметра переданного в IoAllocateIrp. Мы извлекаем его из объекта «устройство», которому собираемся отправить IRP. Каждый объект «устройство» знает, сколько под ним объектов и, соответственно, сколько нужно блоков стека.
  • Irp.CurrentLocation — порядковый номер текущего блока стека (отсчет идет в обратном порядке). Каждый раз при передаче IRP нижестоящему драйверу функция IoCallDriver уменьшает значение этого поля на единицу. Изначально же, как видите, оно на один больше чем действительно необходимо.
  • Irp.Tail.Overlay.CurrentStackLocation — указатель на текущий блок стека. Каждый раз при передаче IRP нижестоящему драйверу функция IoCallDriver уменьшает его значение на размер структуры IO_STACK_LOCATION. Изначально оно указывает на недействительный блок стека, т.е. на область памяти сразу за концом IRP. Строго говоря, это не всегда так. Например, если IRP выделен из ассоциативного списка больших IRP, то у него 8 блоков стека, а мы, допустим, заказали 5. Тогда CurrentStackLocation будет указывать на один из лишних блоков стека. Если же мы просили IRP с одним блоком или он выделен из пула, то CurrentStackLocation указывает на «чужую» память.

По возвращении из IoAllocateIrp наш IRP выглядит так (я использовал команду irp отладчика SoftICE с ключом -f):

  1.  &ThreadListEntry     : 83887018

  2.  IoStatus.Status      : 00000000

  3.  IoStatus.Information : 00000000

  4.  CurrentLocation      : <b>06</b>

  5.  Overlay              : 00000000 00000000

  6.  CancelRoutine *      : 00000000

  7.         &DeviceQueueEntry : 83887048

  8.         AuxiliaryBuffer * : 00000000

  9.         CurrentStackLoc * : <b>8388712C</b>

  10.         OrigFileObject *  : 00000000

  11.  StackLocation 1 at 83887078:

  12.  StackLocation 2 at 8388709C:

  13.  StackLocation 3 at 838870C0:

  14.  StackLocation 4 at 838870E4:

  15.  StackLocation 5 at 83887108:

  16.  CurrentStackLocation at <b>8388712C</b>:

  17.  <заполнен нулями>                      <- недействительный блок стека

IoAllocateIrp делает только заготовку будущего IRP. Кое-какие поля требуется заполнить вручную.

  1.              mov [edi].IoStatus.Status, STATUS_NOT_SUPPORTED

  2.              and [edi].IoStatus.Information, 0

  3.              mov iosb.Status, STATUS_NOT_SUPPORTED

Для формирования IRP разных типов может потребоваться заполнение разных полей. Я заполнил только самые необходимые для нас и вам не следует принимать это за образец. Подробности можно посмотреть в DDK.

После заполнения тела IRP мы должны сформировать блок стека для драйвера, которому мы адресуем запрос. Если использовать нумерацию блоков стека как её использует SoftIce, то мы должны заполнить блок стека за номером 5. Как вы помните, сейчас поле CurrentStackLocation указывает на недействительный блок стека. Для получения указателя на следующий блок стека, принадлежащий драйверу которому мы адресуем запрос, используется макрос IoGetNextIrpStackLocation:

  1.  IoGetNextIrpStackLocation MACRO pIrp:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      sub eax, sizeof IO_STACK_LOCATION

Пусть вас не смущает слово next в имени макроса. Мы ведь имеем дело со стеком. «Следующий драйвер» означает нижестоящий драйвер, а «следующий блок стека» — блок стека с адресом на sizeof(IO_STACK_LOCATION) меньше чем текущий блок стека. Соответственно «предыдущий драйвер» означает вышестоящий драйвер, а «предыдущий блок стека» — блок стека с адресом на sizeof(IO_STACK_LOCATION) больше чем текущий блок стека. Макрос IoGetNextIrpStackLocation берет значение из поля CurrentStackLocation и уменьшает его на размер структуры IO_STACK_LOCATION. Таким образом, мы движемся в сторону меньших адресов по направлению к телу IRP.

  1.              IoGetNextIrpStackLocation edi

  2.              assume ebx:ptr IO_STACK_LOCATION

  3.              mov [ebx].MajorFunction, IRP_MJ_PNP

  4.              mov [ebx].MinorFunction, IRP_MN_QUERY_PNP_DEVICE_STATE

Мы посылаем запрос типа IRP_MJ_PNP, а дополнительный код IRP_MN_QUERY_PNP_DEVICE_STATE определяет какую именно информацию о PnP характеристиках устройства мы хотим получить.

  1.               invoke KeInitializeEvent, addr keEvent, NotificationEvent, FALSE

Инициализируем объект «событие». На этом объекте мы будем ждать момента завершения IRP. Тип события может быть и SyncronizationEvent, т.к. всё равно кроме нас, его никто ждать не будет. В исходных кодах драйверов можно встретить оба варианта.

Буквально через одну строку мы собираемся послать IRP драйверу kbdclass. Если мы не предпримем специальных мер, то никогда уже не сможем увидеть наш IRP. Как это не покажется парадоксальным, с первого взгляда, но после вызова IoCallDriver обращаться к IRP нельзя. К концу статьи, надеюсь, будет ясно почему. Единственная возможность вновь получить контроль над IRP — это установить специальную процедуру — процедуру завершения (completion routine). Процедура завершения будет вызвана, в тот момент, когда какой-либо драйвер ниже по стеку завершит IRP вызовом IoCompleteRequest. Одной из задачь функции IoCompleteRequest как раз и является задача вызова всех процедур завершения. Нашу процедуру завершения я назвал IrpComplete, а установить её можно с помощью макроса IoSetCompletionRoutine (полный вариант в ntddk.inc):

  1.  IoSetCompletionRoutine MACRO pIrp:REQ, Routine:REQ, CompletionContext:REQ, Success:REQ, Error:REQ, Cancel:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      sub eax, sizeof IO_STACK_LOCATION

  4.      assume eax:ptr IO_STACK_LOCATION

  5.      pop [eax].CompletionRoutine

  6.      and byte ptr [eax].Control, 0

  7.          or byte ptr [eax].Control, SL_INVOKE_ON_SUCCESS

  8.          or byte ptr [eax].Control, SL_INVOKE_ON_ERROR

  9.          or byte ptr [eax].Control, SL_INVOKE_ON_CANCEL

Первый параметр — указатель на IRP, при завершении которого должна быть вызвана процедура, указатель на которую передается во втором параметре. Третий параметр — указатель на любые данные. Этот указатель будет передан в процедуру завершения, и в нем мы укажем адрес нашего объекта «событие», которое процедура завершения, при необходимости, должна будет перевести в сигнальное состояние. Три последних параметра определяют, в каком случае будет вызвана процедура. Нам нужно, чтобы она была вызвана в любом случае: при завершении IRP с кодом успеха, при завершении IRP с кодом ошибки, при отмене IRP. Т.е. как бы ни завершился IRP, мы всё равно его перехватим на обратном пути. Обратите внимание, что макрос IoSetCompletionRoutine использует следующий блок стека, т.е. предназначенный для нижестоящего драйвера. Т.е. адрес процедуры завершения и её параметр помещаются не в блок стека драйвера, которому он принадлежит, а в блок стека нижестоящего драйвера. Почему мы лезем в чужой блок стека со своей процедурой завершения? Дело в том, что, во-первых, у нас нет своего блока стека, точнее он нам не нужен. Мы же сами формируем IRP и прекрасно знаем, что в нем содержится. С другой стороны, драйверу, стоящему ниже в стеке, который будет завершать IRP, не нужна процедура завершения. Он же сам его завершает и прекрасно знает как.

И ещё один очень важный момент, касающийся процедур завершения. В общем случае обработка ввода/вывода с физического устройства проходит по следующей схеме. Драйвер инициирует операцию в/в. Когда устройство завершает операцию, то генерирует прерывание, которое обрабатывается процедурой обработки прерывания (Interrupt Service Routine, ISR), зарегистрированной драйвером. Причем обработка будет происходить в контексте того потока, который был текущим на момент прерывания, а это случайный поток. Т.к. ISR работает на повышенном IRQL (больше DISPATCH_LEVEL), работа всех остальных потоков на данном процессоре блокируется. Мало того, блокируются (маскируются) все прерывания с таким же или более низким уровнем. Для того чтобы обработать возможные прерывания от менее приоритетных устройств, необходимо как можно быстрее понизить IRQL. Для этого ISR делает только то, что необходимо сделать немедленно и ставит в очередь так называемый вызов отложенной процедуры (Deferred Procedure Call, DPC). DPC работает при IRQL = DISPATCH_LEVEL. Когда IRQL понижается до DISPATCH_LEVEL, система вызывает отложенную процедуру и она делает дополнительные операции по завершению IRP. В самой последней фазе отложенная процедура вызывает IoCompleteRequest, которая, как я сказал выше, вызывает все процедуры завершения. Поэтому процедура завершения может быть вызвана в контексте случайного потока и при IRQL меньше или равном DISPATCH_LEVEL.

Раз процедура завершения может быть вызвана на повышенном IRQL, то очевидно, что и она сама и все данные, к которым она обращается должны находиться в неподкачиваемой памяти. Наша процедура завершения обращается к двум структурам: IO_STATUS_BLOCK и KEVENT (сам IRP не в счет, т.к. он всегда выделяется из неподкачиваемой памяти), которые располагаются в стеке потока, выполняющего процедуру QueryPnpDeviceState. Если этот поток будет ждать, то его стек может быть выгружен в файл подкачки (то, что, в данном случае, это системный поток не в счет). Чтобы запретить системе это делать, необходимо указывать KernelMode в параметре WaitMode функций ожидания. Я уже как-то раз говорил об этом, но, на всякий случай, повторяю.

  1.              IoSetCompletionRoutine edi, IrpComplete, addr keEvent, TRUE, TRUE, TRUE

  2.              invoke IoCallDriver, esi, edi

Ну что же. Теперь у нас есть всё необходимое: адресат, сформированный IRP и процедура завершения, готовая перехватить его на обратном пути. Вызовом функции IoCallDriver, посылаем IRP драйверу, обслуживающему объект «устройство», указатель на который содержится в первом параметре.

Реализация функции IoCallDriver на удивление проста:

  1.      IN PDEVICE_OBJECT  pDeviceObject,

  2.      PIO_STACK_LOCATION   pStack

  3.      PDRIVER_OBJECT       pDriverObject

  4.      if  pIrp->CurrentLocation &lt;= 0  {

  5.          KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, pIrp, … )

  6.      pIrp->Tail.Overlay.CurrentStackLocation -= sizeof(IO_STACK_LOCATION)

  7.      pStack = pIrp->Tail.Overlay.CurrentStackLocation

  8.      pStack->DeviceObject = pDeviceObject

  9.      pDriverObject = pDeviceObject->DriverObject

  10.      status = pDriverObject->MajorFunction[pStack->MajorFunction]( pDeviceObject, pIrp )

Сначала IoCallDriver уменьшает значение CurrentLocation на единицу и если оно вдруг стало равно нулю или ещё меньше, то система показывает «голубой экран смерти», т.к. нулевое значение в поле CurrentLocation говорит о том, что мы исчерпали все блоки стека и если IoCallDriver пойдет дальше, то просто будет «затирать» тело IRP, что рано или поздно всё равно приведет к краху. Затем значение в CurrentStackLocation уменьшается на размер структуры IO_STACK_LOCATION. Вот теперь оба поля: CurrentLocation и CurrentStackLocation соответствуют заполненному нами блоку стека. CurrentLocation равно 5, а CurrentStackLocation — 83887108. Сейчас наш IRP выглядит так:

  1.  &ThreadListEntry     : 83887018

  2.  IoStatus.Status      : C00000BB

  3.  IoStatus.Information : 00000000

  4.  CurrentLocation      : <b>05</b>

  5.  Overlay              : 00000000 00000000

  6.  CancelRoutine *      : 00000000

  7.         &DeviceQueueEntry : 83887048

  8.         AuxiliaryBuffer * : 00000000

  9.         CurrentStackLoc * : <b>83887108</b>

  10.         OrigFileObject *  : 00000000

  11.  StackLocation 1 at 83887078:

  12.  StackLocation 2 at 8388709C:

  13.  StackLocation 3 at 838870C0:

  14.  StackLocation 4 at 838870E4:

  15.  CurrentStackLocation at <b>83887108</b>:

  16.  MajorFunction     : 1B IRP_MJ_PNP

  17.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

  18.  Others            : 00000000 00000000 00000000 00000000

  19.  DeviceObject *    : 81852AB0

  20.  CompletionRout *  : ED5E14C0

Далее IoCallDriver помещает в поле DeviceObject текущего блока стека указатель на вызываемый объект «устройство». Этот указатель может потребоваться процедуре завершения. Затем из объекта «устройство» извлекается указатель на обслуживающий его драйвер и вызывается одна из процедур диспетчеризации драйвера. Т.к. в pStack->MajorFunction находится IRP_MJ_PNP, IoCallDriver берет из соответствующего элемента массива MajorFunction указатель на процедуру и передает ей адреса объекта «устройство» и IRP (вспомните любую функцию диспетчеризации, коих мы написали уже не мало). Если драйвер не занёс в соответствующее поле массива MajorFunction указатель на свою процедуру обработки данного типа IRP, то по умолчанию там находится указатель на системную функцию IopInvalidDeviceRequest, которая просто возвращает STATUS_INVALID_DEVICE_REQUEST и на этом обработка IRP будет завершена, не начавшись. Если же нужная процедура у драйвера имеется, а kbdclass имеет процедуру для обработки запросов IRP_MJ_PNP, то мы в нее и попадем, а IoCallDriver вернет то, что вернет эта процедура.

Теперь, прежде чем мы погрузимся в kbdclass, немного «уйдем в сторону» и представим, что IRP, только что сформированный нами, не IRP типа IRP_MJ_PNP, а гипотетический IRP_MJ_UNKNOWN, и посылаем мы его абстрактному драйверу unknown, процедура диспетчеризации которого выглядит приблизительно так:

  1.  DispatchUnknown proc uses esi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

  2.          lea ecx, [esi].Tail.Overlay.ListEntry

  3.          InsertTailList addr g_IrpQueue, ecx

  4.          mov status, STATUS_PENDING

  5.          mov status, STATUS_SUCCESS

  6.          mov [esi].IoStatus.Status, STATUS_SUCCESS

  7.          mov [esi].IoStatus.Information, SOME_INFORMATION

  8.          fastcall IofCompleteRequest, esi, IO_NO_INCREMENT

Драйвер unknown либо сразу завершает IRP, либо ставит его в очередь, для того чтобы завершить позже. Сначала рассмотрим первый случай.

Прежде чем добавить IRP в очередь, драйвер должен отметить его как ожидающий завершения (pending). Это можно сделать с помощью макроса IoMarkIrpPending, который выглядит так:

  1.  IoMarkIrpPending MACRO pIrp:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      or (IO_STACK_LOCATION PTR [eax]).Control, SL_PENDING_RETURNED

Обратите внимание — флаг означающий, что IRP ожидает завершения, помещается не в тело IRP, а в текущий блок стека. Т.е. каждый драйвер независимо от других может проделать эту операцию.

Дальше драйвер помещает IRP в очередь и возвращает код STATUS_PENDING, говорящий вышестоящему драйверу о том, что завершение IRP отложено на неопределенное время. В нашем случае, вышестоящий драйвер — наш драйвер и ему необходимы результаты завершения IRP. Поэтому будем ждать, на созданном нами объекте «событие».

Существует несколько механизмов, которыми драйверы могут пользоваться для постановки IRP в очередь, но в итоге все сводится к добавлению IRP в двусвязный список. В самом простом случае можно использовать поле IRP.Tail.Overlay.ListEntry. Для того чтобы гарантировать себе монопольный доступ к очереди драйверы используют блокировку. Как работает очередь и блокировка, сейчас не важно.

По прошествии некоторого времени драйвер решает удалить IRP из очереди и завершить его.

  1.      RemoveHeadList addr g_IrpQueue

  2.      sub eax, _IRP.Tail.Overlay.ListEntry

  3.      mov esi, eax           ; esi -> _IRP

  4.      mov [esi].IoStatus.Status, STATUS_SUCCESS

  5.      mov [esi].IoStatus.Information, SOME_INFORMATION

  6.      fastcall IofCompleteRequest, esi, IO_NO_INCREMENT

Это может произойти в контексте любого потока и в любой момент (в результате прерывания). В данном случае, для нас важно лишь то, что драйвер вызывает IoCompleteRequest.

  1.      PIO_STACK_LOCATION pStack

  2.      pStack->MinorFunction               = 0

  3.      pStack->Parameters.Others.Argument1 = 0

  4.      pStack->Parameters.Others.Argument2 = 0

  5.      pStack->Parameters.Others.Argument3 = 0

  6.      pStack->Parameters.Others.Argument4 = 0

  7.      pStack->FileObject                  = NULL

  8.      PIO_STACK_LOCATION pStack

  9.      if  pIrp->CurrentLocation > pIrp->StackCount + 1  {

  10.          KeBugCheckEx( MULTIPLE_IRP_COMPLETE_REQUESTS, … )

  11.      ASSERT( pIrp->IoStatus.Status != STATUS_PENDING )

  12.      pStack = IoGetCurrentIrpStackLocation( pIrp )

  13.      pIrp->Tail.Overlay.CurrentStackLocation += sizeof(IO_STACK_LOCATION)

  14.      while  pIrp->CurrentLocation <= pIrp->StackCount + 1  {

  15.          pIrp->PendingReturned = pStack->Control & SL_PENDING_RETURNED

  16.          if  pIrp->IoStatus.Status == STATUS_SUCCESS  &&  pStack->Control & SL_INVOKE_ON_SUCCESS

  17.              pIrp->IoStatus.Status != STATUS_SUCCESS  &&  pStack->Control & SL_INVOKE_ON_ERROR

  18.              pIrp->Cancel == TRUE  &&  pStack->Control & SL_INVOKE_ON_CANCEL

  19.              ZeroIrpStackLocation( pStack )

  20.              PDEVICE_OBJECT    pDeviceObject

  21.              if  pIrp->CurrentLocation == pIrp->StackCount + 1  {

  22.                  pDeviceObject = IoGetCurrentIrpStackLocation( pIrp )->DeviceObject

  23.              status = pStack->CompletionRoutine( pDeviceObject, pIrp, pStack->Context )

  24.              if  status == STATUS_MORE_PROCESSING_REQUIRED  {

  25.              if  pIrp->PendingReturned  &&  pIrp->CurrentLocation <= pIrp->StackCount  {

  26.              ZeroIrpStackLocation( pStack )

  27.          pStack += sizeof(IO_STACK_LOCATION)

  28.          pIrp->Tail.Overlay.CurrentStackLocation += sizeof(IO_STACK_LOCATION)

Рис. 15-6. Блок-схема функции IoCompleteRequest.

IoCompleteRequest должна пройтись по всем блокам стека, участвовавшим в обработке IRP, причем в обратном порядке, и вызвать все процедуры завершения. Когда IRP продвигается вниз, то значения полей CurrentLocation и CurrentStackLocation уменьшаются с каждым вызовом IoCallDriver (исключением является случай, когда драйвер передает свой собственный блок стека нижестоящему драйверу, пользуясь макросом IoSkipCurrentIrpStackLocation). IoCompleteRequest проделывает обратную работу, начиная с текущего блока стека, т.е. того, указатель на который находится в поле CurrentStackLocation (именно этот блок стека был текущим для драйвера вызвавшего IoCompleteRequest).
Когда IoCompleteRequest «поднимется» до самого верха, значения этих двух полей будут такими же, какими они были сразу после вызова IoAllocateIrp. Т.е. значение в поле CurrentLocation должно быть на единицу больше чем StackCount, а CurrentStackLocation будет указывать на недействительный блок стека.
Поэтому если CurrentLocation больше или равно StackCount + 1, это означает, что IRP уже был завершен. А завершать два раза IRP это примерно то же самое, что повторно вызывать ExFreePool с одним и тем же указателем. «Синий экран смерти» тут как нельзя кстати. Поэтому завершать IRP можно только один раз.

Дальше идет отладочное утверждение ASSERT. Код, заключенный в макрос ASSERT попадает только в отладочный выпуск (checked build) системы. В свободном выпуске (free build) системы отловить такой баг можно с помощью Driver Verifier. Я специально добавил эту строку, т.к. завершение IRP с кодом STATUS_PENDING — очень распространенная ошибка. IRP может либо завершаться, либо ожидать завершения. Третьего не дано.

Правило:

Завершать IRP с кодом STATUS_PENDING нельзя.

Далее IoCompleteRequest получает указатель на текущий блок стека, вызовом макроса IoGetCurrentIrpStackLocation. А какой блок стека текущий в данном случае? Сейчас текущим является блок стека, принадлежащий драйверу unknown. Ведь IRP продвигался вниз всего на «один шаг». Если бы драйверу unknown понадобился указатель на его блок стека, то вызовом IoGetCurrentIrpStackLocation, он получил бы тот же самый адрес.

Потом IoCompleteRequest крутит цикл, проходя по всем участвовавшим в обработке IRP блокам стека в обратном порядке. Если в блоке стека установлен флаг SL_PENDING_RETURNED, значит драйвер, которому он принадлежит, вызывал IoMarkIrpPending. Если это так, то устанавливается ненулевое значение в поле IRP.PendingReturned. А если флаг SL_PENDING_RETURNED не установлен, то поле IRP.PendingReturned обнуляется. Это нужно для того, чтобы вышестоящий драйвер в своей процедуре завершения смог видеть, что нижестоящий драйвер отмечал IRP как ожидающий завершения. Обращаться к чужим блокам стека драйверы не должны (исключение — копирование/заполнение блока стека при передаче IRP вниз по стеку). IoCompleteRequest даже специально обнуляет некоторые поля обработанного блока стека используя ZeroIrpStackLocation (на самом деле это макрос, а не функция). Поэтому SL_PENDING_RETURNED, как бы «перекладывается» в поле PendingReturned самого IRP. Когда мы доберемся до схемы на рис. 15-7, предназначение поля PendingReturned станет более понятно.

Если вышестоящий драйвер установил процедуру завершения (вы должны помнить, что драйверы устанавливают процедуры завершения в блоке стека, принадлежащем нижестоящему драйверу), она вызывается. В процедуру завершения передается указатель на объект «устройство», принадлежащий драйверу установившему эту процедуру. Поскольку инициатор запроса (наш драйвер, в данном случае) не имеет своего блока стека, то в качестве указателя на объект «устройство» он получит NULL.

Если процедуре завершения потребуется обратиться к текущему блоку стека она тоже может использовать макрос IoGetCurrentIrpStackLocation. А какой блок стека она получит? Процедура завершения получит блок стека, принадлежащий её драйверу. Т.е. и в процедуре диспетчеризации и в процедуре завершения IoGetCurrentIrpStackLocation возвращает один и тот же указатель. Можем ли мы как создатели IRP в нашей процедуре завершения вызвать IoGetCurrentIrpStackLocation? Нет. Точнее указатель то мы получим, но на недействительный блок стека. Ведь своего собственного блока стека у нас нет, т.к. он нам не нужен.

Если процедура завершения вернула STATUS_MORE_PROCESSING_REQUIRED, то IoCompleteRequest, не делая ни одного лишнего движения, сразу возвращает управление, т.к. трогать IRP она уже не имеет права — возможно, IRP уже не существует. В нашем случае это именно так, ведь мы в процедуре завершения вызываем IoFreeIrp и для того, чтобы заставить IoCompleteRequest немедленно прекратить дальнейшие действия по завершению IRP, возвращаем STATUS_MORE_PROCESSING_REQUIRED. Если же процедура завершения возвращает любой другой код, то IoCompleteRequest продолжает работу. DDK рекомендует в качестве «любого другого кода» возвращать STATUS_SUCCESS просто потому, что он равен 0, а это приводит к генерации компилятором более оптимального кода. В более поздних DDK можно найти такие определения:

  1.  #define STATUS_CONTINUE_COMPLETION      STATUS_SUCCESS

  2.  typedef enum _IO_COMPLETION_ROUTINE_RESULT {

  3.      ContinueCompletion = STATUS_CONTINUE_COMPLETION,

  4.      StopCompletion     = STATUS_MORE_PROCESSING_REQUIRED

  5.  } IO_COMPLETION_ROUTINE_RESULT, *PIO_COMPLETION_ROUTINE_RESULT;

Имена констант ContinueCompletion и StopCompletion значительно лучше отражают суть, чем STATUS_SUCCESS и STATUS_MORE_PROCESSING_REQUIRED. Т.о., возвращая StopCompletion, мы говорим функции IoCompleteRequest, что она должна немедленно прекратить работу и вернуть управление. Если мы возвращаем ContinueCompletion (точнее говоря, не возвращаем StopCompletion), то IoCompleteRequest продолжает процесс завершения IRP.

Для чего нам нужно остановить IoCompleteRequest? Мы, как создатели IRP, не можем допустить, чтобы диспетчер в/в завершал созданный нами IRP. Это наша работа. Единственная возможность это сделать — установить процедуру завершения.

Если в обрабатываемом блоке стека нет указателя на процедуру завершения, то IoCompleteRequest смотрит, было ли установлено на предыдущем шаге поле IRP.PendingReturned. Если да, и всё ещё есть действительный блок стека, взводит флаг SL_PENDING_RETURNED в предыдущем блоке стека (этот блок IoCompleteRequest будет обрабатывать при следующем витке цикла), используя макрос IoMarkIrpPending.

Представим теперь два плохих сценария:

  • драйвер unknown возвращает STATUS_PENDING, но забывает про IoMarkIrpPending;
  • драйвер unknown отмечает IRP как ожидающий завершения, используя IoMarkIrpPending, но забывает вернуть STATUS_PENDING.

Сценарий 1: Если драйвер возвращает из процедуры диспетчеризации код STATUS_PENDING, IoCallDriver передаст этот код нам. Увидев такой код, мы бесконечно ждем, пока наша процедура завершения не освободит событие. По прошествии некоторого времени драйвер unknown инициирует завершение IRP. IoCompleteRequest смотрит в блок стека, принадлежащий драйверу unknown, и, не обнаружив там флага SL_PENDING_RETURNED, обнуляет IRP.PendingReturned. Видя, что в блоке стека установлена процедура завершения (установленная нашим драйвером), IoCompleteRequest вызывает её. Получив управление, наша процедура завершения не сигналит событие и освобождает память, занятую под IRP. В результате событие уже никогда не будет освобождено и поток, ожидающий на нем, никогда не возобновит работу.

Вариацией этого сценария будет ситуация, когда драйвер unknown ставит IRP в очередь, а потом вызывает IoMarkIrpPending (имеется ввиду, что блокировка очереди уже снята). Тогда ещё до того как он доберется до IoMarkIrpPending, IRP может быть извлечен из очереди и завершен.

Сценарий 2: Получив от IoCallDriver код отличный от STATUS_PENDING, наш драйвер считает, что IRP завершен и в зависимости от ошибочно возвращенного кода либо получает неверные данные, либо не получает ничего. Но это не самое страшное. Хуже, если мы переведем IoFreeIrp из процедуры завершения в основную процедуру после IoCallDriver, а мы имеем полное право это сделать. Драйвер unknown ведь не знает деталей реализации вышестоящего драйвера, и ни в коем случае не должен на это полагаться. Посчитав, что IRP завершен, мы вызовем IoFreeIrp. Через некоторое время драйвер unknown пытается извлечь уже не существующий IRP из очереди…

Не сложно догадаться, что для сценария 1 можно применить простое противоядие: вне зависимости от значения поля PendingReturned всегда вызывать KeSetEvent в процедуре завершения. Можно конечно, но тогда во всех случаях, когда IRP завершается немедленно, мы будем зря вызывать KeSetEvent, а она блокирует базу данных диспетчера потоков, ищет потоки, ждущие на событии, и делает их планируемыми, разблокирует базу данных диспетчера потоков. Вобщем, кое-какие накладные расходы будут. Но дело даже не в этом. Мы можем переписать нашу процедуру завершения, но мы не можем переписать код диспетчера в/в, который реализует свою логику работы. Диспетчер в/в вообще не устанавливает процедуру завершения. Он использует другие механизмы, но при принятии решений также опирается на код, возвращенный IoCallDriver и значение поля PendingReturned.

Правило:

Если из процедуры диспетчеризации драйвер возвращает код STATUS_PENDING, то перед этим должен вызвать IoMarkIrpPending. Если в процедуре диспетчеризации драйвер вызывает IoMarkIrpPending, то должен вернуть код STATUS_PENDING. Либо и то и другое, либо ни того, ни другого.

Вернемся к стеку клавиатуры. Мы уже вызвали IoCallDriver и сейчас находимся в процедуре диспетчеризации KeyboardPnP драйвера kbdclass.

Я использую здесь исходный код из 2003 IFS KIT. В 2000 DDK код функции KeyboardPnP отличается: драйвер kbdclass синхронизирует обработку IRP, используя функцию KeyboardSendIrpSynchronously, почти идентичную функции I8xSendIrpSynchronously драйвера i8042ptr (см. ниже). Во-первых, так нам будет проще, а во-вторых, это внесет некоторое разнообразие.

  1.      PIO_STACK_LOCATION    pStack

  2.      pStack = IoGetCurrentIrpStackLocation( pIrp )

  3.      if  pStack->MinorFunction == IRP_MN_QUERY_PNP_DEVICE_STATE  {

  4.          pIrp->IoStatus.Information |= PNP_DEVICE_NOT_DISABLEABLE

  5.          pIrp->IoStatus.Status = STATUS_SUCCESS

  6.          IoCopyCurrentIrpStackLocationToNext( pIrp )

  7.          status = IoCallDriver( NextLowerDeviceObject, pIrp )

Первым делом kbdclass получает указатель на свой блок стека, чтобы посмотреть чего от него хотят.

При обработке IRP_MN_QUERY_PNP_DEVICE_STATE драйвер должен поместить в поле IRP.IoStatus.Information флаг, определяющий состояние устройства. При этом, поскольку поле IRP.IoStatus.Information одно, а драйверов в стеке много, все они используют логические операции для установки или сброса нужных флагов. Драйвер kbdclass добавляет флаг PNP_DEVICE_NOT_DISABLEABLE и помещает в IRP код успеха. Теперь он должен передать его нижестоящему драйверу. При этом дальнейшая судьба этого запроса его не интересует и он не устанавливает процедуру завершения. Уак будет завершен IRP, kbdclass не узнает уже никогда. Несмотря на то, что после вызова IoCallDriver в переменной pIrp всё еще будет хранится число, являвшееся указателем на IRP, обращаться по этому указателю драйвер kbdclass не имеет права, т.к., возможно, этот IRP уже не существует и на схеме 15-7 это будет очень хорошо видно.

Перед вызовом нижестоящего драйвера, драйвер kbdclass (и любой другой) должен заполнить причитающийся ему (нижестоящему драйверу) блок стека. В данном случае, т.к. kbdclass не формирует новый IRP, а пересылает переданный ему свыше, он может просто скопировать свой блок стека в следующий (помните, что это стек, где всё поставлено с ног на голову, т.е. следующим будет блок стека расположенный в памяти ниже). Это можно сделать с помощью макроса IoCopyCurrentIrpStackLocationToNext. В ntddk.inc можно увидеть оптимизированный вариант, а здесь приводится белее доступная для понимания версия.

  1.  IoCopyCurrentIrpStackLocationToNext MACRO pIrp:REQ

  2.      mov esi, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      mov edx, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  4.      sub edx, sizeof IO_STACK_LOCATION

  5.      mov ecx, sizeof IO_STACK_LOCATION

  6.      and (IO_STACK_LOCATION PTR [edx]).Control, 0

  7.      and (IO_STACK_LOCATION PTR [edx]).CompletionRoutine, 0

  8.      and (IO_STACK_LOCATION PTR [edx]).Context, 0

Как видно, макрос копирует текущий блок стека в следующий, но три поля: Control, CompletionRoutine и Context обнуляются. Зачем обнуляются эти поля, мы знаем ниже. Теперь kbdclass вызывает IoCallDriver, передавая в своей переменной NextLowerDeviceObject, указатель на объект «устройство» находящийся непосредственно под ним. Этот указатель kbdclass получает при подключении к стеку. Т.к. мы договорились рассматривать классический состав стека, следующим в стеке оказывается объект «устройство», принадлежащий драйверу i8042ptr и мы оказываемся в его процедуре диспетчеризации I8xPnP.

  1.      PIO_STACK_LOCATION  pStack

  2.      pStack = IoGetCurrentIrpStackLocation( pIrp )

  3.      if  pStack->MinorFunction == IRP_MN_QUERY_PNP_DEVICE_STATE  {

  4.          status = I8xSendIrpSynchronously( TopOfStack, pIrp, FALSE )

  5.          pIrp->IoStatus.Information |= PnpDeviceState

  6.          pIrp->IoStatus.Status = status

  7.          IoCompleteRequest( pIrp, IO_NO_INCREMENT )

i8042ptr также получает указатель на свой блок стека и синхронно перенаправляет IRP следующему (нижестоящему) драйверу acpi, указатель на который хранится в переменной TopOfStack.

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2.      KeSetEvent( pEvent, 0, FALSE )   // Four-F: It’s not good to signal event unconditionaly.

  3.      return STATUS_MORE_PROCESSING_REQUIRED

  4.  I8xSendIrpSynchronously (

  5.      IN PDEVICE_OBJECT pDeviceObject,

  6.      KeInitializeEvent( &event, SynchronizationEvent, FALSE )

  7.      IoCopyCurrentIrpStackLocationToNext( pIrp )

  8.      IoSetCompletionRoutine( pIrp, I8xPnPComplete, &Event, TRUE, TRUE, TRUE )

  9.      status = IoCallDriver( pDeviceObject, pIrp )

  10.      if  status == STATUS_PENDING  {

  11.         KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, NULL )

  12.         status = pIrp->IoStatus.Status

Разбирать функции I8xSendIrpSynchronously и I8xPnPComplete я не буду, т.к. они реализуют ту же логику работы, что и наши QueryPnpDeviceState и IrpComplete. Разобравшись с кодом нашего драйвера, вы без труда поймете, как работают эти две функции.

По возвращении из I8xSendIrpSynchronously, драйвер i8042ptr добавляет в поле Information свою порцию флагов из переменной PnpDeviceState и завершает IRP, вызовом IoCompleteRequest.

Ну, и, наконец, процедура диспетчеризации драйвера acpi будет у нас выглядеть так (на самом деле всё гораздо сложнее):

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2.      pIrp->IoStatus.Information |= PNP_DEVICE_NOT_DISABLEABLE

  3.      pIrp->IoStatus.Status = STATUS_SUCCESS

  4.      IoCompleteRequest( pIrp, IO_NO_INCREMENT )

Теперь рассмотрим случай, когда обработка IRP будет синхронной, т.е. пройдет в контексте одного и того же потока. Все драйверы в стеке завершают IRP немедленно и, соответственно, ни одна из процедур диспетчеризации не возвращает STATUS_PENDING. Будем пользоваться схемой на рис. 15-7. Нарисовав эту схему, я был приятно удивлен тем, насколько хорошо видны на ней некоторые совсем неочевидные вещи.

Рис. 15-7. Этапы обработки IRP.

  1. Наш драйвер QueryPnpDeviceState создает IRP, инициализирует объект «событие», на котором будет ждать завершения IRP, если завершение будет отложено, устанавливает процедуру завершения IrpComplete и посылает IRP драйверу kbdclass.

  2. Драйвер kbdclass перенаправляет IRP нижестоящему драйверу i8042prt, не устанавливая процедуру завершения.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : C00000BB

    3.  IoStatus.Information : 00000000

    4.  CurrentLocation      : <b>04</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>838870E4</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  StackLocation 3 at 838870C0:

    14.  CurrentStackLocation at <b>838870E4</b>:

    15.  MajorFunction     : 1B IRP_MJ_PNP

    16.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    17.  Others            : 00000000 00000000 00000000 00000000

    18.  DeviceObject *    : 81852CA0

    19.  CompletionRout *  : 00000000

    20.  StackLocation 5 at 83887108:

    21.  MajorFunction     : 1B IRP_MJ_PNP

    22.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    23.  Others            : 00000000 00000000 00000000 00000000

    24.  DeviceObject *    : 81852AB0

    25.  CompletionRout *  : ED5E14C0

  3. Драйвер i8042prt инициализирует объект «событие», на котором будет ждать завершения IRP, если завершение будет отложено, устанавливает процедуру завершения I8xPnpComplete и передаёт IRP нижестоящему драйверу acpi.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : C00000BB

    3.  IoStatus.Information : 00000000

    4.  CurrentLocation      : <b>03</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>838870C0</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  CurrentStackLocation at <b>838870C0</b>:

    14.  MajorFunction     : 1B IRP_MJ_PNP

    15.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    16.  Others            : 00000000 00000000 00000000 00000000

    17.  DeviceObject *    : 81852CA0

    18.  CompletionRout *  : ED09043F

    19.  StackLocation 4 at 838870E4:

    20.  MajorFunction     : 1B IRP_MJ_PNP

    21.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    22.  Others            : 00000000 00000000 00000000 00000000

    23.  DeviceObject *    : 81852CA0

    24.  CompletionRout *  : 00000000

    25.  StackLocation 5 at 83887108:

    26.  MajorFunction     : 1B IRP_MJ_PNP

    27.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    28.  Others            : 00000000 00000000 00000000 00000000

    29.  DeviceObject *    : 81852AB0

    30.  CompletionRout *  : ED5E14C0

  4. Драйвер acpi завершает IRP (возможно предварительно разослав его каким-то другим драйверам), вызывая IoCompleteRequest.

    Функция IoCompleteRequest начинает завершение IRP. Смотрит в блок стека принадлежащий драйверу acpi. Не найдя там флага SL_PENDING_RETURNED (драйвер acpi не вызывал макрос IoMarkIrpPending), не устанавливает поле IRP.PendingReturned. Находит указатель на процедуру завершения I8xPnpComplete вышестоящего драйвера i8042prt и вызывает её.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : 00000000         <- STATUS_SUCCESS

    3.  IoStatus.Information : 00000020         <- PNP_DEVICE_NOT_DISABLEABLE

    4.  CurrentLocation      : <b>04</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>838870E4</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  StackLocation 3 at 838870C0:

    14.  MajorFunction     : 1B IRP_MJ_PNP

    15.  MinorFunction     : 00                  <- обнулено ZeroIrpStackLocation

    16.  Control           : 00                  <- обнулено ZeroIrpStackLocation

    17.  Others            : 00000000 00000000 00000000 00000000

    18.  DeviceObject *    : 818A64F0

    19.  CompletionRout *  : ED09043F

    20.  CurrentStackLocation at <b>838870E4</b>:

    21.  MajorFunction     : 1B IRP_MJ_PNP

    22.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    23.  Others            : 00000000 00000000 00000000 00000000

    24.  DeviceObject *    : 81852CA0

    25.  CompletionRout *  : 00000000

    26.  StackLocation 5 at 83887108:

    27.  MajorFunction     : 1B IRP_MJ_PNP

    28.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    29.  Others            : 00000000 00000000 00000000 00000000

    30.  DeviceObject *    : 81852AB0

    31.  CompletionRout *  : ED5E14C0

  5. Процедура завершения I8xPnpComplete совершенно напрасно сигналит событие (драйвер i8042prt не ждет и не будет ждать на этом событии) и возвращает код STATUS_MORE_PROCESSING_REQUIRED.

    Увидев код STATUS_MORE_PROCESSING_REQUIRED, IoCompleteRequest немедленно прекращает работу и возвращает управление в процедуру диспетчеризации драйвера acpi.

  6. Драйвер acpi возвращает код STATUS_SUCCESS, и мы выходим из функции IoCallDriver в драйвере i8042prt.

    Увидев, что возвращенный из IoCallDriver код не STATUS_PENDING, драйвер i8042prt не ждет на событии. Сейчас драйвер i8042prt имеет полное право обращаться к IRP, т.к. устанавливал процедуру завершения, которая прервала обработку IRP. Поскольку драйвер i8042prt прервал завершение IRP, вернув из своей процедуры завершения код STATUS_MORE_PROCESSING_REQUIRED, то должен возобновить этот процесс. Что он и делает вызовом IoCompleteRequest.

    Выше мы выяснили, что завершать IRP два раза нельзя. Здесь же мы видим уже второй вызов IoCompleteRequest. Есть ли тут противоречие? Нет. Завершение IRP — это не просто вызов IoCompleteRequest. Это многоэтапный процесс. На каждом этапе он может быть прерван и возобновлен вновь. Только когда все эти этапы будут пройдены, IRP считается завершенным.

  7. Функция IoCompleteRequest продолжает завершать IRP с того места, где её прервали, т.е. с текущего блока стека, а текущим сейчас является блок стека драйвера i8042prt. В блоке стека драйвера i8042prt нет флага SL_PENDING_RETURNED (драйвер i8042prt тоже не вызывал макрос IoMarkIrpPending). Поэтому IRP.PendingReturned опять обнуляется. IoCompleteRequest не находит указатель на процедуру завершения в блоке стека драйвера i8042prt и переходит к предыдущему и последнему блоку стека драйвера kbdclass. kbdclass тоже не использовал макрос IoMarkIrpPending и IRP.PendingReturned опять обнуляется. В блоке стека драйвера kbdclass имеется указатель на нашу процедуру завершения IrpComplete, которая и вызывается.

    Вспомните, что при передаче IRP нижестоящему драйверу, драйвер kbdclass скопировал свой блок стека в следующий, использую макрос IoCopyCurrentIrpStackLocationToNext. Однако этот макрос не копирует поля связанные с процедурой завершения. Если бы он этого не сделал, то указатель на нашу процедуру завершения (он находится в блоке стека драйвера kbdclass) попал бы в блок стека драйвера i8042prt, и наша процедура завершения была бы вызвана дважды. В стародавние времена, когда ещё не было макроса IoCopyCurrentIrpStackLocationToNext, программисты вручную копировали блоки стека, иногда забывая обнулить поля связанные с процедурой завершения, что приводило к трудно находимым багам.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : 00000000

    3.  IoStatus.Information : 00000020

    4.  CurrentLocation      : <b>06</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>8388712C</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  StackLocation 3 at 838870C0:

    14.  MajorFunction     : 1B IRP_MJ_PNP

    15.  MinorFunction     : 00                 <- обнулено ZeroIrpStackLocation

    16.  Control           : 00                 <- обнулено ZeroIrpStackLocation

    17.  Others            : 00000000 00000000 00000000 00000000

    18.  DeviceObject *    : 818A64F0

    19.  CompletionRout *  : ED09043F

    20.  StackLocation 4 at 838870E4:

    21.  MajorFunction     : 1B IRP_MJ_PNP

    22.  MinorFunction     : 00                 <- обнулено ZeroIrpStackLocation

    23.  Others            : 00000000 00000000 00000000 00000000

    24.  DeviceObject *    : 81852CA0

    25.  CompletionRout *  : 00000000

    26.  StackLocation 5 at 83887108:

    27.  MajorFunction     : 1B IRP_MJ_PNP

    28.  MinorFunction     : 00                 <- обнулено ZeroIrpStackLocation

    29.  Control           : 00                 <- обнулено ZeroIrpStackLocation

    30.  Others            : 00000000 00000000 00000000 00000000

    31.  DeviceObject *    : 81852AB0

    32.  CompletionRout *  : ED5E14C0

    33.  CurrentStackLocation at <b>8388712C</b>:

    34.  <заполнен нулями>                      <- недействительный блок стека

    Наша процедура завершения несколько умнее. Видя, что поле PendingReturned равно нулю, она понимает, что нижестоящий драйвер не возвращал STATUS_PENDING, а значит, процедура диспетчеризации драйвера QueryPnpDeviceState не ждет на событии. Поэтому и сигналить его нет смысла. Мы установили процедуру завершения только для того, чтобы удалить, созданный нами IRP. Можем сделать это прямо сейчас, вызвав IoFreeIrp. Поскольку IRP больше нет, мы должны остановить его завершение, вернув код STATUS_MORE_PROCESSING_REQUIRED.

  8. Увидев код STATUS_MORE_PROCESSING_REQUIRED, IoCompleteRequest немедленно прекращает работу и возвращает управление в процедуру диспетчеризации драйвера i8042prt. Вот здесь очень хорошо видно, почему после вызова IoCompleteRequest нельзя обращаться к IRP. Ведь возможно IRP уже не существует, и узнать это драйвер вызывающий IoCompleteRequest не может. Обратите внимание на то, что функция IoCompleteRequest не возвращает никакого значения.

    Правило:

    После вызова процедуры IoCompleteRequest обращаться к IRP нельзя. Возможно, IRP уже не существует.

  9. Процедура диспетчеризации драйвера i8042prt возвращает код, который вернула вызванная им IoCallDriver, а это, в данном случае, STATUS_SUCCESS и мы выходим из функции IoCallDriver в драйвере kbdclass. И опять здесь хорошо видно, почему после вызова IoCallDriver нельзя обращаться к IRP, если, конечно, не устанавливать процедуру завершения и не прерывать завершение IRP. Ведь IRP то уже не существует. Драйвер kbdclass отказался от установки процедуры завершения, а значит, после вызова IoCallDriver полностью потерял контроль над IRP. Кто и когда завершит IRP драйвер kbdclass не узнает, а значит, не может делать никаких предположений о том, существует ли IRP до сих пор или его уже нет. Драйвер i8042prt смог обратиться к IRP после вызова IoCallDriver только потому, что его процедура завершения прервала процесс завершения IRP, а драйвер kbdclass не может.

    Правило:

    Если у вас нет процедуры завершения или имеющаяся у вас процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то после вызова IoCallDriver обращаться к IRP нельзя. Возможно, IRP уже не существует.

  10. Процедура диспетчеризации драйвера kbdclass возвращает код, который вернула, вызванная им, IoCallDriver, а это, в данном случае, STATUS_SUCCESS и мы выходим из функции IoCallDriver в нашем драйвере QueryPnpDeviceState.

    Видя, что возвращенный из IoCallDriver код не STATUS_PENDING, мы не ждем на событии. Хотя мы и установили процедуру завершения, и она вернула STATUS_MORE_PROCESSING_REQUIRED, но трогать IRP после возвращения из IoCallDriver всё равно не можем. Это исключение из правил, т.к. мы являемся создателем IRP. Надеюсь, здесь это очевидно. Мы же сами удалили IRP в процедуре завершения и прекратили его дальнейшее завершение.

Теперь поставим на место драйвера acpi драйвер unknown и представим, что он откладывает завершение IRP и возвращает из своей процедуры диспетчеризации STATUS_PENDING. Т.е. обработка IRP будет асинхронной.

Т.к. драйвер unknown откладывает завершение IRP, то, используя макрос IoMarkIrpPending, заносит в свой блок стека флаг SL_PENDING_RETURNED, ставит IRP в очередь и возвращает STATUS_PENDING. Мы выходим из функции IoCallDriver в драйвере i8042prt. Увидев код STATUS_PENDING, драйвер i8042prt начинает ждать освобождения события и текущий поток блокируется.

Через некоторое время в результате прерывания или по другой причине, но в контексте какого-то другого потока, драйвер unknown достает IRP из очереди и завершает его вызовом IoCompleteRequest. IoCompleteRequest обнаруживает в блоке стека драйвера unknown флаг SL_PENDING_RETURNED, и поле IRP.PendingReturned принимает ненулевое значение. Обнаружив указатель на процедуру завершения I8xPnpComplete вышестоящего драйвера i8042prt, вызывает её. Процедура завершения I8xPnpComplete сигналит событие и возвращает код STATUS_MORE_PROCESSING_REQUIRED, что заставляет функцию IoCompleteRequest прекратить работу и вернуться туда, откуда она была вызвана.

Ожидающий на событии поток пробуждается. Сейчас драйвер i8042prt имеет полное право обращаться к IRP, т.к. прервал завершение IRP, вернув из своей процедуры завершения код STATUS_MORE_PROCESSING_REQUIRED, и совершенно точно знает, что IRP ещё не завершен. Это он и делает, для того чтобы узнать код, с которым завершился отложенный IRP (см. исходный код функции I8xSendIrpSynchronously). Этот код драйвер извлекает из поля IRP.IoStatus.Status и из своей процедуры диспетчеризации будет возвращать именно его, а не первоначальный STATUS_PENDING. Затем драйвер i8042prt возобновляет завершение IRP, вызовом IoCompleteRequest.

Функция IoCompleteRequest продолжает завершать IRP с того места, где её прервали, т.е. с текущего блока стека, а текущим сейчас является блок стека драйвера i8042prt. В этом блоке стека нет флага SL_PENDING_RETURNED… Точнее говоря, его там быть не должно, но взгляните на исходный код функции I8xPnPComplete из 2000 DDK. Вы увидите там такие строки:

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2. <FONT color=»red»>     if  pIrp->PendingReturned  {

  3.          IoMarkIrpPending( pIrp )     // Four-F: Do not do this if you return

  4.                                       //         STATUS_MORE_PROCESSING_REQUIRED!

  5.      KeSetEvent( pEvent, 0, FALSE )   // Four-F: It’s not good to signal event unconditionaly.

  6.      return STATUS_MORE_PROCESSING_REQUIRED

В 2003 DDK эти строки уже закомментарены.

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2.      // Since this completion routines sole purpose in life is to synchronize

  3.      // Irp, we know that unless something else happens that the IoCallDriver

  4.      // will unwind AFTER the we have complete this Irp.  Therefore we should

  5.      // NOT bubble up the pending bit.

  6.      // if  pIrp->PendingReturned  {

  7.      //     IoMarkIrpPending( pIrp )

  8.      KeSetEvent( pEvent, 0, FALSE )   // Four-F: It’s not good to signal event unconditionaly.

  9.      return STATUS_MORE_PROCESSING_REQUIRED

Две выделенные красным строки должны быть в процедуре завершения, но только если она не возвращает STATUS_MORE_PROCESSING_REQUIRED. Чуть позже увидим почему.

Допустим, мы используем I8xPnPComplete из 2000 DDK и в блоке стека драйвера i8042prt ошибочно присутствует флаг SL_PENDING_RETURNED. Видя это, IoCompleteRequest опять помещает в поле IRP.PendingReturned ненулевое значение. Если вы проанализируете дальнейший ход событий, то увидите, что ненулевое значение в поле IRP.PendingReturned дойдет до нашей процедуры завершения. Увидев не равное нулю поле IRP.PendingReturned, она решит, что нижестоящий драйвер вернул STATUS_PENDING и процедура диспетчеризации QueryPnpDeviceState ждет освобождения события, хотя на самом деле это не так. В данном случае, ничего ужасного не произойдет. Мы просто напрасно просигналим событие и всё. В каком-то другом случае, наверное, возможны более серьёзные последствия, т.к. драйвер будет основывать свои действия на неверных допущениях.

Мы уже несколько раз убеждались в том, что не стоит слепо верить документации DDK. Теперь оказывается, что и исходникам DDK нельзя верить?! Да, к сожалению, это так. Особенно много, скажем так, неоптимальных решений в исходниках 2000 DDK. Тексту этой статьи я тоже, кстати, советую не доверять :smile3: В конце концов, все мы люди, а людям, как известно…

Остальные возможные сценарии проанализируйте сами. Я только хочу ещё раз обратить особое внимание на поле IRP.PendingReturned. Во всех источниках, которые мне приходилось видеть, в том числе и в DDK, предназначение этого поля не совсем верно трактуется. Обычно говорится, что это поле сообщает диспетчеру в/в или вышестоящему драйверу о том, что нижестоящий драйвер отмечал IRP как ожидающий завершения (вызывал IoMarkIrpPending и возвращал из процедуры диспетчеризации STATUS_PENDING). Это верно. Также говорится, что якобы если какой-либо драйвер отмечал IRP как ожидающий завершения, то ненулевое значение этого поля так и сохраняется при завершении IRP до самого верха. А вот это уже не совсем так. Функция IoCompleteRequest (и мы с вами тоже должны будем принять в этом участие чуть ниже) действительно старается сохранить состояние этого поля, но только если она не встретит процедуру завершения. Зачем это нужно? В только что рассмотренном нами сценарии с драйвером unknown вместо acpi, обработка IRP до того как он опустился до драйвера i8042prt, была синхронной (проходила в контексте одного и того же потока). После того, как драйвер unknown вернул из процедуры диспетчеризации STATUS_PENDING, обработка IRP стала асинхронной (процедура завершения драйвера i8042prt вызывается в контексте случайного потока, а процедура диспетчеризации драйвера i8042prt ждет события в контексте первоначального потока). Дождавшись освобождения события, процедура диспетчеризации драйвера i8042prt продолжает обработку IRP в контексте первоначального потока, и обработка IRP опять становится синхронной. Вот тут собака и зарыта. Все драйверы находящиеся выше i8042prt вообще не должны знать, что драйвер unknown откладывал завершение IRP. Это проблема драйвера i8042prt и он сам её решил. Для всех вышестоящих драйверов всё как было синхронным, так и осталось. На участке между драйверами unknown и i8042prt поле IRP.PendingReturned будет содержать ненулевое значение, а на участке выше драйвера i8042prt оно обнулится, т.к. обработка IRP вновь стала синхронной и никто никого не ждет. Надеюсь, что понятно объяснил и нигде не ошибся :smile3:

Ну, хорошо, все процедуры завершения, которые мы видели до сих пор, возвращали STATUS_MORE_PROCESSING_REQUIRED. Но, как мы выяснили выше, это не единственно возможный код возврата. Этот код процедуры завершения возвращают в одном из трех случаев:

  1. Драйвер-создатель IRP вновь хочет увидеть своё чадо, для того чтобы его… скажем мягко, освободить (пример — наш драйвер) или повторно использовать;
  2. Драйвер хочет синхронизировать обработку IRP (пример — драйвер i8042prt);
  3. Т.к. процедура завершения может вызываться на повышенном IRQL, драйвер хочет сделать какую-то дополнительную обработку на PASSIVE_LEVEL в своей процедуре диспетчеризации.

Если же драйверу не нужна такая функциональность, но перехватить IRP на обратном пути всё же требуется (например, для того, чтобы посмотреть считанные с диска данные или код нажатой клавиши, что мы и будем делать в следующей статье) и всю обработку драйвер может сделать в процедуре завершения, даже на уровне DISPATCH_LEVEL, то тогда процедуре завершения не требуется прерывать завершение IRP и можно вернуть STATUS_SUCCESS или ContinueCompletion (что одно и то же).

В этом случае процедура завершения может выглядеть примерно так:

  1.  JustComplete proc uses esi edi ebx pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP, pContext:PVOID

  2.      .if [esi].IoStatus.Status == STATUS_SUCCESS

  3.          mov edi, [esi].AssociatedIrp.SystemBuffer

  4.          ; Что-то делаем с данными

  5.      .if [esi].PendingReturned

Самое важное здесь, в контексте нашего разговора, это вызов макроса IoMarkIrpPending в случае, если поле IRP.PendingReturned не равно нулю. Выше мы разобрались, что IoCompleteRequest как бы «перекладывает» флаг SL_PENDING_RETURNED из текущего блока стека в поле PendingReturned самого IRP и наоборот, если в блоке стека нет процедуры завершения, а поле PendingReturned не равно нулю, то вызывает макрос IoMarkIrpPending. Короче говоря, IoCompleteRequest пытается донести до первой встретившейся ей процедуры завершения, тот факт, что какой-то нижестоящий драйвер отмечал IRP как ожидающий завершения. Когда IoCompleteRequest находит процедуру завершения, то возлагает эту задачу на неё (см. исходный код IoCompleteRequest, а лучше блок-схему).

Представим, что вместо драйвера acpi у нас драйвер unknown и процедура завершения I8xPnPComplete драйвера i8042prt похожа на процедуру JustComplete, т.е. не сигналит событие и возвращает код STATUS_SUCCESS. Соответственно, процедура диспетчеризации драйвера i8042prt никакого события не инициализирует и не ждет, а просто возвращает тот код, который вернет IoCallDriver.

Драйвер unknown вызывает макрос IoMarkIrpPending, ставит IRP в очередь и возвращает STATUS_PENDING. Этот код «поднимается» до нашей процедуры диспетчеризации и мы начинаем ждать.

Некоторое время спустя, в результате прерывания или по другой причине, но в контексте какого-то другого потока, драйвер unknown извлекает IRP из очереди и завершает его вызовом IoCompleteRequest. IoCompleteRequest обнаруживает в блоке стека драйвера unknown флаг SL_PENDING_RETURNED, и поле IRP.PendingReturned принимает ненулевое значение. Обнаружив указатель на процедуру завершения JustComplete вышестоящего драйвера i8042prt, вызывает её (повторяю, мы заменили код на JustComplete). Сделав свои дела, процедура завершения JustComplete видит, что поле IRP.PendingReturned не равно нулю и, вызовом макроса IoMarkIrpPending, кладет в свой блок стека флаг SL_PENDING_RETURNED. Функция IoCompleteRequest делает то же самое в ветке else, но т.к. IoCompleteRequest встретила процедуру завершения, то эта задача перекладывается на неё. Т.к. процедура завершения I8xPnpComplete возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, функция IoCompleteRequest продолжает «подниматься» по блокам стека. Сделав дальнейший анализ, вы увидите, что информация о том, что IRP отмечался как ожидающий завершения в виде ненулевого значения в поле IRP.PendingReturned благополучно доходит до нашей процедуры завершения. Наша процедура завершения понимает, что процедура диспетчеризации QueryPnpDeviceState ждет на событии, сигналит его и всё заканчивается благополучно.

А если вы также проанализируете, что будет, если процедура завершения JustComplete забудет должным образом воспользоваться макросом IoMarkIrpPending, то придете к ещё одному правилу.

Правило:

Если процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то должна использовать (в любо месте) макрос IoMarkIrpPending таким образом.

if pIrp->PendingReturned {

IoMarkIrpPending( pIrp )

}

Ну и последнее. Т.к. у процедуры завершения нет другой возможности узнать, с каким кодом нижестоящий драйвер завершает IRP, кроме как обратиться к полю IRP.IoStatus.Status, мы запишем последнее правило.

Правило:

Перед вызовом IoCompleteRequest в процедуре диспетчеризации драйвер должен поместить в поле IRP.IoStatus.Status код с которым он завершает IRP и вернуть из процедуры диспетчеризации тот же самый код.

Начиная писать эту «бесконечную» статью я планировал ещё рассказать о том, какую логику использует диспетчер в/в при обработке IRP, т.к. чаще всего именно он является создателем IRP, но чувствую, что силы покидают меня. Если этот вопрос вас интересует, то рекомендую почитать статью «How Windows NT Handles I/O Completion» в IFS KIT или «The NT Insider» ( http://www.osronline.com/ ). К сожалению, исходного кода диспетчера в/в вы там не найдете, но общее представление получите.

Что вы должны делать и чего вы делать не должны

Подведем итог.

Правило 1:

Перед вызовом IoCompleteRequest в процедуре диспетчеризации драйвер должен поместить в поле IRP.IoStatus.Status код с которым он завершает IRP и вернуть из процедуры диспетчеризации тот же самый код.

Правило 2:

После вызова процедуры IoCompleteRequest обращаться к IRP нельзя. Возможно, IRP уже не существует.

Правило 3:

Завершать IRP с кодом STATUS_PENDING нельзя.

Правило 4:

Если из процедуры диспетчеризации драйвер возвращает код STATUS_PENDING, то перед этим должен вызвать IoMarkIrpPending. Если в процедуре диспетчеризации драйвер вызывает IoMarkIrpPending, то должен вернуть код STATUS_PENDING. Либо и то и другое, либо ни того, ни другого.

Правило 5:

Если у вас нет процедуры завершения или имеющаяся у вас процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то после вызова IoCallDriver обращаться к IRP нельзя. Возможно, IRP уже не существует.

Правило 6:

Если процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то должна использовать (в любо месте) макрос IoMarkIrpPending таким образом.

if pIrp->PendingReturned {

IoMarkIrpPending( pIrp )

}

Некоторые из этих правил, наверное, можно нарушить, если очень хорошо представлять себе, все детали механизма обработки IRP. Если такого представления нет, то лучше следовать им неукоснительно.

В следующий раз мы попробуем применить кое-какие полученные сегодня знания на практике.

Исходный код драйвера в архиве.

© Four-F

Драйверы режима ядра: Часть 15 : Жизненный цикл IRP — Архив WASM.RU

В этой и следующей статье мы рассмотрим принципы фильтрации (перехвата) пакетов запроса в/в (IRP). Для чего нужно перехватывать чужие IRP? Применений этому много. Например, захотелось нам посмотреть, к каким файлам обращается та или иная программа. Что мы сделаем в первую очередь? Правильно — запустим FileMon ( sysinternals.com ), который установит драйвер-фильтр на файловую систему. А поскольку обращение к файлам — это фактически формирование соответствующих IRP (быстрый в/в, при котором формирования IRP не происходит, не в счет) и посылка их драйверам файловой системы, то прежде чем добраться до адресата, IRP попадет в фильтр и FileMon зафиксирует это обращение, после чего перешлет его адресату. При этом воздействовать на перехватываемые пакеты FileMon не может. Его задача — только регистрировать факт посылки IRP. Другой пример. Допустим, вам понадобилось скрыть, например, от ваших ближайших родственников или коллег по работе, наличие некоторых файлов фривольного содержания. Недолго думая, вы наберете в google что-то вроде «Hide Files And Folders» и тут же найдете кучку программ, позволяющих скрывать отдельные файлы и каталоги. Это возможно благодаря тому же самому механизму фильтрации IRP. Получая доступ к пакету, драйвер-фильтр имеет возможность модифицировать передаваемые в нём данные, как на пути к файловой системе, так и обратно. Разумеется, фильтровать можно не только IRP передающиеся в файловую систему, но и любые другие. Фильтрация IRP — это общий и универсальный механизм. Антивирусные мониторы, файерволы, на лету компрессоры/декопрессоры крипторы/декрипторы и т.д. и т.п. используют механизм фильтрации IRP. Фильтр, который мы напишем в следующий раз, будет отслеживать IRP, связанные с клавиатурным вводом.

Фильтрация пакетов запроса в/в — достаточно сложная тема. Поэтому, прежде чем перейти к практической реализации потребуется хотя бы минимальная теоретическая подготовка. Как минимум, надо четко представлять себе жизненный цикл IRP от «рождения до смерти». В этой статье мы, в основном, и будем заниматься исследованием этого вопроса. Поскольку драйверы, обслуживающие клавиатуру, в полной мере поддерживают механизм Plug And Play, то придется, в минимальном объеме, осветить и этот вопрос. При этом наш фильтр не будет драйвером Plug And Play. Это будет по-прежнему унаследованный (legacy), в терминологии Microsoft, драйвер, но подключать мы его будем к Plug And Play драйверу.

Ввиду сложности темы, мне вряд ли удастся осветить этот вопрос со всех сторон. Много дополнительной информации можно получить из раздела DDK «Handling IRPs». В Installable File System Kit (IFS KIT), являющийся надмножеством обычного DDK, имеется также раздел «OSR Technical Articles» куда вошли статьи подготовленные командой Open System Resources ( http://www.osr.com/ ). Если в вашем распоряжении только обычный DDK, то большую часть этих статей, если не все, а также много дополнительной информации можно найти в онлайновом журнале «The NT Insider» ( http://www.osronline.com/ ).

Общая классификация драйверов WDM

Все Plug And Play драйверы должны соответствовать модели драйверов Windows (Windows Driver Model, WDM). В соответствии с этой моделью драйверы подразделяются на три типа:

  • Драйверы шин (Bus Drivers). Управляют логическими или физическими шинами. Отвечают за распознавание устройств, подключение их к управляемой ими шине и оповещение о них диспетчера PnP.
  • Функциональные драйверы (Function Drivers). Управляют конкретным типом устройств. Экспортируют рабочий интерфейс устройства операционной системе.
  • Драйверы фильтров (Filter Drivers). Занимая более высокий логический уровень, чем функциональные драйверы, добавляют функциональность или изменяют поведение устройства либо другого драйвера. Этот тип драйверов не обязателен для нормальной работы устройства.

    Драйверы фильтров, в свою очередь, подразделяются на:

    • Драйверы фильтров шин (Bus Filter Drivers).
    • Низкоуровневые драйверы фильтров (Lower-Level Filter Drivers).
    • Высокоуровневые драйверы фильтров (Upper-Level Filter Drivers).

Как вы знаете, каждый драйвер должен создать, как минимум, один объект «устройство», которым он будет управлять. Объекты «устройство» WDM также делит на типы:

  • Объект «физическое устройство» (Physical Device Object, PDO) — Создается драйвером шины по заданию диспетчера PnP, когда драйвер шины, перечисляя устройства на своей шине, сообщает о наличии какого-либо устройства. PDO представляет физический интерфейс устройства.
  • Объект «функциональное устройство» (Functional Device Object, FDO) — Создается функциональным драйвером, который загружается диспетчером PnP для управления обнаруженным устройством. FDO представляет логический интерфейс устройства.
  • Необязательная группа объектов «устройство-фильтр» (Filter Device Object, FiDO). Одна группа таких объектов размещается между PDO и FDO (эти объекты создаются драйверами фильтров шин), вторая — между первой группой FiDO и FDO (эти объекты создаются низкоуровневыми драйверами фильтров), а третья — над FDO (эти объекты создаются высокоуровневыми драйверами фильтров).

Дерево устройств

Имея вышеозначенную классификацию, начнем с того, что определимся, каким образом система, точнее говоря, диспетчер PnP (PnP Manager) — компонент операционной системы, предназначенный для автоматического распознавания установленных устройств, узнает, какие драйверы необходимы для того или иного устройства. Процесс распознавания включает в себя перечисление устройств при загрузке и обнаружение их добавления или удаления во время работы системы.

Во время загрузки системы диспетчер PnP начинает перечисление устройств с виртуальной шины под именем Root. В качестве виртуального драйвера, обслуживающего эту шину, выступает сама система. Логически, всё устройства (физические и виртуальные) подключены к этой шине. Виртуальный драйвер корневой шины (и драйверы других шин тоже) извлекает необходимую информацию из реестра. В реестр сведения об оборудовании заносятся ещё на этапе установки операционной системы. Программа установки обнаруживает установленные устройства и, используя информационные файлы (INF Files), заполняет соответствующие разделы реестра. Перечисляя устройства на корневой шине, её виртуальный драйвер обнаруживает другие шины (физические и виртуальные), например, физическую шину PCI. На основе данных реестра диспетчер PnP определяет, установлен ли в системе драйвер, способный управлять обнаруженным устройством. Если такой драйвер установлен, диспетчер PnP указывает диспетчеру ввода-вывода (I/O Manager) загрузить его. Если подходящий драйвер не установлен, диспетчер PnP пытается его установить. При этом если не обнаружится соответствующего информационного файла или других необходимых файлов, диспетчер PnP взаимодействует с пользователем, который должен указать месторасположение необходимых компонентов. Будучи загруженным, драйвер, обслуживающий обнаруженную шину, перечисляет подключенные к ней устройства. При этом он может обнаружить другие дополнительные шины. Если для работы устройства, обнаруженного на шине, необходим драйвер, он загружается. Такой рекурсивный процесс — перечисление устройств, загрузка драйвера, дальнейшее перечисление — продолжается до тех пор, пока не будут обнаружены и сконфигурированы все устройства в системе. Диспетчер PnP способен обнаруживать добавление/удаление нового устройства и во время работы системы. В результате перечисления образуется так называемое дерево устройств (Device Tree), отражающее иерархические взаимосвязи между всеми установленными в системе устройствами.

Дерево устройств можно просмотреть с помощью диспетчера устройств (Device Manager). Как выглядит дерево устройств на моём компьютере (в меню «Вид» я выбрал «Устройства по подключению» и отметил «Показать скрытые устройства».) показано на Рис. 15.1.

Рис. 15-1. Дерево устройств.

На рисунке вы можете обнаружить некоторые, созданные нами ранее виртуальные устройства, например, ProcessMon (Process creation/destruction monitor), подключенные (также виртуально) к корневой шине. В Windows 2000 диспетчер устройств показывает все установленные ранее виртуальные устройства, а в Windows XP (и в Windows 2003 Server, наверное, тоже) только активные в данный момент. Информация о виртуальных устройствах извлекается диспетчером устройств из разделов реестра HKEY_LOCAL_MACHINESYSTEMCurrentControlSetEnumRootLEGACY_XXX.

Узлы дерева устройств называются узлами устройств (device nodes или devnodes). Каждый узел обслуживается одним или несколькими драйверами. Каким образом система узнает, какие драйверы, какой узел обслуживают?

Все устройства, обнаруженные в процессе установки системы (а также установленные позже), регистрируются в подразделах реестра HKEY_LOCAL_MACHINESYSTEMCurrentControlSetEnum<enumerator><deviceID><instanceID>. Где enumerator — драйвер шины, перечисляющий устройства на шине, deviceID — уникальный идентификатор устройств данного типа, instanceID — уникальный идентификатор экземпляра устройства данного типа (по нему можно различать несколько одинаковых устройств).

В процессе перечисления драйвер шины сообщает диспетчеру PnP идентификаторы обнаруженных устройств: deviceID и instanceID. Используя эту информацию, диспетчер PnP находит в реестре драйверы нужные для узла данного устройства.

Пример подраздела Enum для клавиатуры показан на рис 15-2.

Рис. 15-2. Подраздел реестра ветви Enum для клавиатуры.

Как видно из рисунка, перечислителем является ACPI, идентификатор устройства — PNP0303, а идентификатор экземпляра устройства — 3&13c0b0c5&0. Если заглянуть в %SystemRoot%infkeyboard.inf, то можно обнаружить, что информация в реестр попадает именно из этого информационного файла. К вашей машине, разумеется, может быть подключена клавиатура другого типа.

Функциональный драйвер задается параметром Service. В данном случае это i8042prt. Параметр ClassGUID (Globally Unique Identifier of Class) определяет подраздел класса устройства HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlClass. Этот подраздел содержит сведения о драйвере класса устройства. Драйвер класса определяет общую функциональность для всех устройств данного типа. Он ничего не знает о том, как управлять конкретным устройством, но, используя стандартизованные сервисы, взаимодействует с функциональным драйвером, который, в свою очередь, знает, как управляет конкретным типом устройств. В данном случае драйвером класса является kbdclass. Он исполняет роль своего рода буфера между функциональным драйвером i8042prt и подсистемой Win32 (подробнее в следующей статье). Пример подраздела Class для клавиатуры показан на рис 15-3.

Рис. 15-3. Подраздел реестра ветви Class для клавиатуры.

Содержимое этих двух разделов дает диспетчеру PnP всю информацию необходимую для загрузки драйверов для узла данного устройства. Имена драйверов указывают на подразделы реестра HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServices<drivername>.

Загрузка драйверов для узла устройства происходит в следующем порядке:

  • Низкоуровневые драйверы фильтров, указанные в параметрах LowerFilters ветвей реестра Enum и Class.
  • Функциональный драйвер, заданный в параметре Service ветви реестра Enum.
  • Высокоуровневые драйверы фильтров, указанные в параметрах UpperFilters ветвей реестра Enum и Class.

Стек объектов «устройство»

Всё, вышесказанное не имеет прямого отношения к материалу статьи. Используемый в ней драйвер не является PnP-драйвером, а по-прежнему относится к унаследованным драйверам (legacy drivers). Общее понимание механизма перечисления и знание того, что представляет собой дерево устройств необходимо для ввода следующего, уже непосредственно важного для нас, понятия.

Загружая каждый PnP драйвер, диспетчер PnP вызывает стандартную процедуру драйвера AddDevice. В параметре PhysicalDeviceObject передается указатель на объект «физическое устройство», созданный драйвером шины. Загруженный драйвер, в свою очередь, создает свой объект «устройство» и подключает его к объекту «физическое устройство», вызовом функции IoAttachDeviceToDeviceStack. В эту функцию он передает два указателя: переданный ему диспетчером Pnp указатель на объект «физическое устройство» и указатель на созданный им объект «устройство». При этом новый объект всегда подключается к самому верхнему объекту в этой цепочке, вне зависимости от того, имеется ли над PDO другие объекты или нет. Указатель на объект «физическое устройство», при подключении нового объекта, используется как указатель на цепочку объектов, к которой происходит подключение, а не указатель на конкретный объект «устройство». Функция IoAttachDeviceToDeviceStack сама находит самый верхний объект.

Получившаяся конструкция состоит, как минимум, из двух объектов: объект «физическое устройство», созданный драйвером шины, и объект «функциональное устройство», созданный функциональным драйвером, и называется стеком объектов «устройство» (device stack) или просто стеком. Т.о. каждый узел в дереве устройств представлен своим стеком.

Учитывая всё вышесказанное, и имея содержимое разделов реестра Enum и Class, мы можем предсказать, из каких объектов будет состоять стек для узла устройства «клавиатура» (объекты перечисляются снизу вверх):

  • объект «физическое устройство», созданный драйвером шины ACPI.
  • объект «функциональное устройство», созданный функциональным драйвером i8042prt.
  • объект «устройство-фильтр», созданный высокоуровневым драйвером фильтра nmfilter (NTICE Support File).
  • объект «устройство-фильтр», созданный высокоуровневым драйвером фильтра kbdclass.

Оба объекта «устройство-фильтр» созданы высокоуровневыми драйверами фильтров, а драйверов фильтров шины и низкоуровневых драйверов фильтров в данном случае нет.

Просмотреть стеки устройств можно с помощью программы Devide Tree ( osr.com или osronline.com ). Но я избегаю пользоваться этой утилитой, т.к. её работа на трех моих машинах с разными версиями системы неизбежно приводит к появлению «синего экрана смерти» (по крайней мере, в режиме PnP). Удивительно, что эта утилита входит в DDK. Мы воспользуемся более надежной командой !devstack отладчика Kernel Debugger.

Рис. 15-4. Стеки объектов «устройство» для клавиатуры.

На этой машине активна система Terminal Server и у клавиатуры имеется не один, а два стека. Как видите, наши предположения о составе устройств подтвердились. На вашей машине его состав, естественно, может отличаться. Далее мы будем рассматривать классический состав стека для клавиатуры, а именно: Kbdclass сверху, i8042prt посередине, ACPI внизу.

В общем случае стек объектов «устройство» может выглядеть так (см. классификацию драйверов и объектов в WDM выше):

Рис. 15-5. Стек объектов «устройство» для узла устройства (общая схема).

Поскольку каждым объектом «устройство» в стеке управляет драйвер, то очень часто наряду с понятием «стек устройств» употребляют «стек драйверов». Это не совсем верно, но о чём идет речь, надеюсь, понятно. Далее по ходу статьи я тоже буду иногда говорить «стек устройств», и иногда «стек драйверов».

IRP формируется диспетчером в/в или драйвером не принадлежащим стеку и направляется на вершину стека. Если для обработки запроса драйверу требуется помощь нижестоящего драйвера, он перенаправляет IRP ниже по стеку и т.д. IRP всегда идет по стеку сверху вниз. Решение об окончании обработки IRP может быть принято на любом уровне. Мало того, любой драйвер в стеке может сформировать дополнительные IRP (например, разбить запрос чтения из файла на несколько запросов) и разслать его необходимым драйверам. Любой драйвер может отклонить запрос или может модифицировать передаваемые в нем данные. В общем случае, если драйвер получил IRP, то может делать с ним всё что угодно.

Язык с за три минуты

Мне придется использовать исходные коды некоторых системных функций, т.к. по-настоящему разобраться с обработкой IRP без анализа исходного кода, по-моему, невозможно. Эти фрагменты, конечно, не будут истинным кодом операционной системы и будут урезаны, порой весьма значительно. Также опущена вся обработка ошибок: проверки указателей, входных данных и возвращаемых функциями значений, убраны обработчики SEH. Оставлена только самая суть. Для упрощения анализа кода я буду использовать c-подобный псевдоязык (почти чистый с). Вполне допускаю, что вы можете и не знать этого языка, т.к. мы всё же занимаемся разработкой драйверов на ассемблере. Поэтому тезисно приведу базовые конструкции, без которых не обойтись.

На ассемблере место под инициализированную переменную отводится так:

В языке с глобальные и локальные инициализированные переменные определяются так:

Если надо передать адрес переменной в функцию (используя макрос invoke) мы делаем это так:

Программист на с делает это так:

Обратная операция — запись в переменную значения по указателю на переменную — в ассемблере это выглядит так (pwd — указатель на переменную размером в двойное слово):

В с это несколько проще:

Если у нас есть структура FILE_OBJECT, то мы можем записать в её поле DeviceObject указатель на объект «устройство» таким образом:

  1.  pDeviceObject  PDEVICE_OBJECT ?

  2.  mov FileObject.DeviceObject, eax

Программист на с делает это так:

  1.  PDEVICE_OBJECT  pDeviceObject;

  2.  FileObject.DeviceObject = pDeviceObject;

Если же, вместо структуры, в нашем распоряжении указатель на неё, то вышеозначенную операцию нам придется делать примерно так:

  1.  pFileObject PFILE_OBJECT ?

  2.  pDeviceObject  PDEVICE_OBJECT ?

  3.  mov (FILE_OBJECT PTR [ecx]).DeviceObject, eax

Программисту на с, как всегда, немного проще:

  1.  PFILE_OBJECT    pFileObject;

  2.  PDEVICE_OBJECT  pDeviceObject;

  3.  pFileObject->DeviceObject = pDeviceObject;

Если с-программисту надо увеличить значение переменной на единицу, то он может сделать это, как минимум тремя способами, самый очевидный из которых следующий:

Для уменьшения числа IRP циркулирующих в системе можно сделать это и так:

Такой же трюк можно проделать и с другими тремя математическими операциями. Логические операции тоже можно записывать в такой форме. Например:

Эквивилентно

Но, скажу вам по секрету, есть ещё один способ, которым обычно пользуются только гуру или самые ленивые с-программисты для приращения переменных на единицу:

Такой же трюк можно проделывать и с операцией вычитания.

В дальнейшие подробности вдаваться не будем, этого минимума должно хватить. Также имейте в виду, что в DDK есть полный исходный код драйверов kbdclass и i8042prt. Правда, в разных DDK он немного отличается. Соответственно, отличаются и эти драйверы в разных версиях системы.

Мы уже много раз получали IRP, но ещё ни разу не создавали его сами. Поскольку мы собираемся рассмотреть весь жизненный цикл пакета запроса в/в, то без его создания нам никак не обойтись. С этого и начнем.

Допустим, у нас есть имя объекта «устройство», скажем, DeviceKeyboardClass0. Судя по названию, этот объект имеет какое-то отношение к обслуживанию физического устройства «клавиатура». Для чего этот объект нужен и какова его роль, мы подробнее поговорим в следующей статье. Пока нас интересует только одно: у нас есть имя устройства и мы хотим послать ему какой-нибудь IRP. Это можно сделать вызовом функции IoCallDriver, прототип которой выглядит так:

  1.      IN PDEVICE_OBJECT  DeviceObject,

Несмотря на название функции, первым аргументом является указатель на объект «устройство», а не «драйвер», которому адресован IRP. Обрабатывать же IRP будет, естественно, драйвер, это устройство создавший. Второй параметр — указатель на сам пакет запроса в/в.

  1.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  2.  ;                                  I N C L U D E   F I L E S                                        

  3.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  4.  include masm32includew2kntstatus.inc

  5.  include masm32includew2kntddk.inc

  6.  include masm32includew2kntoskrnl.inc

  7.  includelib masm32libw2kntoskrnl.lib

  8.  include masm32MacrosStrings.mac

  9.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  10.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  11.  CCOUNTED_UNICODE_STRING «DeviceKeyboardClass0», g_usTargetDeviceName, 4

  12.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  13.  ;                              D I S C A R D A B L E   C O D E                                      

  14.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  15.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  16.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  17.  IrpComplete proc uses esi edi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP, pContext:PVOID

  18.      assume edi:ptr IO_STATUS_BLOCK

  19.      mov eax, [esi].IoStatus.Status

  20.      mov eax, [esi].IoStatus.Information

  21.      mov [edi].Information, eax

  22.      .if [esi].PendingReturned

  23.          invoke KeSetEvent, pContext, 0, FALSE

  24.      mov eax, STATUS_MORE_PROCESSING_REQUIRED

  25.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  26.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  27.  QueryPnpDeviceState proc uses esi edi ebx pDeviceObject:PDEVICE_OBJECT

  28.  local iosb:IO_STATUS_BLOCK

  29.      mov status, STATUS_NOT_SUPPORTED

  30.      assume esi:ptr DEVICE_OBJECT

  31.      .if ( esi != NULL  &&  [esi]._Type == IO_TYPE_DEVICE )

  32.          movzx eax, [esi].StackSize

  33.          invoke IoAllocateIrp, eax, FALSE

  34.              mov [edi].IoStatus.Status, STATUS_NOT_SUPPORTED

  35.              and [edi].IoStatus.Information, 0

  36.              mov iosb.Status, STATUS_NOT_SUPPORTED

  37.              IoGetNextIrpStackLocation edi

  38.              assume ebx:ptr IO_STACK_LOCATION

  39.              mov [ebx].MajorFunction, IRP_MJ_PNP

  40.              mov [ebx].MinorFunction, IRP_MN_QUERY_PNP_DEVICE_STATE

  41.              invoke KeInitializeEvent, addr keEvent, NotificationEvent, FALSE

  42.              IoSetCompletionRoutine edi, IrpComplete, addr keEvent, TRUE, TRUE, TRUE

  43.              invoke IoCallDriver, esi, edi

  44.              .if eax == STATUS_PENDING

  45.                  invoke DbgPrint, $CTA0(«QueryPnpDeviceState: Request pended. Waiting…n»)

  46.                  invoke KeWaitForSingleObject, addr keEvent, Executive, KernelMode, FALSE, NULL

  47.              .if status == STATUS_SUCCESS

  48.                  invoke DbgPrint, $CTA0(«QueryPnpDeviceState: Device State: %08Xn»), iosb.Information

  49.              mov status, STATUS_INSUFFICIENT_RESOURCES

  50.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  51.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  52.  DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

  53.  local pTargetDeviceObject:PDEVICE_OBJECT

  54.  local pTargetFileObject:PFILE_OBJECT

  55.      invoke IoGetDeviceObjectPointer, addr g_usTargetDeviceName, FILE_READ_DATA,

  56.                                       addr pTargetFileObject, addr pTargetDeviceObject

  57.      .if eax == STATUS_SUCCESS

  58.          invoke QueryPnpDeviceState, pTargetDeviceObject

  59.          invoke ObDereferenceObject, pTargetFileObject

  60.      mov eax, STATUS_DEVICE_CONFIGURATION_ERROR

  61.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  62.  ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

  63.  set drv=QueryPnpDeviceState

  64.  masm32binml /nologo /c /coff %drv%.bat

  65.  masm32binlink /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj

Получить указатель на нужное нам устройство по его имени мы можем с помощью IoGetDeviceObjectPointer. В случае успеха, эта функция вернёт даже два указателя: один — собственно указатель на нужное нам устройство в переменной pTargetDeviceObject, а второй — указатель на объект «файл» ассоциированный с этим устройством в переменной pTargetFileObject. Откуда взялся объект «файл»? Заглянем внутрь функции IoGetDeviceObjectPointer, а также двух других, которые она вызывает.

  1.      IN PDEVICE_OBJECT pDeviceObject

  2.      while pDeviceObject->AttachedDevice

  3.          pDeviceObject = pDeviceObject->AttachedDevice

  4.    IoGetRelatedDeviceObject(

  5.      IN PFILE_OBJECT pFileObject

  6.      PDEVICE_OBJECT pDeviceObject

  7.      pDeviceObject = pFileObject->Vpb->DeviceObject

  8.      pDeviceObject = pFileObject->DeviceObject->Vpb->DeviceObject

  9.      pDeviceObject = pFileObject->DeviceObject

  10.      if pDeviceObject->AttachedDevice != NULL

  11.          pDeviceObject = IoGetAttachedDevice( pDeviceObject )

  12.    IoGetDeviceObjectPointer(

  13.      IN PUNICODE_STRING  pusObjectName,

  14.      IN ACCESS_MASK      DesiredAccess,

  15.      OUT PFILE_OBJECT    *out_pFileObject,

  16.      OUT PDEVICE_OBJECT  *out_pDeviceObject

  17.      InitializeObjectAttributes( &oa, pusObjectName, … )

  18.      ZwOpenFile( &hFile, DesiredAccess, &oa, … )

  19.      ObReferenceObjectByHandle( hFile, 0, IoFileObjectType, KernelMode, &pFileObject, NULL )

  20.      *out_pFileObject   = pFileObject

  21.      *out_pDeviceObject = IoGetRelatedDeviceObject( pFileObject )

Первым делом, функция IoGetDeviceObjectPointer получает описатель объекта «файл» (представлен структурой FILE_OBJECT).

Вспомните, как в программе управления драйвером мы получаем описатель для взаимодействия с его устройством. Мы вызываем функцию CreateFile, которая создает объект «файл», представляющий не собственно файл на диске, а виртуальное устройство (структура DEVICE_OBJECT), созданное драйвером. Т.е. на самом деле, описатель файла используется для ввода-вывода в устройство. Такая схема нужна, во-первых, для разграничения прав доступа, т.к. в структуре DEVICE_OBJECT нет, например, полей WriteAccess и SharedRead, а в FILE_OBJECT такие поля есть, во-вторых, в объекте «файл» можно хранить некоторые другие атрибуты операции ввода-вывода. Адрес истинного получателя пакета запроса в/в, в нашем случае, находится в поле FILE_OBJECT.DeviceObject. Итак, вызов ZwOpenFile, так же как и CreateFile, приводит к созданию объекта «файл», а значит формированию IRP типа IRP_MJ_CREATE и посылке его целевому устройству (в нашем случае устройству DeviceKeyboardClass0). Этот пакет, как вы понимаете, попадает в драйвер обслуживающий это устройство (устройство DeviceKeyboardClass0 обслуживает драйвер kbdclass). Т.е. решение об удовлетворении запроса — вызове IoCompleteRequest со статусом STATUS_SUCCESS — принимает обслуживающий драйвер.

Вот фрагмент функции KeyboardClassCreate драйвера kbdclass:

  1.     PIO_STACK_LOCATION   pStack;

  2.     pStack = IoGetCurrentIrpStackLocation( pIrp )

  3.     if  pIrp->RequestorMode == UserMode

  4.         pStack->Parameters.Create.SecurityContext->DesiredAccess & FILE_READ_DATA  {

  5.         status = STATUS_ACCESS_DENIED

  6.         goto KeyboardClassCreateEnd

Как видите, kbdclass отклоняет попытку режима пользователя получить доступ к его устройствам для чтения.

Кстати, раз уж мы так подробно собрались во всем разбираться, посмотрим на внутренности макроса IoGetCurrentIrpStackLocation, который мы сами уже много раз использовали (полная версия в ntddk.inc).

  1.  IoGetCurrentIrpStackLocation MACRO pIrp:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

Марос IoGetCurrentIrpStackLocation просто извлекает указатель на текущий блок стека из поля CurrentStackLocation.

Получив описатель объекта «файл», функция IoGetDeviceObjectPointer дополнительно увеличивает счетчик ссылок в объекте «файл», вызовом ObReferenceObjectByHandle. Затем IoGetDeviceObjectPointer пытается получить указатель на целевое устройство, сопоставленное с объектом «файл», вызывая IoGetRelatedDeviceObject. В зависимости от принадлежности объекта «файл» тому или иному типу устройств, IoGetRelatedDeviceObject может извлечь необходимый указатель из разных мест (в нашем случае из поля pFileObject->DeviceObject). Далее, обратите на это особое внимание, если к целевому устройству прикреплено ещё одно устройство (об этом говорит ненулевое значение в поле pDeviceObject->AttachedDevice), функция IoGetAttachedDevice «поднимается» по стеку устройств до самого верха и возвращает указатель на устройство, находящееся на вершине стека. Если же прикрепленных устройств нет, то возвращается указатель на само целевое устройство, т.е. то, имя которого было передано в IoGetDeviceObjectPointer. Запомните: Функция IoGetAttachedDevice всегда возвращает указатель на объект «устройство», находящийся на вершине стека.

После получения указателя IoGetDeviceObjectPointer закрывает описатель объекта «файл» и в этот момент счетчик описателей становится равным нулю, что приводит к формированию и посылке драйверу kbdclass IRP типа IRP_MJ_CLEANUP. Т.о. функция IoGetDeviceObjectPointer вернет указатели на два объекта: «файл» и «устройство». Причем в объекте «устройство» значение счетчиков указателей и описателей не меняется, а в объекте «файл» равно 1 и 0, соответственно. Единичное значение счетчика указателей достигается благодаря дополнительному вызову ObReferenceObjectByHandle. До тех пор, пока существует объект «файл», объект «устройство», с которым он связан, не будет удален и соответственно драйвер, управляющий устройством, также не может быть выгружен, т.к. в управляемом им объекте «устройство», будет установлен соответствующий флаг и при попытке выгрузить такой драйвер он отмечается как ожидающий выгрузки, а процедура DriverUnload просто не будет вызвана. Только после того, как будут удалены все управляемые драйвером устройства, драйвер сможет отработать DriverUnload.

Т.о. в случае с IoGetDeviceObjectPointer схема точно такая же, какой пользуется режим пользователя, получая описатель объекта «файл» и таким образом блокируя связанный с ним объект. При этом сам объект «файл» относится к любому источнику или приемнику ввода-вывода (собственно файлу или каталогу, именованному каналу, почтовому ящику и др.), который рассматривается как файл. При таком механизме все считываемые или записываемые данные представляются простыми потоками байтов, направляемыми в виртуальные файлы. По окончании работы, программа режима пользователя закрывает описатель файла, а мы должны будем удалить ссылку, вызовом ObDereferenceObject. При этом счетчик указателей в объекте «файл» обнулится, и это приведет к формированию и посылке драйверу kbdclass IRP типа IRP_MJ_CLOSE. Только после этого объект «файл» будет удален.

Вернемся к исходному коду нашего драйвера.

  1.          invoke QueryPnpDeviceState, pTargetDeviceObject

Теперь у нас есть адресат для посылки IRP. Осталось только сформировать сам пакет.

IRP состоит из тела или заголовка (собственно структура IRP) и одного или нескольких блоков стека (stack locations). Тело IRP хранит общую информацию о запросе ввода-вывода: указатели на буферы, данные о состоянии и др. Блоки стека содержат информацию специфичную для конкретного этапа обработки IRP. Передавая IRP на обработку драйверу, диспетчер в/в (или драйвер самостоятельно создающий IRP, как мы в этом примере) заполняет верхний блок стека. Если драйвер, получивший IRP, решает отправить его на дальнейшую обработку нижестоящему драйверу, он заполняет следующий блок стека (т.к. это стек, то в памяти следующий блок стека находится по меньшему адресу — подробнее об этом чуть позже) и передает IRP ниже и т.д. Т.о. блоки стека — по одному на каждый вызываемый драйвер — хранят информацию, необходимую каждому драйверу для обработки своей части запроса.

  1.      assume esi:ptr DEVICE_OBJECT

  2.      .if ( esi != NULL  &&  [esi]._Type == IO_TYPE_DEVICE )

  3.          movzx eax, [esi].StackSize

  4.          invoke IoAllocateIrp, eax, FALSE

Создать IRP можно одной из четырех функций: IoBuildSynchronousFsdRequest, IoBuildDeviceIoControlRequest, IoBuildAsynchronousFsdRequest и IoAllocateIrp. Если быть совсем точным, то можно сделать IRP вообще вручную, выделив память из пула или ассоциативного списка, но тогда все его поля придется заполнять самим. Мы воспользуемся самой универсальной из четырех вышеперечисленных функций — IoAllocateIrp. В отличие от трех остальных, с её помощью можно создавать IRP любого типа.

По соображениям лучшей производительности, память под IRP выделяется в одном из двух ассоциативных списков, индивидуальных для каждого процессора (структуры управляющие списками хранятся в специфичной для каждого процессора структуре KPRCB). Если нужен IRP с одним блоком стека, то используется ассоциативный список малых IRP. Если IRP должен содержать более одного блока стека — используется ассоциативный список больших IRP. Такие IRP содержат 8 блоков стека (эта цифра хранится в переменной ядра IopLargeIrpStackLocations). В Windows NT4 эта цифра равнялась 4, но с приходом PnP глубина стеков увеличилась. Если же IRP требует более 8 блоков стека или ассоциативный список пуст, то диспетчеру в/в ничего другого не остается, как выделить память под IRP из неподкачиваемого пула. Перед тем как вернуть управление, IoAllocateIrp обнуляет весь IRP и инициализирует некоторые его поля.

  1.     Irp.Size                              = sizeof(IRP) + StackSize * sizeof(IO_STACK_LOCATION)

  2.     Irp.AllocationFlags                   = <some flags>

  3.     Irp.StackCount                        = StackSize

  4.     Irp.CurrentLocation                   = StackSize + 1

  5.     Irp.Tail.Overlay.CurrentStackLocation = &Irp + sizeof(IRP) + StackSize * sizeof(IO_STACK_LOCATION)

Самые важные для нас на данный момент поля это:

  • Irp.StackCount — максимально необходимое количество блоков стека в IRP. Это поле будет равно значению первого параметра переданного в IoAllocateIrp. Мы извлекаем его из объекта «устройство», которому собираемся отправить IRP. Каждый объект «устройство» знает, сколько под ним объектов и, соответственно, сколько нужно блоков стека.
  • Irp.CurrentLocation — порядковый номер текущего блока стека (отсчет идет в обратном порядке). Каждый раз при передаче IRP нижестоящему драйверу функция IoCallDriver уменьшает значение этого поля на единицу. Изначально же, как видите, оно на один больше чем действительно необходимо.
  • Irp.Tail.Overlay.CurrentStackLocation — указатель на текущий блок стека. Каждый раз при передаче IRP нижестоящему драйверу функция IoCallDriver уменьшает его значение на размер структуры IO_STACK_LOCATION. Изначально оно указывает на недействительный блок стека, т.е. на область памяти сразу за концом IRP. Строго говоря, это не всегда так. Например, если IRP выделен из ассоциативного списка больших IRP, то у него 8 блоков стека, а мы, допустим, заказали 5. Тогда CurrentStackLocation будет указывать на один из лишних блоков стека. Если же мы просили IRP с одним блоком или он выделен из пула, то CurrentStackLocation указывает на «чужую» память.

По возвращении из IoAllocateIrp наш IRP выглядит так (я использовал команду irp отладчика SoftICE с ключом -f):

  1.  &ThreadListEntry     : 83887018

  2.  IoStatus.Status      : 00000000

  3.  IoStatus.Information : 00000000

  4.  CurrentLocation      : <b>06</b>

  5.  Overlay              : 00000000 00000000

  6.  CancelRoutine *      : 00000000

  7.         &DeviceQueueEntry : 83887048

  8.         AuxiliaryBuffer * : 00000000

  9.         CurrentStackLoc * : <b>8388712C</b>

  10.         OrigFileObject *  : 00000000

  11.  StackLocation 1 at 83887078:

  12.  StackLocation 2 at 8388709C:

  13.  StackLocation 3 at 838870C0:

  14.  StackLocation 4 at 838870E4:

  15.  StackLocation 5 at 83887108:

  16.  CurrentStackLocation at <b>8388712C</b>:

  17.  <заполнен нулями>                      <- недействительный блок стека

IoAllocateIrp делает только заготовку будущего IRP. Кое-какие поля требуется заполнить вручную.

  1.              mov [edi].IoStatus.Status, STATUS_NOT_SUPPORTED

  2.              and [edi].IoStatus.Information, 0

  3.              mov iosb.Status, STATUS_NOT_SUPPORTED

Для формирования IRP разных типов может потребоваться заполнение разных полей. Я заполнил только самые необходимые для нас и вам не следует принимать это за образец. Подробности можно посмотреть в DDK.

После заполнения тела IRP мы должны сформировать блок стека для драйвера, которому мы адресуем запрос. Если использовать нумерацию блоков стека как её использует SoftIce, то мы должны заполнить блок стека за номером 5. Как вы помните, сейчас поле CurrentStackLocation указывает на недействительный блок стека. Для получения указателя на следующий блок стека, принадлежащий драйверу которому мы адресуем запрос, используется макрос IoGetNextIrpStackLocation:

  1.  IoGetNextIrpStackLocation MACRO pIrp:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      sub eax, sizeof IO_STACK_LOCATION

Пусть вас не смущает слово next в имени макроса. Мы ведь имеем дело со стеком. «Следующий драйвер» означает нижестоящий драйвер, а «следующий блок стека» — блок стека с адресом на sizeof(IO_STACK_LOCATION) меньше чем текущий блок стека. Соответственно «предыдущий драйвер» означает вышестоящий драйвер, а «предыдущий блок стека» — блок стека с адресом на sizeof(IO_STACK_LOCATION) больше чем текущий блок стека. Макрос IoGetNextIrpStackLocation берет значение из поля CurrentStackLocation и уменьшает его на размер структуры IO_STACK_LOCATION. Таким образом, мы движемся в сторону меньших адресов по направлению к телу IRP.

  1.              IoGetNextIrpStackLocation edi

  2.              assume ebx:ptr IO_STACK_LOCATION

  3.              mov [ebx].MajorFunction, IRP_MJ_PNP

  4.              mov [ebx].MinorFunction, IRP_MN_QUERY_PNP_DEVICE_STATE

Мы посылаем запрос типа IRP_MJ_PNP, а дополнительный код IRP_MN_QUERY_PNP_DEVICE_STATE определяет какую именно информацию о PnP характеристиках устройства мы хотим получить.

  1.               invoke KeInitializeEvent, addr keEvent, NotificationEvent, FALSE

Инициализируем объект «событие». На этом объекте мы будем ждать момента завершения IRP. Тип события может быть и SyncronizationEvent, т.к. всё равно кроме нас, его никто ждать не будет. В исходных кодах драйверов можно встретить оба варианта.

Буквально через одну строку мы собираемся послать IRP драйверу kbdclass. Если мы не предпримем специальных мер, то никогда уже не сможем увидеть наш IRP. Как это не покажется парадоксальным, с первого взгляда, но после вызова IoCallDriver обращаться к IRP нельзя. К концу статьи, надеюсь, будет ясно почему. Единственная возможность вновь получить контроль над IRP — это установить специальную процедуру — процедуру завершения (completion routine). Процедура завершения будет вызвана, в тот момент, когда какой-либо драйвер ниже по стеку завершит IRP вызовом IoCompleteRequest. Одной из задачь функции IoCompleteRequest как раз и является задача вызова всех процедур завершения. Нашу процедуру завершения я назвал IrpComplete, а установить её можно с помощью макроса IoSetCompletionRoutine (полный вариант в ntddk.inc):

  1.  IoSetCompletionRoutine MACRO pIrp:REQ, Routine:REQ, CompletionContext:REQ, Success:REQ, Error:REQ, Cancel:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      sub eax, sizeof IO_STACK_LOCATION

  4.      assume eax:ptr IO_STACK_LOCATION

  5.      pop [eax].CompletionRoutine

  6.      and byte ptr [eax].Control, 0

  7.          or byte ptr [eax].Control, SL_INVOKE_ON_SUCCESS

  8.          or byte ptr [eax].Control, SL_INVOKE_ON_ERROR

  9.          or byte ptr [eax].Control, SL_INVOKE_ON_CANCEL

Первый параметр — указатель на IRP, при завершении которого должна быть вызвана процедура, указатель на которую передается во втором параметре. Третий параметр — указатель на любые данные. Этот указатель будет передан в процедуру завершения, и в нем мы укажем адрес нашего объекта «событие», которое процедура завершения, при необходимости, должна будет перевести в сигнальное состояние. Три последних параметра определяют, в каком случае будет вызвана процедура. Нам нужно, чтобы она была вызвана в любом случае: при завершении IRP с кодом успеха, при завершении IRP с кодом ошибки, при отмене IRP. Т.е. как бы ни завершился IRP, мы всё равно его перехватим на обратном пути. Обратите внимание, что макрос IoSetCompletionRoutine использует следующий блок стека, т.е. предназначенный для нижестоящего драйвера. Т.е. адрес процедуры завершения и её параметр помещаются не в блок стека драйвера, которому он принадлежит, а в блок стека нижестоящего драйвера. Почему мы лезем в чужой блок стека со своей процедурой завершения? Дело в том, что, во-первых, у нас нет своего блока стека, точнее он нам не нужен. Мы же сами формируем IRP и прекрасно знаем, что в нем содержится. С другой стороны, драйверу, стоящему ниже в стеке, который будет завершать IRP, не нужна процедура завершения. Он же сам его завершает и прекрасно знает как.

И ещё один очень важный момент, касающийся процедур завершения. В общем случае обработка ввода/вывода с физического устройства проходит по следующей схеме. Драйвер инициирует операцию в/в. Когда устройство завершает операцию, то генерирует прерывание, которое обрабатывается процедурой обработки прерывания (Interrupt Service Routine, ISR), зарегистрированной драйвером. Причем обработка будет происходить в контексте того потока, который был текущим на момент прерывания, а это случайный поток. Т.к. ISR работает на повышенном IRQL (больше DISPATCH_LEVEL), работа всех остальных потоков на данном процессоре блокируется. Мало того, блокируются (маскируются) все прерывания с таким же или более низким уровнем. Для того чтобы обработать возможные прерывания от менее приоритетных устройств, необходимо как можно быстрее понизить IRQL. Для этого ISR делает только то, что необходимо сделать немедленно и ставит в очередь так называемый вызов отложенной процедуры (Deferred Procedure Call, DPC). DPC работает при IRQL = DISPATCH_LEVEL. Когда IRQL понижается до DISPATCH_LEVEL, система вызывает отложенную процедуру и она делает дополнительные операции по завершению IRP. В самой последней фазе отложенная процедура вызывает IoCompleteRequest, которая, как я сказал выше, вызывает все процедуры завершения. Поэтому процедура завершения может быть вызвана в контексте случайного потока и при IRQL меньше или равном DISPATCH_LEVEL.

Раз процедура завершения может быть вызвана на повышенном IRQL, то очевидно, что и она сама и все данные, к которым она обращается должны находиться в неподкачиваемой памяти. Наша процедура завершения обращается к двум структурам: IO_STATUS_BLOCK и KEVENT (сам IRP не в счет, т.к. он всегда выделяется из неподкачиваемой памяти), которые располагаются в стеке потока, выполняющего процедуру QueryPnpDeviceState. Если этот поток будет ждать, то его стек может быть выгружен в файл подкачки (то, что, в данном случае, это системный поток не в счет). Чтобы запретить системе это делать, необходимо указывать KernelMode в параметре WaitMode функций ожидания. Я уже как-то раз говорил об этом, но, на всякий случай, повторяю.

  1.              IoSetCompletionRoutine edi, IrpComplete, addr keEvent, TRUE, TRUE, TRUE

  2.              invoke IoCallDriver, esi, edi

Ну что же. Теперь у нас есть всё необходимое: адресат, сформированный IRP и процедура завершения, готовая перехватить его на обратном пути. Вызовом функции IoCallDriver, посылаем IRP драйверу, обслуживающему объект «устройство», указатель на который содержится в первом параметре.

Реализация функции IoCallDriver на удивление проста:

  1.      IN PDEVICE_OBJECT  pDeviceObject,

  2.      PIO_STACK_LOCATION   pStack

  3.      PDRIVER_OBJECT       pDriverObject

  4.      if  pIrp->CurrentLocation &lt;= 0  {

  5.          KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, pIrp, … )

  6.      pIrp->Tail.Overlay.CurrentStackLocation -= sizeof(IO_STACK_LOCATION)

  7.      pStack = pIrp->Tail.Overlay.CurrentStackLocation

  8.      pStack->DeviceObject = pDeviceObject

  9.      pDriverObject = pDeviceObject->DriverObject

  10.      status = pDriverObject->MajorFunction[pStack->MajorFunction]( pDeviceObject, pIrp )

Сначала IoCallDriver уменьшает значение CurrentLocation на единицу и если оно вдруг стало равно нулю или ещё меньше, то система показывает «голубой экран смерти», т.к. нулевое значение в поле CurrentLocation говорит о том, что мы исчерпали все блоки стека и если IoCallDriver пойдет дальше, то просто будет «затирать» тело IRP, что рано или поздно всё равно приведет к краху. Затем значение в CurrentStackLocation уменьшается на размер структуры IO_STACK_LOCATION. Вот теперь оба поля: CurrentLocation и CurrentStackLocation соответствуют заполненному нами блоку стека. CurrentLocation равно 5, а CurrentStackLocation — 83887108. Сейчас наш IRP выглядит так:

  1.  &ThreadListEntry     : 83887018

  2.  IoStatus.Status      : C00000BB

  3.  IoStatus.Information : 00000000

  4.  CurrentLocation      : <b>05</b>

  5.  Overlay              : 00000000 00000000

  6.  CancelRoutine *      : 00000000

  7.         &DeviceQueueEntry : 83887048

  8.         AuxiliaryBuffer * : 00000000

  9.         CurrentStackLoc * : <b>83887108</b>

  10.         OrigFileObject *  : 00000000

  11.  StackLocation 1 at 83887078:

  12.  StackLocation 2 at 8388709C:

  13.  StackLocation 3 at 838870C0:

  14.  StackLocation 4 at 838870E4:

  15.  CurrentStackLocation at <b>83887108</b>:

  16.  MajorFunction     : 1B IRP_MJ_PNP

  17.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

  18.  Others            : 00000000 00000000 00000000 00000000

  19.  DeviceObject *    : 81852AB0

  20.  CompletionRout *  : ED5E14C0

Далее IoCallDriver помещает в поле DeviceObject текущего блока стека указатель на вызываемый объект «устройство». Этот указатель может потребоваться процедуре завершения. Затем из объекта «устройство» извлекается указатель на обслуживающий его драйвер и вызывается одна из процедур диспетчеризации драйвера. Т.к. в pStack->MajorFunction находится IRP_MJ_PNP, IoCallDriver берет из соответствующего элемента массива MajorFunction указатель на процедуру и передает ей адреса объекта «устройство» и IRP (вспомните любую функцию диспетчеризации, коих мы написали уже не мало). Если драйвер не занёс в соответствующее поле массива MajorFunction указатель на свою процедуру обработки данного типа IRP, то по умолчанию там находится указатель на системную функцию IopInvalidDeviceRequest, которая просто возвращает STATUS_INVALID_DEVICE_REQUEST и на этом обработка IRP будет завершена, не начавшись. Если же нужная процедура у драйвера имеется, а kbdclass имеет процедуру для обработки запросов IRP_MJ_PNP, то мы в нее и попадем, а IoCallDriver вернет то, что вернет эта процедура.

Теперь, прежде чем мы погрузимся в kbdclass, немного «уйдем в сторону» и представим, что IRP, только что сформированный нами, не IRP типа IRP_MJ_PNP, а гипотетический IRP_MJ_UNKNOWN, и посылаем мы его абстрактному драйверу unknown, процедура диспетчеризации которого выглядит приблизительно так:

  1.  DispatchUnknown proc uses esi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

  2.          lea ecx, [esi].Tail.Overlay.ListEntry

  3.          InsertTailList addr g_IrpQueue, ecx

  4.          mov status, STATUS_PENDING

  5.          mov status, STATUS_SUCCESS

  6.          mov [esi].IoStatus.Status, STATUS_SUCCESS

  7.          mov [esi].IoStatus.Information, SOME_INFORMATION

  8.          fastcall IofCompleteRequest, esi, IO_NO_INCREMENT

Драйвер unknown либо сразу завершает IRP, либо ставит его в очередь, для того чтобы завершить позже. Сначала рассмотрим первый случай.

Прежде чем добавить IRP в очередь, драйвер должен отметить его как ожидающий завершения (pending). Это можно сделать с помощью макроса IoMarkIrpPending, который выглядит так:

  1.  IoMarkIrpPending MACRO pIrp:REQ

  2.      mov eax, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      or (IO_STACK_LOCATION PTR [eax]).Control, SL_PENDING_RETURNED

Обратите внимание — флаг означающий, что IRP ожидает завершения, помещается не в тело IRP, а в текущий блок стека. Т.е. каждый драйвер независимо от других может проделать эту операцию.

Дальше драйвер помещает IRP в очередь и возвращает код STATUS_PENDING, говорящий вышестоящему драйверу о том, что завершение IRP отложено на неопределенное время. В нашем случае, вышестоящий драйвер — наш драйвер и ему необходимы результаты завершения IRP. Поэтому будем ждать, на созданном нами объекте «событие».

Существует несколько механизмов, которыми драйверы могут пользоваться для постановки IRP в очередь, но в итоге все сводится к добавлению IRP в двусвязный список. В самом простом случае можно использовать поле IRP.Tail.Overlay.ListEntry. Для того чтобы гарантировать себе монопольный доступ к очереди драйверы используют блокировку. Как работает очередь и блокировка, сейчас не важно.

По прошествии некоторого времени драйвер решает удалить IRP из очереди и завершить его.

  1.      RemoveHeadList addr g_IrpQueue

  2.      sub eax, _IRP.Tail.Overlay.ListEntry

  3.      mov esi, eax           ; esi -> _IRP

  4.      mov [esi].IoStatus.Status, STATUS_SUCCESS

  5.      mov [esi].IoStatus.Information, SOME_INFORMATION

  6.      fastcall IofCompleteRequest, esi, IO_NO_INCREMENT

Это может произойти в контексте любого потока и в любой момент (в результате прерывания). В данном случае, для нас важно лишь то, что драйвер вызывает IoCompleteRequest.

  1.      PIO_STACK_LOCATION pStack

  2.      pStack->MinorFunction               = 0

  3.      pStack->Parameters.Others.Argument1 = 0

  4.      pStack->Parameters.Others.Argument2 = 0

  5.      pStack->Parameters.Others.Argument3 = 0

  6.      pStack->Parameters.Others.Argument4 = 0

  7.      pStack->FileObject                  = NULL

  8.      PIO_STACK_LOCATION pStack

  9.      if  pIrp->CurrentLocation > pIrp->StackCount + 1  {

  10.          KeBugCheckEx( MULTIPLE_IRP_COMPLETE_REQUESTS, … )

  11.      ASSERT( pIrp->IoStatus.Status != STATUS_PENDING )

  12.      pStack = IoGetCurrentIrpStackLocation( pIrp )

  13.      pIrp->Tail.Overlay.CurrentStackLocation += sizeof(IO_STACK_LOCATION)

  14.      while  pIrp->CurrentLocation <= pIrp->StackCount + 1  {

  15.          pIrp->PendingReturned = pStack->Control & SL_PENDING_RETURNED

  16.          if  pIrp->IoStatus.Status == STATUS_SUCCESS  &&  pStack->Control & SL_INVOKE_ON_SUCCESS

  17.              pIrp->IoStatus.Status != STATUS_SUCCESS  &&  pStack->Control & SL_INVOKE_ON_ERROR

  18.              pIrp->Cancel == TRUE  &&  pStack->Control & SL_INVOKE_ON_CANCEL

  19.              ZeroIrpStackLocation( pStack )

  20.              PDEVICE_OBJECT    pDeviceObject

  21.              if  pIrp->CurrentLocation == pIrp->StackCount + 1  {

  22.                  pDeviceObject = IoGetCurrentIrpStackLocation( pIrp )->DeviceObject

  23.              status = pStack->CompletionRoutine( pDeviceObject, pIrp, pStack->Context )

  24.              if  status == STATUS_MORE_PROCESSING_REQUIRED  {

  25.              if  pIrp->PendingReturned  &&  pIrp->CurrentLocation <= pIrp->StackCount  {

  26.              ZeroIrpStackLocation( pStack )

  27.          pStack += sizeof(IO_STACK_LOCATION)

  28.          pIrp->Tail.Overlay.CurrentStackLocation += sizeof(IO_STACK_LOCATION)

Рис. 15-6. Блок-схема функции IoCompleteRequest.

IoCompleteRequest должна пройтись по всем блокам стека, участвовавшим в обработке IRP, причем в обратном порядке, и вызвать все процедуры завершения. Когда IRP продвигается вниз, то значения полей CurrentLocation и CurrentStackLocation уменьшаются с каждым вызовом IoCallDriver (исключением является случай, когда драйвер передает свой собственный блок стека нижестоящему драйверу, пользуясь макросом IoSkipCurrentIrpStackLocation). IoCompleteRequest проделывает обратную работу, начиная с текущего блока стека, т.е. того, указатель на который находится в поле CurrentStackLocation (именно этот блок стека был текущим для драйвера вызвавшего IoCompleteRequest).
Когда IoCompleteRequest «поднимется» до самого верха, значения этих двух полей будут такими же, какими они были сразу после вызова IoAllocateIrp. Т.е. значение в поле CurrentLocation должно быть на единицу больше чем StackCount, а CurrentStackLocation будет указывать на недействительный блок стека.
Поэтому если CurrentLocation больше или равно StackCount + 1, это означает, что IRP уже был завершен. А завершать два раза IRP это примерно то же самое, что повторно вызывать ExFreePool с одним и тем же указателем. «Синий экран смерти» тут как нельзя кстати. Поэтому завершать IRP можно только один раз.

Дальше идет отладочное утверждение ASSERT. Код, заключенный в макрос ASSERT попадает только в отладочный выпуск (checked build) системы. В свободном выпуске (free build) системы отловить такой баг можно с помощью Driver Verifier. Я специально добавил эту строку, т.к. завершение IRP с кодом STATUS_PENDING — очень распространенная ошибка. IRP может либо завершаться, либо ожидать завершения. Третьего не дано.

Правило:

Завершать IRP с кодом STATUS_PENDING нельзя.

Далее IoCompleteRequest получает указатель на текущий блок стека, вызовом макроса IoGetCurrentIrpStackLocation. А какой блок стека текущий в данном случае? Сейчас текущим является блок стека, принадлежащий драйверу unknown. Ведь IRP продвигался вниз всего на «один шаг». Если бы драйверу unknown понадобился указатель на его блок стека, то вызовом IoGetCurrentIrpStackLocation, он получил бы тот же самый адрес.

Потом IoCompleteRequest крутит цикл, проходя по всем участвовавшим в обработке IRP блокам стека в обратном порядке. Если в блоке стека установлен флаг SL_PENDING_RETURNED, значит драйвер, которому он принадлежит, вызывал IoMarkIrpPending. Если это так, то устанавливается ненулевое значение в поле IRP.PendingReturned. А если флаг SL_PENDING_RETURNED не установлен, то поле IRP.PendingReturned обнуляется. Это нужно для того, чтобы вышестоящий драйвер в своей процедуре завершения смог видеть, что нижестоящий драйвер отмечал IRP как ожидающий завершения. Обращаться к чужим блокам стека драйверы не должны (исключение — копирование/заполнение блока стека при передаче IRP вниз по стеку). IoCompleteRequest даже специально обнуляет некоторые поля обработанного блока стека используя ZeroIrpStackLocation (на самом деле это макрос, а не функция). Поэтому SL_PENDING_RETURNED, как бы «перекладывается» в поле PendingReturned самого IRP. Когда мы доберемся до схемы на рис. 15-7, предназначение поля PendingReturned станет более понятно.

Если вышестоящий драйвер установил процедуру завершения (вы должны помнить, что драйверы устанавливают процедуры завершения в блоке стека, принадлежащем нижестоящему драйверу), она вызывается. В процедуру завершения передается указатель на объект «устройство», принадлежащий драйверу установившему эту процедуру. Поскольку инициатор запроса (наш драйвер, в данном случае) не имеет своего блока стека, то в качестве указателя на объект «устройство» он получит NULL.

Если процедуре завершения потребуется обратиться к текущему блоку стека она тоже может использовать макрос IoGetCurrentIrpStackLocation. А какой блок стека она получит? Процедура завершения получит блок стека, принадлежащий её драйверу. Т.е. и в процедуре диспетчеризации и в процедуре завершения IoGetCurrentIrpStackLocation возвращает один и тот же указатель. Можем ли мы как создатели IRP в нашей процедуре завершения вызвать IoGetCurrentIrpStackLocation? Нет. Точнее указатель то мы получим, но на недействительный блок стека. Ведь своего собственного блока стека у нас нет, т.к. он нам не нужен.

Если процедура завершения вернула STATUS_MORE_PROCESSING_REQUIRED, то IoCompleteRequest, не делая ни одного лишнего движения, сразу возвращает управление, т.к. трогать IRP она уже не имеет права — возможно, IRP уже не существует. В нашем случае это именно так, ведь мы в процедуре завершения вызываем IoFreeIrp и для того, чтобы заставить IoCompleteRequest немедленно прекратить дальнейшие действия по завершению IRP, возвращаем STATUS_MORE_PROCESSING_REQUIRED. Если же процедура завершения возвращает любой другой код, то IoCompleteRequest продолжает работу. DDK рекомендует в качестве «любого другого кода» возвращать STATUS_SUCCESS просто потому, что он равен 0, а это приводит к генерации компилятором более оптимального кода. В более поздних DDK можно найти такие определения:

  1.  #define STATUS_CONTINUE_COMPLETION      STATUS_SUCCESS

  2.  typedef enum _IO_COMPLETION_ROUTINE_RESULT {

  3.      ContinueCompletion = STATUS_CONTINUE_COMPLETION,

  4.      StopCompletion     = STATUS_MORE_PROCESSING_REQUIRED

  5.  } IO_COMPLETION_ROUTINE_RESULT, *PIO_COMPLETION_ROUTINE_RESULT;

Имена констант ContinueCompletion и StopCompletion значительно лучше отражают суть, чем STATUS_SUCCESS и STATUS_MORE_PROCESSING_REQUIRED. Т.о., возвращая StopCompletion, мы говорим функции IoCompleteRequest, что она должна немедленно прекратить работу и вернуть управление. Если мы возвращаем ContinueCompletion (точнее говоря, не возвращаем StopCompletion), то IoCompleteRequest продолжает процесс завершения IRP.

Для чего нам нужно остановить IoCompleteRequest? Мы, как создатели IRP, не можем допустить, чтобы диспетчер в/в завершал созданный нами IRP. Это наша работа. Единственная возможность это сделать — установить процедуру завершения.

Если в обрабатываемом блоке стека нет указателя на процедуру завершения, то IoCompleteRequest смотрит, было ли установлено на предыдущем шаге поле IRP.PendingReturned. Если да, и всё ещё есть действительный блок стека, взводит флаг SL_PENDING_RETURNED в предыдущем блоке стека (этот блок IoCompleteRequest будет обрабатывать при следующем витке цикла), используя макрос IoMarkIrpPending.

Представим теперь два плохих сценария:

  • драйвер unknown возвращает STATUS_PENDING, но забывает про IoMarkIrpPending;
  • драйвер unknown отмечает IRP как ожидающий завершения, используя IoMarkIrpPending, но забывает вернуть STATUS_PENDING.

Сценарий 1: Если драйвер возвращает из процедуры диспетчеризации код STATUS_PENDING, IoCallDriver передаст этот код нам. Увидев такой код, мы бесконечно ждем, пока наша процедура завершения не освободит событие. По прошествии некоторого времени драйвер unknown инициирует завершение IRP. IoCompleteRequest смотрит в блок стека, принадлежащий драйверу unknown, и, не обнаружив там флага SL_PENDING_RETURNED, обнуляет IRP.PendingReturned. Видя, что в блоке стека установлена процедура завершения (установленная нашим драйвером), IoCompleteRequest вызывает её. Получив управление, наша процедура завершения не сигналит событие и освобождает память, занятую под IRP. В результате событие уже никогда не будет освобождено и поток, ожидающий на нем, никогда не возобновит работу.

Вариацией этого сценария будет ситуация, когда драйвер unknown ставит IRP в очередь, а потом вызывает IoMarkIrpPending (имеется ввиду, что блокировка очереди уже снята). Тогда ещё до того как он доберется до IoMarkIrpPending, IRP может быть извлечен из очереди и завершен.

Сценарий 2: Получив от IoCallDriver код отличный от STATUS_PENDING, наш драйвер считает, что IRP завершен и в зависимости от ошибочно возвращенного кода либо получает неверные данные, либо не получает ничего. Но это не самое страшное. Хуже, если мы переведем IoFreeIrp из процедуры завершения в основную процедуру после IoCallDriver, а мы имеем полное право это сделать. Драйвер unknown ведь не знает деталей реализации вышестоящего драйвера, и ни в коем случае не должен на это полагаться. Посчитав, что IRP завершен, мы вызовем IoFreeIrp. Через некоторое время драйвер unknown пытается извлечь уже не существующий IRP из очереди…

Не сложно догадаться, что для сценария 1 можно применить простое противоядие: вне зависимости от значения поля PendingReturned всегда вызывать KeSetEvent в процедуре завершения. Можно конечно, но тогда во всех случаях, когда IRP завершается немедленно, мы будем зря вызывать KeSetEvent, а она блокирует базу данных диспетчера потоков, ищет потоки, ждущие на событии, и делает их планируемыми, разблокирует базу данных диспетчера потоков. Вобщем, кое-какие накладные расходы будут. Но дело даже не в этом. Мы можем переписать нашу процедуру завершения, но мы не можем переписать код диспетчера в/в, который реализует свою логику работы. Диспетчер в/в вообще не устанавливает процедуру завершения. Он использует другие механизмы, но при принятии решений также опирается на код, возвращенный IoCallDriver и значение поля PendingReturned.

Правило:

Если из процедуры диспетчеризации драйвер возвращает код STATUS_PENDING, то перед этим должен вызвать IoMarkIrpPending. Если в процедуре диспетчеризации драйвер вызывает IoMarkIrpPending, то должен вернуть код STATUS_PENDING. Либо и то и другое, либо ни того, ни другого.

Вернемся к стеку клавиатуры. Мы уже вызвали IoCallDriver и сейчас находимся в процедуре диспетчеризации KeyboardPnP драйвера kbdclass.

Я использую здесь исходный код из 2003 IFS KIT. В 2000 DDK код функции KeyboardPnP отличается: драйвер kbdclass синхронизирует обработку IRP, используя функцию KeyboardSendIrpSynchronously, почти идентичную функции I8xSendIrpSynchronously драйвера i8042ptr (см. ниже). Во-первых, так нам будет проще, а во-вторых, это внесет некоторое разнообразие.

  1.      PIO_STACK_LOCATION    pStack

  2.      pStack = IoGetCurrentIrpStackLocation( pIrp )

  3.      if  pStack->MinorFunction == IRP_MN_QUERY_PNP_DEVICE_STATE  {

  4.          pIrp->IoStatus.Information |= PNP_DEVICE_NOT_DISABLEABLE

  5.          pIrp->IoStatus.Status = STATUS_SUCCESS

  6.          IoCopyCurrentIrpStackLocationToNext( pIrp )

  7.          status = IoCallDriver( NextLowerDeviceObject, pIrp )

Первым делом kbdclass получает указатель на свой блок стека, чтобы посмотреть чего от него хотят.

При обработке IRP_MN_QUERY_PNP_DEVICE_STATE драйвер должен поместить в поле IRP.IoStatus.Information флаг, определяющий состояние устройства. При этом, поскольку поле IRP.IoStatus.Information одно, а драйверов в стеке много, все они используют логические операции для установки или сброса нужных флагов. Драйвер kbdclass добавляет флаг PNP_DEVICE_NOT_DISABLEABLE и помещает в IRP код успеха. Теперь он должен передать его нижестоящему драйверу. При этом дальнейшая судьба этого запроса его не интересует и он не устанавливает процедуру завершения. Уак будет завершен IRP, kbdclass не узнает уже никогда. Несмотря на то, что после вызова IoCallDriver в переменной pIrp всё еще будет хранится число, являвшееся указателем на IRP, обращаться по этому указателю драйвер kbdclass не имеет права, т.к., возможно, этот IRP уже не существует и на схеме 15-7 это будет очень хорошо видно.

Перед вызовом нижестоящего драйвера, драйвер kbdclass (и любой другой) должен заполнить причитающийся ему (нижестоящему драйверу) блок стека. В данном случае, т.к. kbdclass не формирует новый IRP, а пересылает переданный ему свыше, он может просто скопировать свой блок стека в следующий (помните, что это стек, где всё поставлено с ног на голову, т.е. следующим будет блок стека расположенный в памяти ниже). Это можно сделать с помощью макроса IoCopyCurrentIrpStackLocationToNext. В ntddk.inc можно увидеть оптимизированный вариант, а здесь приводится белее доступная для понимания версия.

  1.  IoCopyCurrentIrpStackLocationToNext MACRO pIrp:REQ

  2.      mov esi, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  3.      mov edx, (_IRP PTR [eax]).Tail.Overlay.CurrentStackLocation

  4.      sub edx, sizeof IO_STACK_LOCATION

  5.      mov ecx, sizeof IO_STACK_LOCATION

  6.      and (IO_STACK_LOCATION PTR [edx]).Control, 0

  7.      and (IO_STACK_LOCATION PTR [edx]).CompletionRoutine, 0

  8.      and (IO_STACK_LOCATION PTR [edx]).Context, 0

Как видно, макрос копирует текущий блок стека в следующий, но три поля: Control, CompletionRoutine и Context обнуляются. Зачем обнуляются эти поля, мы знаем ниже. Теперь kbdclass вызывает IoCallDriver, передавая в своей переменной NextLowerDeviceObject, указатель на объект «устройство» находящийся непосредственно под ним. Этот указатель kbdclass получает при подключении к стеку. Т.к. мы договорились рассматривать классический состав стека, следующим в стеке оказывается объект «устройство», принадлежащий драйверу i8042ptr и мы оказываемся в его процедуре диспетчеризации I8xPnP.

  1.      PIO_STACK_LOCATION  pStack

  2.      pStack = IoGetCurrentIrpStackLocation( pIrp )

  3.      if  pStack->MinorFunction == IRP_MN_QUERY_PNP_DEVICE_STATE  {

  4.          status = I8xSendIrpSynchronously( TopOfStack, pIrp, FALSE )

  5.          pIrp->IoStatus.Information |= PnpDeviceState

  6.          pIrp->IoStatus.Status = status

  7.          IoCompleteRequest( pIrp, IO_NO_INCREMENT )

i8042ptr также получает указатель на свой блок стека и синхронно перенаправляет IRP следующему (нижестоящему) драйверу acpi, указатель на который хранится в переменной TopOfStack.

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2.      KeSetEvent( pEvent, 0, FALSE )   // Four-F: It’s not good to signal event unconditionaly.

  3.      return STATUS_MORE_PROCESSING_REQUIRED

  4.  I8xSendIrpSynchronously (

  5.      IN PDEVICE_OBJECT pDeviceObject,

  6.      KeInitializeEvent( &event, SynchronizationEvent, FALSE )

  7.      IoCopyCurrentIrpStackLocationToNext( pIrp )

  8.      IoSetCompletionRoutine( pIrp, I8xPnPComplete, &Event, TRUE, TRUE, TRUE )

  9.      status = IoCallDriver( pDeviceObject, pIrp )

  10.      if  status == STATUS_PENDING  {

  11.         KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, NULL )

  12.         status = pIrp->IoStatus.Status

Разбирать функции I8xSendIrpSynchronously и I8xPnPComplete я не буду, т.к. они реализуют ту же логику работы, что и наши QueryPnpDeviceState и IrpComplete. Разобравшись с кодом нашего драйвера, вы без труда поймете, как работают эти две функции.

По возвращении из I8xSendIrpSynchronously, драйвер i8042ptr добавляет в поле Information свою порцию флагов из переменной PnpDeviceState и завершает IRP, вызовом IoCompleteRequest.

Ну, и, наконец, процедура диспетчеризации драйвера acpi будет у нас выглядеть так (на самом деле всё гораздо сложнее):

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2.      pIrp->IoStatus.Information |= PNP_DEVICE_NOT_DISABLEABLE

  3.      pIrp->IoStatus.Status = STATUS_SUCCESS

  4.      IoCompleteRequest( pIrp, IO_NO_INCREMENT )

Теперь рассмотрим случай, когда обработка IRP будет синхронной, т.е. пройдет в контексте одного и того же потока. Все драйверы в стеке завершают IRP немедленно и, соответственно, ни одна из процедур диспетчеризации не возвращает STATUS_PENDING. Будем пользоваться схемой на рис. 15-7. Нарисовав эту схему, я был приятно удивлен тем, насколько хорошо видны на ней некоторые совсем неочевидные вещи.

Рис. 15-7. Этапы обработки IRP.

  1. Наш драйвер QueryPnpDeviceState создает IRP, инициализирует объект «событие», на котором будет ждать завершения IRP, если завершение будет отложено, устанавливает процедуру завершения IrpComplete и посылает IRP драйверу kbdclass.

  2. Драйвер kbdclass перенаправляет IRP нижестоящему драйверу i8042prt, не устанавливая процедуру завершения.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : C00000BB

    3.  IoStatus.Information : 00000000

    4.  CurrentLocation      : <b>04</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>838870E4</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  StackLocation 3 at 838870C0:

    14.  CurrentStackLocation at <b>838870E4</b>:

    15.  MajorFunction     : 1B IRP_MJ_PNP

    16.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    17.  Others            : 00000000 00000000 00000000 00000000

    18.  DeviceObject *    : 81852CA0

    19.  CompletionRout *  : 00000000

    20.  StackLocation 5 at 83887108:

    21.  MajorFunction     : 1B IRP_MJ_PNP

    22.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    23.  Others            : 00000000 00000000 00000000 00000000

    24.  DeviceObject *    : 81852AB0

    25.  CompletionRout *  : ED5E14C0

  3. Драйвер i8042prt инициализирует объект «событие», на котором будет ждать завершения IRP, если завершение будет отложено, устанавливает процедуру завершения I8xPnpComplete и передаёт IRP нижестоящему драйверу acpi.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : C00000BB

    3.  IoStatus.Information : 00000000

    4.  CurrentLocation      : <b>03</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>838870C0</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  CurrentStackLocation at <b>838870C0</b>:

    14.  MajorFunction     : 1B IRP_MJ_PNP

    15.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    16.  Others            : 00000000 00000000 00000000 00000000

    17.  DeviceObject *    : 81852CA0

    18.  CompletionRout *  : ED09043F

    19.  StackLocation 4 at 838870E4:

    20.  MajorFunction     : 1B IRP_MJ_PNP

    21.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    22.  Others            : 00000000 00000000 00000000 00000000

    23.  DeviceObject *    : 81852CA0

    24.  CompletionRout *  : 00000000

    25.  StackLocation 5 at 83887108:

    26.  MajorFunction     : 1B IRP_MJ_PNP

    27.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    28.  Others            : 00000000 00000000 00000000 00000000

    29.  DeviceObject *    : 81852AB0

    30.  CompletionRout *  : ED5E14C0

  4. Драйвер acpi завершает IRP (возможно предварительно разослав его каким-то другим драйверам), вызывая IoCompleteRequest.

    Функция IoCompleteRequest начинает завершение IRP. Смотрит в блок стека принадлежащий драйверу acpi. Не найдя там флага SL_PENDING_RETURNED (драйвер acpi не вызывал макрос IoMarkIrpPending), не устанавливает поле IRP.PendingReturned. Находит указатель на процедуру завершения I8xPnpComplete вышестоящего драйвера i8042prt и вызывает её.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : 00000000         <- STATUS_SUCCESS

    3.  IoStatus.Information : 00000020         <- PNP_DEVICE_NOT_DISABLEABLE

    4.  CurrentLocation      : <b>04</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>838870E4</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  StackLocation 3 at 838870C0:

    14.  MajorFunction     : 1B IRP_MJ_PNP

    15.  MinorFunction     : 00                  <- обнулено ZeroIrpStackLocation

    16.  Control           : 00                  <- обнулено ZeroIrpStackLocation

    17.  Others            : 00000000 00000000 00000000 00000000

    18.  DeviceObject *    : 818A64F0

    19.  CompletionRout *  : ED09043F

    20.  CurrentStackLocation at <b>838870E4</b>:

    21.  MajorFunction     : 1B IRP_MJ_PNP

    22.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    23.  Others            : 00000000 00000000 00000000 00000000

    24.  DeviceObject *    : 81852CA0

    25.  CompletionRout *  : 00000000

    26.  StackLocation 5 at 83887108:

    27.  MajorFunction     : 1B IRP_MJ_PNP

    28.  MinorFunction     : 14 IRP_MN_QUERY_PNP_DEVICE_STATE

    29.  Others            : 00000000 00000000 00000000 00000000

    30.  DeviceObject *    : 81852AB0

    31.  CompletionRout *  : ED5E14C0

  5. Процедура завершения I8xPnpComplete совершенно напрасно сигналит событие (драйвер i8042prt не ждет и не будет ждать на этом событии) и возвращает код STATUS_MORE_PROCESSING_REQUIRED.

    Увидев код STATUS_MORE_PROCESSING_REQUIRED, IoCompleteRequest немедленно прекращает работу и возвращает управление в процедуру диспетчеризации драйвера acpi.

  6. Драйвер acpi возвращает код STATUS_SUCCESS, и мы выходим из функции IoCallDriver в драйвере i8042prt.

    Увидев, что возвращенный из IoCallDriver код не STATUS_PENDING, драйвер i8042prt не ждет на событии. Сейчас драйвер i8042prt имеет полное право обращаться к IRP, т.к. устанавливал процедуру завершения, которая прервала обработку IRP. Поскольку драйвер i8042prt прервал завершение IRP, вернув из своей процедуры завершения код STATUS_MORE_PROCESSING_REQUIRED, то должен возобновить этот процесс. Что он и делает вызовом IoCompleteRequest.

    Выше мы выяснили, что завершать IRP два раза нельзя. Здесь же мы видим уже второй вызов IoCompleteRequest. Есть ли тут противоречие? Нет. Завершение IRP — это не просто вызов IoCompleteRequest. Это многоэтапный процесс. На каждом этапе он может быть прерван и возобновлен вновь. Только когда все эти этапы будут пройдены, IRP считается завершенным.

  7. Функция IoCompleteRequest продолжает завершать IRP с того места, где её прервали, т.е. с текущего блока стека, а текущим сейчас является блок стека драйвера i8042prt. В блоке стека драйвера i8042prt нет флага SL_PENDING_RETURNED (драйвер i8042prt тоже не вызывал макрос IoMarkIrpPending). Поэтому IRP.PendingReturned опять обнуляется. IoCompleteRequest не находит указатель на процедуру завершения в блоке стека драйвера i8042prt и переходит к предыдущему и последнему блоку стека драйвера kbdclass. kbdclass тоже не использовал макрос IoMarkIrpPending и IRP.PendingReturned опять обнуляется. В блоке стека драйвера kbdclass имеется указатель на нашу процедуру завершения IrpComplete, которая и вызывается.

    Вспомните, что при передаче IRP нижестоящему драйверу, драйвер kbdclass скопировал свой блок стека в следующий, использую макрос IoCopyCurrentIrpStackLocationToNext. Однако этот макрос не копирует поля связанные с процедурой завершения. Если бы он этого не сделал, то указатель на нашу процедуру завершения (он находится в блоке стека драйвера kbdclass) попал бы в блок стека драйвера i8042prt, и наша процедура завершения была бы вызвана дважды. В стародавние времена, когда ещё не было макроса IoCopyCurrentIrpStackLocationToNext, программисты вручную копировали блоки стека, иногда забывая обнулить поля связанные с процедурой завершения, что приводило к трудно находимым багам.

    1.  &ThreadListEntry     : 83887018

    2.  IoStatus.Status      : 00000000

    3.  IoStatus.Information : 00000020

    4.  CurrentLocation      : <b>06</b>

    5.  Overlay              : 00000000 00000000

    6.  CancelRoutine *      : 00000000

    7.         &DeviceQueueEntry : 83887048

    8.         AuxiliaryBuffer * : 00000000

    9.         CurrentStackLoc * : <b>8388712C</b>

    10.         OrigFileObject *  : 00000000

    11.  StackLocation 1 at 83887078:

    12.  StackLocation 2 at 8388709C:

    13.  StackLocation 3 at 838870C0:

    14.  MajorFunction     : 1B IRP_MJ_PNP

    15.  MinorFunction     : 00                 <- обнулено ZeroIrpStackLocation

    16.  Control           : 00                 <- обнулено ZeroIrpStackLocation

    17.  Others            : 00000000 00000000 00000000 00000000

    18.  DeviceObject *    : 818A64F0

    19.  CompletionRout *  : ED09043F

    20.  StackLocation 4 at 838870E4:

    21.  MajorFunction     : 1B IRP_MJ_PNP

    22.  MinorFunction     : 00                 <- обнулено ZeroIrpStackLocation

    23.  Others            : 00000000 00000000 00000000 00000000

    24.  DeviceObject *    : 81852CA0

    25.  CompletionRout *  : 00000000

    26.  StackLocation 5 at 83887108:

    27.  MajorFunction     : 1B IRP_MJ_PNP

    28.  MinorFunction     : 00                 <- обнулено ZeroIrpStackLocation

    29.  Control           : 00                 <- обнулено ZeroIrpStackLocation

    30.  Others            : 00000000 00000000 00000000 00000000

    31.  DeviceObject *    : 81852AB0

    32.  CompletionRout *  : ED5E14C0

    33.  CurrentStackLocation at <b>8388712C</b>:

    34.  <заполнен нулями>                      <- недействительный блок стека

    Наша процедура завершения несколько умнее. Видя, что поле PendingReturned равно нулю, она понимает, что нижестоящий драйвер не возвращал STATUS_PENDING, а значит, процедура диспетчеризации драйвера QueryPnpDeviceState не ждет на событии. Поэтому и сигналить его нет смысла. Мы установили процедуру завершения только для того, чтобы удалить, созданный нами IRP. Можем сделать это прямо сейчас, вызвав IoFreeIrp. Поскольку IRP больше нет, мы должны остановить его завершение, вернув код STATUS_MORE_PROCESSING_REQUIRED.

  8. Увидев код STATUS_MORE_PROCESSING_REQUIRED, IoCompleteRequest немедленно прекращает работу и возвращает управление в процедуру диспетчеризации драйвера i8042prt. Вот здесь очень хорошо видно, почему после вызова IoCompleteRequest нельзя обращаться к IRP. Ведь возможно IRP уже не существует, и узнать это драйвер вызывающий IoCompleteRequest не может. Обратите внимание на то, что функция IoCompleteRequest не возвращает никакого значения.

    Правило:

    После вызова процедуры IoCompleteRequest обращаться к IRP нельзя. Возможно, IRP уже не существует.

  9. Процедура диспетчеризации драйвера i8042prt возвращает код, который вернула вызванная им IoCallDriver, а это, в данном случае, STATUS_SUCCESS и мы выходим из функции IoCallDriver в драйвере kbdclass. И опять здесь хорошо видно, почему после вызова IoCallDriver нельзя обращаться к IRP, если, конечно, не устанавливать процедуру завершения и не прерывать завершение IRP. Ведь IRP то уже не существует. Драйвер kbdclass отказался от установки процедуры завершения, а значит, после вызова IoCallDriver полностью потерял контроль над IRP. Кто и когда завершит IRP драйвер kbdclass не узнает, а значит, не может делать никаких предположений о том, существует ли IRP до сих пор или его уже нет. Драйвер i8042prt смог обратиться к IRP после вызова IoCallDriver только потому, что его процедура завершения прервала процесс завершения IRP, а драйвер kbdclass не может.

    Правило:

    Если у вас нет процедуры завершения или имеющаяся у вас процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то после вызова IoCallDriver обращаться к IRP нельзя. Возможно, IRP уже не существует.

  10. Процедура диспетчеризации драйвера kbdclass возвращает код, который вернула, вызванная им, IoCallDriver, а это, в данном случае, STATUS_SUCCESS и мы выходим из функции IoCallDriver в нашем драйвере QueryPnpDeviceState.

    Видя, что возвращенный из IoCallDriver код не STATUS_PENDING, мы не ждем на событии. Хотя мы и установили процедуру завершения, и она вернула STATUS_MORE_PROCESSING_REQUIRED, но трогать IRP после возвращения из IoCallDriver всё равно не можем. Это исключение из правил, т.к. мы являемся создателем IRP. Надеюсь, здесь это очевидно. Мы же сами удалили IRP в процедуре завершения и прекратили его дальнейшее завершение.

Теперь поставим на место драйвера acpi драйвер unknown и представим, что он откладывает завершение IRP и возвращает из своей процедуры диспетчеризации STATUS_PENDING. Т.е. обработка IRP будет асинхронной.

Т.к. драйвер unknown откладывает завершение IRP, то, используя макрос IoMarkIrpPending, заносит в свой блок стека флаг SL_PENDING_RETURNED, ставит IRP в очередь и возвращает STATUS_PENDING. Мы выходим из функции IoCallDriver в драйвере i8042prt. Увидев код STATUS_PENDING, драйвер i8042prt начинает ждать освобождения события и текущий поток блокируется.

Через некоторое время в результате прерывания или по другой причине, но в контексте какого-то другого потока, драйвер unknown достает IRP из очереди и завершает его вызовом IoCompleteRequest. IoCompleteRequest обнаруживает в блоке стека драйвера unknown флаг SL_PENDING_RETURNED, и поле IRP.PendingReturned принимает ненулевое значение. Обнаружив указатель на процедуру завершения I8xPnpComplete вышестоящего драйвера i8042prt, вызывает её. Процедура завершения I8xPnpComplete сигналит событие и возвращает код STATUS_MORE_PROCESSING_REQUIRED, что заставляет функцию IoCompleteRequest прекратить работу и вернуться туда, откуда она была вызвана.

Ожидающий на событии поток пробуждается. Сейчас драйвер i8042prt имеет полное право обращаться к IRP, т.к. прервал завершение IRP, вернув из своей процедуры завершения код STATUS_MORE_PROCESSING_REQUIRED, и совершенно точно знает, что IRP ещё не завершен. Это он и делает, для того чтобы узнать код, с которым завершился отложенный IRP (см. исходный код функции I8xSendIrpSynchronously). Этот код драйвер извлекает из поля IRP.IoStatus.Status и из своей процедуры диспетчеризации будет возвращать именно его, а не первоначальный STATUS_PENDING. Затем драйвер i8042prt возобновляет завершение IRP, вызовом IoCompleteRequest.

Функция IoCompleteRequest продолжает завершать IRP с того места, где её прервали, т.е. с текущего блока стека, а текущим сейчас является блок стека драйвера i8042prt. В этом блоке стека нет флага SL_PENDING_RETURNED… Точнее говоря, его там быть не должно, но взгляните на исходный код функции I8xPnPComplete из 2000 DDK. Вы увидите там такие строки:

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2. <FONT color=»red»>     if  pIrp->PendingReturned  {

  3.          IoMarkIrpPending( pIrp )     // Four-F: Do not do this if you return

  4.                                       //         STATUS_MORE_PROCESSING_REQUIRED!

  5.      KeSetEvent( pEvent, 0, FALSE )   // Four-F: It’s not good to signal event unconditionaly.

  6.      return STATUS_MORE_PROCESSING_REQUIRED

В 2003 DDK эти строки уже закомментарены.

  1.      IN PDEVICE_OBJECT pDeviceObject,

  2.      // Since this completion routines sole purpose in life is to synchronize

  3.      // Irp, we know that unless something else happens that the IoCallDriver

  4.      // will unwind AFTER the we have complete this Irp.  Therefore we should

  5.      // NOT bubble up the pending bit.

  6.      // if  pIrp->PendingReturned  {

  7.      //     IoMarkIrpPending( pIrp )

  8.      KeSetEvent( pEvent, 0, FALSE )   // Four-F: It’s not good to signal event unconditionaly.

  9.      return STATUS_MORE_PROCESSING_REQUIRED

Две выделенные красным строки должны быть в процедуре завершения, но только если она не возвращает STATUS_MORE_PROCESSING_REQUIRED. Чуть позже увидим почему.

Допустим, мы используем I8xPnPComplete из 2000 DDK и в блоке стека драйвера i8042prt ошибочно присутствует флаг SL_PENDING_RETURNED. Видя это, IoCompleteRequest опять помещает в поле IRP.PendingReturned ненулевое значение. Если вы проанализируете дальнейший ход событий, то увидите, что ненулевое значение в поле IRP.PendingReturned дойдет до нашей процедуры завершения. Увидев не равное нулю поле IRP.PendingReturned, она решит, что нижестоящий драйвер вернул STATUS_PENDING и процедура диспетчеризации QueryPnpDeviceState ждет освобождения события, хотя на самом деле это не так. В данном случае, ничего ужасного не произойдет. Мы просто напрасно просигналим событие и всё. В каком-то другом случае, наверное, возможны более серьёзные последствия, т.к. драйвер будет основывать свои действия на неверных допущениях.

Мы уже несколько раз убеждались в том, что не стоит слепо верить документации DDK. Теперь оказывается, что и исходникам DDK нельзя верить?! Да, к сожалению, это так. Особенно много, скажем так, неоптимальных решений в исходниках 2000 DDK. Тексту этой статьи я тоже, кстати, советую не доверять :smile3: В конце концов, все мы люди, а людям, как известно…

Остальные возможные сценарии проанализируйте сами. Я только хочу ещё раз обратить особое внимание на поле IRP.PendingReturned. Во всех источниках, которые мне приходилось видеть, в том числе и в DDK, предназначение этого поля не совсем верно трактуется. Обычно говорится, что это поле сообщает диспетчеру в/в или вышестоящему драйверу о том, что нижестоящий драйвер отмечал IRP как ожидающий завершения (вызывал IoMarkIrpPending и возвращал из процедуры диспетчеризации STATUS_PENDING). Это верно. Также говорится, что якобы если какой-либо драйвер отмечал IRP как ожидающий завершения, то ненулевое значение этого поля так и сохраняется при завершении IRP до самого верха. А вот это уже не совсем так. Функция IoCompleteRequest (и мы с вами тоже должны будем принять в этом участие чуть ниже) действительно старается сохранить состояние этого поля, но только если она не встретит процедуру завершения. Зачем это нужно? В только что рассмотренном нами сценарии с драйвером unknown вместо acpi, обработка IRP до того как он опустился до драйвера i8042prt, была синхронной (проходила в контексте одного и того же потока). После того, как драйвер unknown вернул из процедуры диспетчеризации STATUS_PENDING, обработка IRP стала асинхронной (процедура завершения драйвера i8042prt вызывается в контексте случайного потока, а процедура диспетчеризации драйвера i8042prt ждет события в контексте первоначального потока). Дождавшись освобождения события, процедура диспетчеризации драйвера i8042prt продолжает обработку IRP в контексте первоначального потока, и обработка IRP опять становится синхронной. Вот тут собака и зарыта. Все драйверы находящиеся выше i8042prt вообще не должны знать, что драйвер unknown откладывал завершение IRP. Это проблема драйвера i8042prt и он сам её решил. Для всех вышестоящих драйверов всё как было синхронным, так и осталось. На участке между драйверами unknown и i8042prt поле IRP.PendingReturned будет содержать ненулевое значение, а на участке выше драйвера i8042prt оно обнулится, т.к. обработка IRP вновь стала синхронной и никто никого не ждет. Надеюсь, что понятно объяснил и нигде не ошибся :smile3:

Ну, хорошо, все процедуры завершения, которые мы видели до сих пор, возвращали STATUS_MORE_PROCESSING_REQUIRED. Но, как мы выяснили выше, это не единственно возможный код возврата. Этот код процедуры завершения возвращают в одном из трех случаев:

  1. Драйвер-создатель IRP вновь хочет увидеть своё чадо, для того чтобы его… скажем мягко, освободить (пример — наш драйвер) или повторно использовать;
  2. Драйвер хочет синхронизировать обработку IRP (пример — драйвер i8042prt);
  3. Т.к. процедура завершения может вызываться на повышенном IRQL, драйвер хочет сделать какую-то дополнительную обработку на PASSIVE_LEVEL в своей процедуре диспетчеризации.

Если же драйверу не нужна такая функциональность, но перехватить IRP на обратном пути всё же требуется (например, для того, чтобы посмотреть считанные с диска данные или код нажатой клавиши, что мы и будем делать в следующей статье) и всю обработку драйвер может сделать в процедуре завершения, даже на уровне DISPATCH_LEVEL, то тогда процедуре завершения не требуется прерывать завершение IRP и можно вернуть STATUS_SUCCESS или ContinueCompletion (что одно и то же).

В этом случае процедура завершения может выглядеть примерно так:

  1.  JustComplete proc uses esi edi ebx pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP, pContext:PVOID

  2.      .if [esi].IoStatus.Status == STATUS_SUCCESS

  3.          mov edi, [esi].AssociatedIrp.SystemBuffer

  4.          ; Что-то делаем с данными

  5.      .if [esi].PendingReturned

Самое важное здесь, в контексте нашего разговора, это вызов макроса IoMarkIrpPending в случае, если поле IRP.PendingReturned не равно нулю. Выше мы разобрались, что IoCompleteRequest как бы «перекладывает» флаг SL_PENDING_RETURNED из текущего блока стека в поле PendingReturned самого IRP и наоборот, если в блоке стека нет процедуры завершения, а поле PendingReturned не равно нулю, то вызывает макрос IoMarkIrpPending. Короче говоря, IoCompleteRequest пытается донести до первой встретившейся ей процедуры завершения, тот факт, что какой-то нижестоящий драйвер отмечал IRP как ожидающий завершения. Когда IoCompleteRequest находит процедуру завершения, то возлагает эту задачу на неё (см. исходный код IoCompleteRequest, а лучше блок-схему).

Представим, что вместо драйвера acpi у нас драйвер unknown и процедура завершения I8xPnPComplete драйвера i8042prt похожа на процедуру JustComplete, т.е. не сигналит событие и возвращает код STATUS_SUCCESS. Соответственно, процедура диспетчеризации драйвера i8042prt никакого события не инициализирует и не ждет, а просто возвращает тот код, который вернет IoCallDriver.

Драйвер unknown вызывает макрос IoMarkIrpPending, ставит IRP в очередь и возвращает STATUS_PENDING. Этот код «поднимается» до нашей процедуры диспетчеризации и мы начинаем ждать.

Некоторое время спустя, в результате прерывания или по другой причине, но в контексте какого-то другого потока, драйвер unknown извлекает IRP из очереди и завершает его вызовом IoCompleteRequest. IoCompleteRequest обнаруживает в блоке стека драйвера unknown флаг SL_PENDING_RETURNED, и поле IRP.PendingReturned принимает ненулевое значение. Обнаружив указатель на процедуру завершения JustComplete вышестоящего драйвера i8042prt, вызывает её (повторяю, мы заменили код на JustComplete). Сделав свои дела, процедура завершения JustComplete видит, что поле IRP.PendingReturned не равно нулю и, вызовом макроса IoMarkIrpPending, кладет в свой блок стека флаг SL_PENDING_RETURNED. Функция IoCompleteRequest делает то же самое в ветке else, но т.к. IoCompleteRequest встретила процедуру завершения, то эта задача перекладывается на неё. Т.к. процедура завершения I8xPnpComplete возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, функция IoCompleteRequest продолжает «подниматься» по блокам стека. Сделав дальнейший анализ, вы увидите, что информация о том, что IRP отмечался как ожидающий завершения в виде ненулевого значения в поле IRP.PendingReturned благополучно доходит до нашей процедуры завершения. Наша процедура завершения понимает, что процедура диспетчеризации QueryPnpDeviceState ждет на событии, сигналит его и всё заканчивается благополучно.

А если вы также проанализируете, что будет, если процедура завершения JustComplete забудет должным образом воспользоваться макросом IoMarkIrpPending, то придете к ещё одному правилу.

Правило:

Если процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то должна использовать (в любо месте) макрос IoMarkIrpPending таким образом.

if pIrp->PendingReturned {

IoMarkIrpPending( pIrp )

}

Ну и последнее. Т.к. у процедуры завершения нет другой возможности узнать, с каким кодом нижестоящий драйвер завершает IRP, кроме как обратиться к полю IRP.IoStatus.Status, мы запишем последнее правило.

Правило:

Перед вызовом IoCompleteRequest в процедуре диспетчеризации драйвер должен поместить в поле IRP.IoStatus.Status код с которым он завершает IRP и вернуть из процедуры диспетчеризации тот же самый код.

Начиная писать эту «бесконечную» статью я планировал ещё рассказать о том, какую логику использует диспетчер в/в при обработке IRP, т.к. чаще всего именно он является создателем IRP, но чувствую, что силы покидают меня. Если этот вопрос вас интересует, то рекомендую почитать статью «How Windows NT Handles I/O Completion» в IFS KIT или «The NT Insider» ( http://www.osronline.com/ ). К сожалению, исходного кода диспетчера в/в вы там не найдете, но общее представление получите.

Что вы должны делать и чего вы делать не должны

Подведем итог.

Правило 1:

Перед вызовом IoCompleteRequest в процедуре диспетчеризации драйвер должен поместить в поле IRP.IoStatus.Status код с которым он завершает IRP и вернуть из процедуры диспетчеризации тот же самый код.

Правило 2:

После вызова процедуры IoCompleteRequest обращаться к IRP нельзя. Возможно, IRP уже не существует.

Правило 3:

Завершать IRP с кодом STATUS_PENDING нельзя.

Правило 4:

Если из процедуры диспетчеризации драйвер возвращает код STATUS_PENDING, то перед этим должен вызвать IoMarkIrpPending. Если в процедуре диспетчеризации драйвер вызывает IoMarkIrpPending, то должен вернуть код STATUS_PENDING. Либо и то и другое, либо ни того, ни другого.

Правило 5:

Если у вас нет процедуры завершения или имеющаяся у вас процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то после вызова IoCallDriver обращаться к IRP нельзя. Возможно, IRP уже не существует.

Правило 6:

Если процедура завершения возвращает код отличный от STATUS_MORE_PROCESSING_REQUIRED, то должна использовать (в любо месте) макрос IoMarkIrpPending таким образом.

if pIrp->PendingReturned {

IoMarkIrpPending( pIrp )

}

Некоторые из этих правил, наверное, можно нарушить, если очень хорошо представлять себе, все детали механизма обработки IRP. Если такого представления нет, то лучше следовать им неукоснительно.

В следующий раз мы попробуем применить кое-какие полученные сегодня знания на практике.

Исходный код драйвера в архиве.

© Four-F

25.04.2019

Просмотров: 12551

Синий экран смерти MULTIPLE IRP COMPLETE REQUESTS с цифровым кодом 0x00000044 чаще всего появляется на старых сборках Windows XP и 2000 по причине повреждения системного драйвера, сбоев в работе жесткого диска, в результате работы вирусного приложения. Также ошибка 0x00000044 на более старшей версии Windows 7 и 8 возникает по причине конфликта операционной системы с файлами антивируса. Поэтому для решения синего экрана смерти MULTIPLE IRP COMPLETE REQUESTS придется провести ряд диагностических действий.

Читайте также: Решение ошибки 0x000000A5: ACPI BIOS ERROR при загрузке и установке Windows

Способы решения ошибки MULTIPLE IRP COMPLETE REQUESTS

Если на вашем компьютере возник синий экран смерти MULTIPLE IRP COMPLETE REQUESTS, то, в первую очередь, нужно проверить операционную систему на наличие вирусов. Для этого нужно иметь установленный антивирус с актуальными вирусными базами или скачать лечащую утилиту Dr.Web Curelt.

Если в результате проверки вирусы не были обнаружены, стоит на время отключить, а еще лучше, удалить антивирус. Как показал анализ различных форумов, ошибка 0x00000044 часто появлялась на ПК пользователей по вине антивируса. Поэтому на этапе диагностики системы лучше Защитник Windows отключить, а антивирус удалить.

Если BSOD все равно появляется, то виной могут быть драйвера. Определить, какой драйвер вызывает ошибку, можно как при анализе малого дампа памяти и самого синего экрана (иногда сбойный файл указывается на экране), так и вручную. Для самостоятельного определения сбойного драйвера нужно перейти в Диспетчер устройств, нажав «Win+R» и ввел «devmgmt.msc».

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

На следующем этапе кликаем на ссылку «Выполнить поиск драйверов на этом компьютере».

Указываем на ранее загруженный с сайта официального производителя драйвер. Запускаем обновление ПО. Как только установка будет закончена, нужно перезагрузить систему, чтобы изменения вступили в силу. Проверяем ПК на предмет ошибки.

ВАЖНО! Если ошибочного драйвера не было обнаружено, стоит запустить утилиту Driver Pack Solution для автоматического поиска и обновления устаревших драйверов. Можно установить последние имеющиеся обновления Windows.

Ошибка MULTIPLE IRP COMPLETE REQUESTS также может возникать в случае повреждения жесткого диска. Для начала накопитель нужно проверить утилитой чекдиск, которую стоит запустить через командную строку с правами Администратора, ввел chkdsk C: /f, где С: — буква диска с операционной системой, /f – параметр команды для исправления ошибок.

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

Для глубокой проверки накопителя рекомендуем скачать программу Victoria HDD, которая проверить HDD или SSD на наличие битых секторов.

На некоторых форумах данную ошибку связывают с проблемами оперативной памяти. Исправить её можно с помощью программы MemTest86, которая разработана для диагностики и исправления ошибок с ОЗУ. Программу нужно скачать и записать как образ на диск или флешку. Далее нужно загрузиться в BIOS и выставить приоритетность загрузки со съемного носителя или CD-ROM. После этого запускаем проверку памяти. Желательно выполнить несколько проходов данной программой, так как за один проход софт может не обнаружить неполадок.

Также для уверенности можно почистить контакты модулей ОЗУ и переставить модули местами. Возможно, модули просто сбоили. Однако это уже сигнал к тому, что планки нужно будет заменить.

Важно отметить, что на Windows 7 и Windows 8 такая неполадка также появлялась при подключении накопителя к портам USB 2.0 и USB 3.0. Поэтому, если вы подключили к ПК какое-то оборудование через данные порты, то стоит их перепроверить и подключить заново. Возможно, вы подключили устройство USB 3.0 к порту USB 2.0.

Во всех остальных случаях, при возникновении неполадки нужно либо откатить систему до более раннего состояния, либо вовсе переустановить Windows.

Как убрать « MULTIPLE_IRP_COMPLETE_REQUESTS» ( 0x00000044)?

На компьютере или ноутбуке под управлением Windows появился «синий экран смерти»? После появления сообщения « MULTIPLE_IRP_COMPLETE_REQUESTS» ( 0x00000044) система перезагружается? Ищите как исправить 0x00000044: « MULTIPLE_IRP_COMPLETE_REQUESTS»?

Как просмотреть информацию об ошибках, исправить ошибки в Windows 10, 8 или 7

Причины появления ошибки

Актуально для ОС: Windows 10, Windows 8.1, Windows Server 2012, Windows 8, Windows Home Server 2011, Windows 7 (Seven), Windows Small Business Server, Windows Server 2008, Windows Home Server, Windows Vista, Windows XP, Windows 2000, Windows NT.

Вот несколько способов исправления ошибки « MULTIPLE_IRP_COMPLETE_REQUESTS»:

Восстановите удаленные файлы

Часто появление BSoD ошибок приводит к утери важных файлов. Блокировка файлов компьютерными вирусами, ошибки в работе программ для резервного копирования, повреждение или перезапись системных файлов, ошибки файловой системы или наличие битых секторов диска – это не далеко полный список причин удаления данных.

Исправление соответствующих ошибок восстановит работоспособность устройства, но не всегда восстановит повреждённые или утерянные файлы. Не всегда имеется возможность устранить ту или иную ошибку без форматирования диска или чистой установки операционной системы, что также может повлечь за собой утерю файлов.

Загрузите бесплатно и просканируйте ваше устройство с помощью Hetman Partition Recovery. Ознакомьтесь с возможностями программы и пошаговой инструкцией.

Запустите компьютер в «безопасном режиме»

Если ошибка «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044) возникает в момент загрузки Windows и блокирует любую возможность работы с системой, попробуйте включить компьютер в «безопасном режиме». Этот режим предназначен для диагностики операционной системы (далее ОС), но функционал Windows при этом сильно ограничен. «Безопасный режим» следует использовать только если работа с системой заблокирована.

Чтобы запустить безопасный режим сделайте следующее:

Как загрузить Windows в безопасном режиме

Обновите драйвер через Диспетчер устройств

Вы установили новое аппаратное обеспечение на компьютере? Возможно вы начали использовать новое USB-устройство с вашим компьютером. Это могло привести к ошибке «MULTIPLE_IRP_COMPLETE_REQUESTS». Если вы установили драйвер устройства используя диск, который поставляется вместе с ним, или использовали драйвер не c официального сайта Microsoft, то причина в нем. Вам придется обновить драйвер устройства, чтобы устранить эту проблему.

Вы можете сделать это вручную в диспетчере устройств Windows, для того выполните следующие инструкции:

Перезагрузите компьютер после установки драйвера.

Используйте sfc /scannow для проверки всех файлов системы

Повреждение или перезапись системных файлов может привести к ошибке «MULTIPLE_IRP_COMPLETE_REQUESTS». Команда Sfc находит поврежденные системные файлы Windows и заменяет их.

Этот процесс может занять несколько минут.

Как восстановить системные файлы Windows 10

Проверьте диск с Windows на наличие ошибок командой chkdsk c: /f

Возможно к синему экрану с «MULTIPLE_IRP_COMPLETE_REQUESTS» привела ошибка файловой системы или наличие битых секторов диска. Команда CHKDSK проверяет диск на наличие ошибок файловой системы и битых секторов. Использование параметра /f заставит программу автоматически исправлять найденные на диске ошибки, а параметр /r позволяет найти и «исправить» проблемные сектора диска. Для запуска следуйте инструкциям:

Дождитесь окончания процесса и перезагрузите компьютер.

Используйте режим совместимости со старой версией Windows

BSOD с кодом MULTIPLE_IRP_COMPLETE_REQUESTS» может вызывать «устаревшее» программное обеспечение. Если ошибка появляется после запуска программы, то использование режима совместимости Windows избавит от появления ошибки. Для устранения проблемы следуйте следующим инструкциям:

Запуск программы в режиме совместимости Windows 10, 8, 7

Отключите лишние программы из автозагрузки Windows

Программное обеспечение, вызывающее «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044), может быть прописано в автозагрузку Windows и ошибка будет появляться сразу после запуска системы без вашего участия. Удалить программы из Автозагрузки можно с помощью Диспетчера задач.

Обратитесь в поддержку Microsoft

Microsoft предлагает несколько решений удаления ошибки «голубого экрана». «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044) можно убрать с помощью Центра обновления или обратившись в Поддержку Windows.

Установите последние обновления системы

С обновлениями Windows дополняет базу драйверов, исправляет ошибки и уязвимости в системе безопасности. Загрузите последние обновления, что бы избавиться от ошибки «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044).

Запустить Центр обновления Windows можно следующим образом:

Рекомендуется настроить автоматическую загрузку и установку обновлений операционной системы с помощью меню Дополнительные параметры.

Чтобы включить автоматическое обновление системы необходимо запустить Центр обновления Windows:

Запустите проверку системы на вирусы

«Синий экран смерти» с ошибкой «MULTIPLE_IRP_COMPLETE_REQUESTS» 0x00000044 может вызывать компьютерный вирус, заразивший систему Windows.

Для проверки системы на наличие вирусов запустите установленную на компьютере антивирусную программу.

Современные антивирусы позволяют проверить как жесткие диски, так и оперативную память. Выполните полную проверку системы.

Выполните проверку оперативной памяти

Неполадки с памятью могут привести к ошибкам, потере информации или прекращению работы компьютера.

Прежде чем проверять оперативную память, отключите её из разъёма на материнской плате компьютера и повторно вставьте в него. Иногда ошибка MULTIPLE_IRP_COMPLETE_REQUESTS» вызвана неправильно или не плотно вставленной в разъём планкой оперативной памяти, или засорением контактов разъёма.

Если предложенные действия не привели к положительному результату, исправность оперативной памяти можно проверить средствами операционной системы, с помощью средства проверки памяти Windows.

Запустить средство проверки памяти Windows можно двумя способами:

Для начала проверки выберите один из предлагаемых вариантов, проверка оперативной памяти в соответствии с которым будет запущена после перезагрузки компьютера.

Если в результате проверки будут определены ошибки, исправить которые не представляется возможным, то такую память необходимо заменить (замене подлежит модуль памяти с неполадками).

Выполните «чистую» установку Windows

Если не один из перечисленных методов не помог избавиться от MULTIPLE_IRP_COMPLETE_REQUESTS», попробуйте переустановить Windows. Для того чтобы выполнить чистую установку Windows необходимо создать установочный диск или другой носитель с которого планируется осуществление установки операционной системы.

Загрузите компьютер из установочного диска. Для этого может понадобиться изменить устройство загрузки в BIOS или UEFI для более современных компьютеров.

Следуя пунктам меню установщика укажите диск, на который вы хотите установить Windows. Для этого понадобится отформатировать его. Если вас не устраивает количество или размер локальных дисков, их можно полностью удалить и заново перераспределить.

Помните, что форматирование, удаление или перераспределения дисков удалит все файлы с жесткого диска.

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

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

После загрузки рабочего стола чистую установку Windows можно считать законченной.

Примечание. Прежде чем приступить к выполнению чистой установки Windows заранее побеспокойтесь о наличии драйверов ко всем установленным устройствам и сохранности ваших файлов. Загрузите и сохраните драйвера на отдельный носитель информации, а для важных данных создайте резервную копию.

ПОЛНЫЙ ОБЗОР: MULTIPLE_IRP_COMPLETE_REQUESTS в Windows 10

Windows Это сложная операционная система: она обслуживает миллионы ПК по всему миру и работает на тысячах различных аппаратных комбинаций, что может быть весьма интересно для инженеров Microsoft.

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

В крайнем случае это синий экран: когда операционная система не знает, что еще делать, она решает просто аварийно завершить работу и сообщить пользователю о проблеме.

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

Мы исправили ошибки и предоставили методы для исправления ошибки NTFS_File_System в Windows 10 в прошлом, поэтому вы можете ожидать, что эти решения будут работать на вас. Если этого не произойдет, возможно, вам придется искать лучшее решение.

Как я могу исправить ошибку MULTIPLE_IRP_COMPLETE_REQUESTS?

MULTIPLE_IRP_COMPLETE_REQUESTS – ошибка синего экрана и может быть довольно неприятной. Говоря об ошибках такого рода, вот некоторые похожие проблемы, о которых сообщили пользователи:

Решение 1. Проверьте свой антивирус

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

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

Если отключение антивируса не решило проблему, следующим шагом будет полное его удаление. Имейте это в виду Windows 10 га Windows Защищайтесь как антивирус по умолчанию, поэтому даже если вы удалите антивирус, у вас все равно будет некоторая базовая защита.

После удаления антивируса проверьте, не устранена ли проблема. Если нет, возможно, вам следует подумать о переходе на другое антивирусное решение. На рынке есть много отличных антивирусных инструментов, но если вам нужен надежный антивирус, который не мешает вашей системе, вы должны рассмотреть возможность использования BullGuard,

Решение 2. Используйте средство проверки системных файлов

По словам пользователей, иногда это может привести к повреждению системных файлов. Однако вы можете решить проблему, просто выполнив сканирование SFC. Чтобы сделать это, просто выполните следующие действия:

После завершения сканирования проверьте, сохраняется ли проблема. Если проблема сохраняется, или если вы не смогли запустить сканирование SFC, мы рекомендуем вам проверить сканирование DISM. Чтобы сделать это, просто выполните следующие действия:

После завершения двух сканирований проверьте, сохраняется ли проблема.

Решение 3 – Запустите CHKDSK, чтобы исправить это

Использование CHKDSK для исправления этой ошибки – это еще один способ, поскольку вы можете легко исправить многие виды ошибок, включая ошибки, такие как KERNEL_DATA_INPAGE_ERROR в Windows 10. Давайте посмотрим, как выполнить эту команду, чтобы исправить эту конкретную ошибку.

Теперь вам просто нужно перезагрузить компьютер и позволить ему сканировать системный диск. Этот процесс может занять около 20-30 минут, но как только вы закончите, проблема должна быть полностью решена.

Решение 4 – Обновите ваши драйверы

Другой причиной MULTIPLE_IRP_COMPLETE_REQUESTS могут быть его драйверы. Иногда эта проблема может быть вызвана устаревшими драйверами, и лучший способ решить эту проблему – обновить все основные драйверы на вашем компьютере.

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

Тем не менее, вы можете использовать такие инструменты, как Обновление драйвера TweakBit обновить все ваши драйверы всего за несколько кликов. Этот инструмент автоматически обновит драйверы для вас, поэтому вам не придется искать их вручную.

После обновления драйверов проверьте, сохраняется ли проблема.

Решение 5 – Удалить проблемное программное обеспечение

Иногда сторонние приложения могут мешать работе вашей системы и вызывать ошибку MULTIPLE_IRP_COMPLETE_REQUESTS. По словам пользователей, такие приложения, как LogMeIn HamachiAsRock и EasyTune могут вызвать эту проблему.

Если вы используете какое-либо из этих приложений, мы рекомендуем удалить их и проверить, решает ли это проблему. Хотя вы можете удалить эти приложения с помощью приложения «Настройки», мы настоятельно рекомендуем использовать такое программное обеспечение, как: Revo деинсталлятор удалить их

Программное обеспечение удаления предназначено для полного удаления всех файлов и записей реестра, связанных с приложением, которое вы пытаетесь удалить. В результате приложение будет полностью удалено, и не останется доступных файлов, которые могли бы помешать работе вашей системы.

Имейте в виду, что другие приложения также могут вызывать эту проблему, поэтому обязательно проведите детальный осмотр вашей системы.

Решение 6 – Сброс BIOS на значения по умолчанию

В некоторых случаях ваши настройки BIOS могут вызвать ошибку MULTIPLE_IRP_COMPLETE_REQUESTS. Обычно это вызвано вашими настройками, но вы можете решить проблему, просто сбросив настройки BIOS на значения по умолчанию.

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

Решение 7 – Обновите свой BIOS

Другой способ исправить ошибку MULTIPLE_IRP_COMPLETE_REQUESTS – обновить BIOS. Прежде чем мы начнем, мы должны упомянуть, что обновление BIOS может быть рискованной процедурой, поэтому, если вы решите обновить его, имейте в виду, что вы делаете это на свой страх и риск.

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

Обязательно внимательно следуйте инструкциям в руководстве по эксплуатации, чтобы избежать нанесения непоправимого ущерба вашей системе. После обновления BIOS проверьте, устранена ли проблема.

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

Часто задаваемые вопросы: Узнайте больше о Ошибка MULTIPLE_IRP_COMPLETE_REQUESTS

Ошибка MULTIPLE_IRP_COMPLETE_REQUESTS – это ошибка BSoD, которая может быть вызвана различными причинами, включая неисправные драйверы, неисправный жесткий диск, настройки антивируса или другие проблемы программного обеспечения.

Чтобы устранить эту проблему, выполните следующие действия: проверьте антивирус, используйте средство проверки системных файлов, запустите CHKDSK, обновите драйверы, удалите проблемное программное обеспечение, сбросьте настройки BIOS до значений по умолчанию и, наконец, обновите BIOS.

Краткий ответ – да. В основном, ошибки BSoD вызваны проблемами с вашим оборудованием, поэтому да, ошибка BSoD также может быть вызвана неисправной материнской платой. Прочитайте код ошибки на черном экране и укажите этот конкретный код ошибки или сообщение, чтобы устранить проблему.

От редактора Note: Этот пост был первоначально опубликован в октябре 2018 года и с тех пор был обновлен и обновлен в апреле 2020 года для обеспечения свежести, точности и полноты.

Источники:

Https://byr1.ru/fix-multiple-irp-complete-requests-bsod-error

Https://tehnografi. com/%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-%D0%BE%D0%B1%D0%B7%D0%BE%D1%80-multiple_irp_complete_requests-%D0%B2-windows-10/

Драйверы фильтров (Filter Drivers). Занимая более высокий логический уровень, чем функциональные драйверы, добавляют функциональность или изменяют поведение устройства либо другого драйвера. Этот тип драйверов не обязателен для нормальной работы устройства.

Драйверы фильтров, в свою очередь, подразделяются на:

  • Драйверы фильтров шин (Bus Filter Drivers).
  • Низкоуровневые драйверы фильтров (Lower-Level Filter Drivers).
  • Высокоуровневые драйверы фильтров (Upper-Level Filter Drivers).
  • Объект «физическое устройство» (Physical Device Object, PDO) — Создается драйвером шины по заданию диспетчера PnP, когда драйвер шины, перечисляя устройства на своей шине, сообщает о наличии какого-либо устройства. PDO представляет физический интерфейс устройства.
  • Объект «функциональное устройство» (Functional Device Object, FDO) — Создается функциональным драйвером, который загружается диспетчером PnP для управления обнаруженным устройством. FDO представляет логический интерфейс устройства.
  • Необязательная группа объектов «устройство-фильтр» (Filter Device Object, FiDO). Одна группа таких объектов размещается между PDO и FDO (эти объекты создаются драйверами фильтров шин), вторая — между первой группой FiDO и FDO (эти объекты создаются низкоуровневыми драйверами фильтров), а третья — над FDO (эти объекты создаются высокоуровневыми драйверами фильтров).

Дерево устройств

  • Низкоуровневые драйверы фильтров, указанные в параметрах LowerFilters ветвей реестра Enum и Class.
  • Функциональный драйвер, заданный в параметре Service ветви реестра Enum.
  • Высокоуровневые драйверы фильтров, указанные в параметрах UpperFilters ветвей реестра Enum и Class.

Стек объектов «устройство»

  • объект «физическое устройство», созданный драйвером шины ACPI.
  • объект «функциональное устройство», созданный функциональным драйвером i8042prt.
  • объект «устройство-фильтр», созданный высокоуровневым драйвером фильтра nmfilter (NTICE Support File).
  • объект «устройство-фильтр», созданный высокоуровневым драйвером фильтра kbdclass.

Язык с за три минуты

Мне придется использовать исходные коды некоторых системных функций, т.к. по-настоящему разобраться с обработкой IRP без анализа исходного кода, по-моему, невозможно. Эти фрагменты, конечно, не будут истинным кодом операционной системы и будут урезаны, порой весьма значительно. Также опущена вся обработка ошибок: проверки указателей, входных данных и возвращаемых функциями значений, убраны обработчики SEH. Оставлена только самая суть. Для упрощения анализа кода я буду использовать c-подобный псевдоязык (почти чистый с). Вполне допускаю, что вы можете и не знать этого языка, т.к. мы всё же занимаемся разработкой драйверов на ассемблере. Поэтому тезисно приведу базовые конструкции, без которых не обойтись.

На ассемблере место под инициализированную переменную отводится так:

Как убрать MULTIPLE_IRP_COMPLETE_REQUESTS (0x00000044)?

Как убрать « MULTIPLE_IRP_COMPLETE_REQUESTS» ( 0x00000044)?

Причины появления ошибки

Актуально для ОС: Windows 10, Windows 8.1, Windows Server 2012, Windows 8, Windows Home Server 2011, Windows 7 (Seven), Windows Small Business Server, Windows Server 2008, Windows Home Server, Windows Vista, Windows XP, Windows 2000, Windows NT.

Вот несколько способов исправления ошибки « MULTIPLE_IRP_COMPLETE_REQUESTS»:

Восстановите удаленные файлы

Часто появление BSoD ошибок приводит к утери важных файлов. Блокировка файлов компьютерными вирусами, ошибки в работе программ для резервного копирования, повреждение или перезапись системных файлов, ошибки файловой системы или наличие битых секторов диска – это не далеко полный список причин удаления данных.

Исправление соответствующих ошибок восстановит работоспособность устройства, но не всегда восстановит повреждённые или утерянные файлы. Не всегда имеется возможность устранить ту или иную ошибку без форматирования диска или чистой установки операционной системы, что также может повлечь за собой утерю файлов.

Загрузите бесплатно и просканируйте ваше устройство с помощью Hetman Partition Recovery. Ознакомьтесь с возможностями программы и пошаговой инструкцией.

Чтобы запустить безопасный режим сделайте следующее:

Как загрузить Windows в безопасном режиме

Обновите драйвер через Диспетчер устройств

Вы установили новое аппаратное обеспечение на компьютере? Возможно вы начали использовать новое USB-устройство с вашим компьютером. Это могло привести к ошибке «MULTIPLE_IRP_COMPLETE_REQUESTS». Если вы установили драйвер устройства используя диск, который поставляется вместе с ним, или использовали драйвер не c официального сайта Microsoft, то причина в нем. Вам придется обновить драйвер устройства, чтобы устранить эту проблему.

Вы можете сделать это вручную в диспетчере устройств Windows, для того выполните следующие инструкции:

Перезагрузите компьютер после установки драйвера.

Используйте sfc /scannow для проверки всех файлов системы

Повреждение или перезапись системных файлов может привести к ошибке «MULTIPLE_IRP_COMPLETE_REQUESTS». Команда Sfc находит поврежденные системные файлы Windows и заменяет их.

Этот процесс может занять несколько минут.

Как восстановить системные файлы Windows 10

Проверьте диск с Windows на наличие ошибок командой chkdsk c: /f

Дождитесь окончания процесса и перезагрузите компьютер.

Используйте режим совместимости со старой версией Windows

Запуск программы в режиме совместимости Windows 10, 8, 7

Отключите лишние программы из автозагрузки Windows

Программное обеспечение, вызывающее «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044), может быть прописано в автозагрузку Windows и ошибка будет появляться сразу после запуска системы без вашего участия. Удалить программы из Автозагрузки можно с помощью Диспетчера задач.

Обратитесь в поддержку Microsoft

Установите последние обновления системы

С обновлениями Windows дополняет базу драйверов, исправляет ошибки и уязвимости в системе безопасности. Загрузите последние обновления, что бы избавиться от ошибки «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044).

Запустить Центр обновления Windows можно следующим образом:

Рекомендуется настроить автоматическую загрузку и установку обновлений операционной системы с помощью меню Дополнительные параметры.

Чтобы включить автоматическое обновление системы необходимо запустить Центр обновления Windows:

Запустите проверку системы на вирусы

Для проверки системы на наличие вирусов запустите установленную на компьютере антивирусную программу.

Современные антивирусы позволяют проверить как жесткие диски, так и оперативную память. Выполните полную проверку системы.

Выполните проверку оперативной памяти

Неполадки с памятью могут привести к ошибкам, потере информации или прекращению работы компьютера.

Прежде чем проверять оперативную память, отключите её из разъёма на материнской плате компьютера и повторно вставьте в него. Иногда ошибка MULTIPLE_IRP_COMPLETE_REQUESTS» вызвана неправильно или не плотно вставленной в разъём планкой оперативной памяти, или засорением контактов разъёма.

Если предложенные действия не привели к положительному результату, исправность оперативной памяти можно проверить средствами операционной системы, с помощью средства проверки памяти Windows.

Запустить средство проверки памяти Windows можно двумя способами:

Для начала проверки выберите один из предлагаемых вариантов, проверка оперативной памяти в соответствии с которым будет запущена после перезагрузки компьютера.

Если не один из перечисленных методов не помог избавиться от MULTIPLE_IRP_COMPLETE_REQUESTS», попробуйте переустановить Windows. Для того чтобы выполнить чистую установку Windows необходимо создать установочный диск или другой носитель с которого планируется осуществление установки операционной системы.

Загрузите компьютер из установочного диска. Для этого может понадобиться изменить устройство загрузки в BIOS или UEFI для более современных компьютеров.

Следуя пунктам меню установщика укажите диск, на который вы хотите установить Windows. Для этого понадобится отформатировать его. Если вас не устраивает количество или размер локальных дисков, их можно полностью удалить и заново перераспределить.

Помните, что форматирование, удаление или перераспределения дисков удалит все файлы с жесткого диска.

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

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

После загрузки рабочего стола чистую установку Windows можно считать законченной.

Примечание. Прежде чем приступить к выполнению чистой установки Windows заранее побеспокойтесь о наличии драйверов ко всем установленным устройствам и сохранности ваших файлов. Загрузите и сохраните драйвера на отдельный носитель информации, а для важных данных создайте резервную копию.

ПОЛНЫЙ ОБЗОР: MULTIPLE_IRP_COMPLETE_REQUESTS в Windows 10

Windows Это сложная операционная система: она обслуживает миллионы ПК по всему миру и работает на тысячах различных аппаратных комбинаций, что может быть весьма интересно для инженеров Microsoft.

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

В крайнем случае это синий экран: когда операционная система не знает, что еще делать, она решает просто аварийно завершить работу и сообщить пользователю о проблеме.

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

Мы исправили ошибки и предоставили методы для исправления ошибки NTFS_File_System в Windows 10 в прошлом, поэтому вы можете ожидать, что эти решения будут работать на вас. Если этого не произойдет, возможно, вам придется искать лучшее решение.

Решение 1. Проверьте свой антивирус

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

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

Если отключение антивируса не решило проблему, следующим шагом будет полное его удаление. Имейте это в виду Windows 10 га Windows Защищайтесь как антивирус по умолчанию, поэтому даже если вы удалите антивирус, у вас все равно будет некоторая базовая защита.

После удаления антивируса проверьте, не устранена ли проблема. Если нет, возможно, вам следует подумать о переходе на другое антивирусное решение. На рынке есть много отличных антивирусных инструментов, но если вам нужен надежный антивирус, который не мешает вашей системе, вы должны рассмотреть возможность использования BullGuard,

Решение 2. Используйте средство проверки системных файлов

По словам пользователей, иногда это может привести к повреждению системных файлов. Однако вы можете решить проблему, просто выполнив сканирование SFC. Чтобы сделать это, просто выполните следующие действия:

После завершения сканирования проверьте, сохраняется ли проблема. Если проблема сохраняется, или если вы не смогли запустить сканирование SFC, мы рекомендуем вам проверить сканирование DISM. Чтобы сделать это, просто выполните следующие действия:

После завершения двух сканирований проверьте, сохраняется ли проблема.

Теперь вам просто нужно перезагрузить компьютер и позволить ему сканировать системный диск. Этот процесс может занять около 20-30 минут, но как только вы закончите, проблема должна быть полностью решена.

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

Тем не менее, вы можете использовать такие инструменты, как Обновление драйвера TweakBit обновить все ваши драйверы всего за несколько кликов. Этот инструмент автоматически обновит драйверы для вас, поэтому вам не придется искать их вручную.

После обновления драйверов проверьте, сохраняется ли проблема.

Иногда сторонние приложения могут мешать работе вашей системы и вызывать ошибку MULTIPLE_IRP_COMPLETE_REQUESTS. По словам пользователей, такие приложения, как LogMeIn HamachiAsRock и EasyTune могут вызвать эту проблему.

Если вы используете какое-либо из этих приложений, мы рекомендуем удалить их и проверить, решает ли это проблему. Хотя вы можете удалить эти приложения с помощью приложения «Настройки», мы настоятельно рекомендуем использовать такое программное обеспечение, как: Revo деинсталлятор удалить их

Программное обеспечение удаления предназначено для полного удаления всех файлов и записей реестра, связанных с приложением, которое вы пытаетесь удалить. В результате приложение будет полностью удалено, и не останется доступных файлов, которые могли бы помешать работе вашей системы.

Имейте в виду, что другие приложения также могут вызывать эту проблему, поэтому обязательно проведите детальный осмотр вашей системы.

В некоторых случаях ваши настройки BIOS могут вызвать ошибку MULTIPLE_IRP_COMPLETE_REQUESTS. Обычно это вызвано вашими настройками, но вы можете решить проблему, просто сбросив настройки BIOS на значения по умолчанию.

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

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

Обязательно внимательно следуйте инструкциям в руководстве по эксплуатации, чтобы избежать нанесения непоправимого ущерба вашей системе. После обновления BIOS проверьте, устранена ли проблема.

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

Часто задаваемые вопросы: Узнайте больше о Ошибка MULTIPLE_IRP_COMPLETE_REQUESTS

Чтобы устранить эту проблему, выполните следующие действия: проверьте антивирус, используйте средство проверки системных файлов, запустите CHKDSK, обновите драйверы, удалите проблемное программное обеспечение, сбросьте настройки BIOS до значений по умолчанию и, наконец, обновите BIOS.

От редактора Note: Этот пост был первоначально опубликован в октябре 2018 года и с тех пор был обновлен и обновлен в апреле 2020 года для обеспечения свежести, точности и полноты.

MULTIPLE_IRP_COMPLETE_REQUESTS (44)
A driver has requested that an IRP be completed (IoCompleteRequest()), but
the packet has already been completed. This is a tough bug to find because
the easiest case, a driver actually attempted to complete its own packet
twice, is generally not what happened. Rather, two separate drivers each
believe that they own the packet, and each attempts to complete it. The
first actually works, and the second fails. Tracking down which drivers
in the system actually did this is difficult, generally because the trails
of the first driver have been covered by the second. However, the driver
stack for the current request can be found by examining the DeviceObject
fields in each of the stack locations.
Arguments:
Arg1: fffffa805dfcecc0, Address of the IRP
Arg2: 0000000000000ec0
Arg3: 0000000000000000
Arg4: 0000000000000000

SYSTEM_PRODUCT_NAME: Super Server

SYSTEM_SKU: Default string

BIOS_VENDOR: American Megatrends Inc.

FOLLOWUP_IP:
rdpdr!CVCChannel::OnClose+146
fffff880`083a6302 488b8f08010000 mov rcx,qword ptr [rdi+108h]

CPU_MICROCODE: 6,4f,1,0 (F,M,S,R) SIG: B00002A’00000000 (cache) B00002A’00000000 (init)

ANALYSIS_VERSION: 10.0.17763.132 amd64fre

LAST_CONTROL_TRANSFER: from fffff800022de406 to fffff800022aaac0

STACK_TEXT:
fffff880`0b2ec6b8 fffff800`022de406 : 00000000`00000044 fffffa80`5dfcecc0 00000000`00000ec0 00000000`00000000 : nt!KeBugCheckEx
fffff880`0b2ec6c0 fffff880`083a6302 : fffffa80`41da4040 00000000`00000001 fffffa80`2f9b52c8 fffffa80`2f9b51f0 : nt! ?? ::FNODOBFM::`string’+0x20f06
fffff880`0b2ec7b0 fffff880`083a3e92 : fffffa80`41da4040 00000000`00000000 00000000`00000000 fffffa80`2f7ab701 : rdpdr!CVCChannel::OnClose+0x146
fffff880`0b2ec7e0 fffff880`083a371d : fffffa80`4395c370 fffffa80`251acc50 00000000`00000000 fffffa80`28581920 : rdpdr!CVCSession::Disconnect+0x146
fffff880`0b2ec830 fffff880`083a33f3 : fffffa80`4395c370 fffffa80`251ac9d0 fffffa80`41da4010 fffffa80`4395c370 : rdpdr!CDynVC::NotifySessionDisconnected+0x71
fffff880`0b2ec860 fffff880`083a314b : fffffa80`4395c370 fffffa80`4395c370 00000000`00000001 fffff800`0224b33c : rdpdr!CDynVC::Close+0x3b
fffff880`0b2ec890 fffff880`08392a19 : fffffa80`4395c370 00000000`00000000 fffffa80`19a704b0 fffff800`022458a6 : rdpdr!DYNVC_Dispatch+0x107
fffff880`0b2ec8c0 fffff800`025141ae : 00000000`00033852 fffffa80`2559a420 00000000`00000001 00000000`00000000 : rdpdr!DrPeekDispatch+0x61
fffff880`0b2ec910 fffff800`0224a3d4 : fffff8a0`02a71570 fffffa80`253d0b00 fffffa80`19a704b0 00000000`00000000 : nt!IopDeleteFile+0x11e
fffff880`0b2ec9a0 fffff800`02642720 : fffffa80`253d0b00 fffffa80`43b55b50 00000000`00000e44 fffffa80`43b55b50 : nt!ObfDereferenceObject+0xd4
fffff880`0b2eca00 fffff800`0250f6e4 : 00000000`00000e44 fffffa80`2559a3f0 fffffa80`253d0b00 fffff8a0`02a21010 : nt!ObpCloseHandleTableEntry+0x280
fffff880`0b2eca90 fffff800`022b8b53 : fffffa80`43b55b50 fffff880`0b2ecb60 00000000`00a2e9b8 00000000`03204500 : nt!ObpCloseHandle+0x94
fffff880`0b2ecae0 00000000`77238e0a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`00a2e448 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x77238e0a

здраствуйте! я вот создаю драйвер перехватчик для NtTerminateProcess. вопрос:
как при каждом перехвате передавать строковые данные в приложение в usermode, а потом ждать ответа от приложения?

Добавлено через 1 минуту
знаю , что возможно запросить данные из драйвера в приложение с помощью «DeviceIoControl», но мне нужно чтобы драйвер сам передавал данные в приложение, без запроса.

__________________
Помощь в написании контрольных, курсовых и дипломных работ здесь

Общение между приложением и драйвером
Проблемы с общением между приложением и драйвером. Драйвер принимает строку, а ответные данные .

Обмен данными между веб-приложением (Flask) и графическим приложением (PyQt5) (Python3.7)
С помощью каких библиотек (модулей) и каким образом можно реализовать сервер на базе графического.

Связь c драйвером
Hello all. Собсно подскажите api ( Create, Write, Read ) для этого дела, если существуют. Если.

Наиболее простая и безопасная модель общения с драйвером:

1) Для начала нужно определить управляющие коды (I/O Control Code, IOCTL).
Информация здесь:

Управляющий код формируется из четырех частей:

device type — тип устройства, обычно FILE_DEVICE_UNKNOWN;

function code — значение из диапазона 0x800 до 0x1000;

i/o transfer type — способ передачи, METHOD_BUFFERED, METHOD_IN_DIRECT,
METHOD_OUT_DIRECT или METHOD_NEITHER;

required access — права, необходимые для выполнения операции:
FILE_READ_ACCESS, FILE_WRITE_ACCESS или FILE_ANY_ACCESS.

Пример определения IOCTL:

METHOD_XXXX определяет способ передачи данных, от этого зависит то,
как драйвер должен обрабатывать буфер с данными, переданный из программы.

Я не буду здесь расписывать все подробности, т.к. все это есть
на MSDN и в соответствующих документах. Например:

2) Драйвер должен реализовать обработчики IRP_MJ_CREATE (открытие устройства),
IRP_MJ_DEVICE_CONTROL (обработка DeviceIoControl), IRP_MJ_CLEANUP и
IRP_MJ_CLOSE (закрытие хэндла и очистка ресурсов).

3) Приложение открывает устройство, созданное драйвером — CreateFile,
причем с флагом FILE_FLAG_OVERLAPPED (асинхронный ввод-вывод), а затем с
помощью DeviceIoControl и управляющего кода отправляет ему команду.
Драйвер ставит IRP в очередь, возвращая код STATUS_PENDING.
Далее приложение может ждать на событии (см. структуру OVERLAPPED).
В некоторый момент времени, когда драйверу нужно передать в программу
какие-то данные, он вытаскивает IRP из очереди, записывает данные в
его буфер и завершает IRP (IoCompleteRequest). После этого приложение
ставит в очередь еще один IRP.

Я рекомендую не заниматься самодеятельностью (потому что правильная
синхронизация отмены IRP — штука непростая), а сразу для реализации
очереди использовать Cancel-Safe IRP Queues:

Читайте также:

  • Какая версия скайпа самая лучшая для виндовс 7
  • Как дать права apache на папку в ubuntu
  • Как раздать интернет с windows phone на компьютер через usb
  • Установил виндовс на мак как вернуть
  • Где находятся сохранения растения против зомби на windows 10

by Madalina Dinita

Madalina has been a Windows fan ever since she got her hands on her first Windows XP computer. She is interested in all things technology, especially emerging technologies… read more


Updated on December 3, 2021

  • This MULTIPLE_IRP_COMPLETE_REQUESTS error in Windows 10 can be caused due to bad drivers or a faulty hard disk, so diagnosing it isn’t simple. 
  • In some cases, your antivirus can cause the blue screen MULTIPLE_IRP_COMPLETE_REQUESTS error to appear.
  • You can fix this issue by using a dedicated, third-party solution specialized in BSoD issues.
  • Checking your antivirus may also fix the problem as it might cause this error.

How to fix MULTIPLE_IRP_COMPLETE_REQUESTS in Windows 10

Windows 10 is a complex operating system – serving millions of PCs all around the world and running on thousands of different hardware combinations.

Unfortunately, this complexity also means there are bound to be errors due to various reasons that can be hard to diagnose.

One of such problems is the MULTIPLE_IRP_COMPLETE_REQUESTS Blue Screen of Death error.

A BSoD happens as the last resort – when the OS does not know what else to do, it decides to simply crash and inform the user of the problem.

This error can be caused due to various reasons, it could be anything from bad drivers to a faulty hard disk, so diagnosing it isn’t simple.

We can, however, give a shot at the most common solutions and see if they work.

The stop code MULTIPLE_IRP_COMPLETE_REQUESTS can be quite problematic. Speaking of these kinds of errors, here are some similar issues that users reported:

  • MULTIPLE_IRP_COMPLETE_REQUESTS Blue Screen of Death – This is a blue screen error, and if you encounter it, you should be able to fix it by using one of our solutions.
  • MULTIPLE_IRP_COMPLETE_REQUESTS ntoskrnl.exe, classpnp.sys, wdf01000.sys, hal.dll, usbport.sys, acpi.sys, ntfs.sys, nvlddmkm.sys – Sometimes a specific file can cause this error to occur. To fix this issue, you need to do a bit of research and find out how is this file related to your hardware or software. Once you find the problematic hardware or software, the issue should be resolved.
  • MULTIPLE_IRP_COMPLETE_REQUESTS Windows 10, Windows Server 2003, Windows Server 2008 r2 – This error can occur on other versions of Windows, but even if you don’t use Windows 10, you should be able to apply some of our solutions to it.

How can I fix MULTIPLE_IRP_COMPLETE_REQUESTS error?

1. Use a dedicated software to fix the BSoD

You need to address any BSoD seriously and immediately as they can also lead to hardware failure. Don’t worry because we offer a lot of good solutions below and we will get to the bottom of this.

However, before trying the solutions below, you should try a quicker and easier one that involves using a third-party specialized solution to fix BSoD issues. The tool recommended below is light and fast.

Restoro repair

Restoro is powered by an online database that features the latest functional system files for Windows 10 PCs, allowing you to easily replace any elements that can cause a BSoD error.

This software can also help you by creating a restore point before starting the repairs, enabling you to easily restore the previous version of your system if something goes wrong.

This is how you can fix registry errors using Restoro:

  1. Download and install Restoro.
  2. Launch the application.
  3. Wait for the software to identify stability issues and possible corrupted files.
  4. Press Start Repair.
  5. Restart your PC for all the changes to take effect.

As soon as the fixing process is completed, your PC should work without problems, and you will not have to worry about BSoD errors or slow response times ever again.

⇒ Get Restoro


Disclaimer: This program needs to be upgraded from the free version in order to perform some specific actions.


2. Check your antivirus

In some cases, your antivirus can cause MULTIPLE_IRP_COMPLETE_REQUESTS error to appear, and in order to fix it, it’s advised to check your antivirus settings.

Sometimes certain features of your antivirus can cause this problem to appear, and in order to fix the issue, you just need to disable those features.

If disabling these features doesn’t help, you might have to completely disable your antivirus.

In some cases, you might even have to completely remove your antivirus and check if that solves the problem.

If disabling the antivirus didn’t fix your issue, your next step would be to completely remove your antivirus.

Keep in mind that Windows 10 has Windows Defender as its default antivirus, so even if you remove your antivirus, you’ll still have some form of basic protection.

Once you remove your antivirus, check if the problem is still there. If not, you should perhaps consider switching to a different antivirus solution.

3. Update your drivers

Another cause for MULTIPLE_IRP_COMPLETE_REQUESTS can be your drivers. Sometimes this issue can be caused by outdated drivers, and a way to solve the problem is to update the main drivers on your PC.

This usually includes your graphics card, network, and chipset drivers. To do so, just visit your hardware manufacturer’s website and download the latest drivers for your device.

Doing this manually can be a bit tedious since you need to download each driver manually.

However, you can use a dedicated tool to update all your drivers with just a couple of clicks in just a few seconds.

This kind of tool will automatically update your drivers for you, so you won’t have to search for them manually. Once your drivers are up to date, check if the problem is still there.

4. Perform SFC and DISM scans

4.1 Perform a SFC scan

  1. Start Command Prompt as an administrator. To do that, just type cmd in Windows search and click on Run as administrator below the results.
  2. Now run the sfc /scannow command.
    Multiple_irp_complete_requests blue screen of death
  3. The System File Checker scan will start. This scan can take about 15 minutes, so don’t interfere with it.

After the scan is completed, check if the problem is still there. If the problem is still there, or if you weren’t able to run the SFC scan at all, we suggest you try DISM scan.

Some PC issues are hard to tackle, especially when it comes to corrupted repositories or missing Windows files. If you are having troubles fixing an error, your system may be partially broken.
We recommend installing Restoro, a tool that will scan your machine and identify what the fault is.
Click here to download and start repairing.

4.2 Perform a DISM scan

  1. Open Command Prompt as an administrator.
  2. Now enter the following command and press Enter to run it: DISM /Online /Cleanup-Image /RestoreHealth
    Multiple_irp_complete_requests classpnp.sys
  3. DISM scan will now start. Keep in mind that this scan can take about 20 minutes, so don’t interfere with it.

According to users, sometimes corrupted system files can cause this issue to appear. However, you can fix the issue simply by performing an SFC scan. To do that, just follow the steps above.

After both scans are finished, check if the problem is still there.

5. Run CHKDSK to fix this problem

  1. Open Command Prompt as administrator. To see how to do that properly, check our previous solution.
  2. When Command Prompt opens, enter the following command and press Enter (Replace the X with the letter that represents your system drive. In most cases that would be C): chkdsk /f :X
    Multiple_irp_complete_requests Windows 10
  3. You’ll be asked to schedule a scan, so press Y to do so.

Using CHKDSK to fix this error is another way as it can easily fix many types of errors including errors like KERNEL_DATA_INPAGE_ERROR in Windows 10.

After running the command, you have to restart your PC and let it scan your system drive. This process can take about 20-30 minutes, but after it’s finished the problem should be completely resolved.

6. Remove the problematic software

Sometimes third-party applications can interfere with your system and cause MULTIPLE_IRP_COMPLETE_REQUESTS error to appear.

According to users, applications such as LogMeIn Hamachi, AsRock, and EasyTune software can cause this issue to appear.

If you’re using any of these applications, we advise you to remove them and check if that solves the problem.

Although you can remove these applications using the Settings app, we strongly recommend using dedicated uninstaller software to remove them.

Uninstaller software is designed to completely remove all files and registry entries associated with the application you’re trying to remove.

As a result, the application will be removed in its entirety and there won’t be any leftover files available to interfere with your system.

Keep in mind that other applications can also cause this issue, so be sure to perform a detailed inspection of your system.

7. Reset your BIOS to the default

In some cases, your BIOS settings can cause a MULTIPLE_IRP_COMPLETE_REQUESTS error to appear.

This is usually caused by your settings, but you can fix the issue simply by resetting BIOS to the default.

This is quite simple to do, and you just need to enter BIOS and choose the option to load the default settings.

This procedure might differ depending on the version of BIOS you’re using.

So in order to see how to properly enter and reset BIOS to default, we suggest that you check your motherboard manual for detailed instructions.

8. Update your BIOS

Another way to fix the MULTIPLE_IRP_COMPLETE_REQUESTS error is to update your BIOS.

Before we start, we have to mention that BIOS update can be a risky procedure, so if you decide to update it, keep in mind that you’re doing that at your own risk.

We already wrote a short guide on how to flash your BIOS, but since this is just a general guide, we advise you to check your motherboard manual for detailed instructions on how to update your BIOS.

Be sure to carefully follow the instructions in the instruction manual in order to avoid causing permanent damage to your system. Once your BIOS is up to date, check if the problem is resolved.

These solutions should at the least help you figure out what is exactly going wrong with your Windows, and in some cases also be able to fix these issues.

We have solved errors and provided methods to fix NTFS_File_System Error on Windows 10 in the past so you can expect these solutions to work for you.

We hope that our guide helped you fix the MULTIPLE_IRP_COMPLETE_REQUESTS ntoskrnl.exe issue and if you have any suggestions, leave them in a comment below.

newsletter icon

Newsletter

25.04.2019

Просмотров: 12984

Синий экран смерти MULTIPLE IRP COMPLETE REQUESTS с цифровым кодом 0x00000044 чаще всего появляется на старых сборках Windows XP и 2000 по причине повреждения системного драйвера, сбоев в работе жесткого диска, в результате работы вирусного приложения. Также ошибка 0x00000044 на более старшей версии Windows 7 и 8 возникает по причине конфликта операционной системы с файлами антивируса. Поэтому для решения синего экрана смерти MULTIPLE IRP COMPLETE REQUESTS придется провести ряд диагностических действий.

Читайте также: Решение ошибки 0x000000A5: ACPI BIOS ERROR при загрузке и установке Windows

Способы решения ошибки MULTIPLE IRP COMPLETE REQUESTS

Если на вашем компьютере возник синий экран смерти MULTIPLE IRP COMPLETE REQUESTS, то, в первую очередь, нужно проверить операционную систему на наличие вирусов. Для этого нужно иметь установленный антивирус с актуальными вирусными базами или скачать лечащую утилиту Dr.Web Curelt.

Если в результате проверки вирусы не были обнаружены, стоит на время отключить, а еще лучше, удалить антивирус. Как показал анализ различных форумов, ошибка 0x00000044 часто появлялась на ПК пользователей по вине антивируса. Поэтому на этапе диагностики системы лучше Защитник Windows отключить, а антивирус удалить.

Если BSOD все равно появляется, то виной могут быть драйвера. Определить, какой драйвер вызывает ошибку, можно как при анализе малого дампа памяти и самого синего экрана (иногда сбойный файл указывается на экране), так и вручную. Для самостоятельного определения сбойного драйвера нужно перейти в Диспетчер устройств, нажав «Win+R» и ввел «devmgmt.msc».

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

На следующем этапе кликаем на ссылку «Выполнить поиск драйверов на этом компьютере».

Указываем на ранее загруженный с сайта официального производителя драйвер. Запускаем обновление ПО. Как только установка будет закончена, нужно перезагрузить систему, чтобы изменения вступили в силу. Проверяем ПК на предмет ошибки.

ВАЖНО! Если ошибочного драйвера не было обнаружено, стоит запустить утилиту Driver Pack Solution для автоматического поиска и обновления устаревших драйверов. Можно установить последние имеющиеся обновления Windows.

Ошибка MULTIPLE IRP COMPLETE REQUESTS также может возникать в случае повреждения жесткого диска. Для начала накопитель нужно проверить утилитой чекдиск, которую стоит запустить через командную строку с правами Администратора, ввел chkdsk C: /f, где С: — буква диска с операционной системой, /f – параметр команды для исправления ошибок.

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

Для глубокой проверки накопителя рекомендуем скачать программу Victoria HDD, которая проверить HDD или SSD на наличие битых секторов.

На некоторых форумах данную ошибку связывают с проблемами оперативной памяти. Исправить её можно с помощью программы MemTest86, которая разработана для диагностики и исправления ошибок с ОЗУ. Программу нужно скачать и записать как образ на диск или флешку. Далее нужно загрузиться в BIOS и выставить приоритетность загрузки со съемного носителя или CD-ROM. После этого запускаем проверку памяти. Желательно выполнить несколько проходов данной программой, так как за один проход софт может не обнаружить неполадок.

Также для уверенности можно почистить контакты модулей ОЗУ и переставить модули местами. Возможно, модули просто сбоили. Однако это уже сигнал к тому, что планки нужно будет заменить.

Важно отметить, что на Windows 7 и Windows 8 такая неполадка также появлялась при подключении накопителя к портам USB 2.0 и USB 3.0. Поэтому, если вы подключили к ПК какое-то оборудование через данные порты, то стоит их перепроверить и подключить заново. Возможно, вы подключили устройство USB 3.0 к порту USB 2.0.

Во всех остальных случаях, при возникновении неполадки нужно либо откатить систему до более раннего состояния, либо вовсе переустановить Windows.

Как убрать « MULTIPLE_IRP_COMPLETE_REQUESTS» ( 0x00000044)?

На компьютере или ноутбуке под управлением Windows появился «синий экран смерти»? После появления сообщения « MULTIPLE_IRP_COMPLETE_REQUESTS» ( 0x00000044) система перезагружается? Ищите как исправить 0x00000044: « MULTIPLE_IRP_COMPLETE_REQUESTS»?

Как просмотреть информацию об ошибках, исправить ошибки в Windows 10, 8 или 7

Причины появления ошибки

Актуально для ОС: Windows 10, Windows 8.1, Windows Server 2012, Windows 8, Windows Home Server 2011, Windows 7 (Seven), Windows Small Business Server, Windows Server 2008, Windows Home Server, Windows Vista, Windows XP, Windows 2000, Windows NT.

Вот несколько способов исправления ошибки « MULTIPLE_IRP_COMPLETE_REQUESTS»:

Восстановите удаленные файлы

Часто появление BSoD ошибок приводит к утери важных файлов. Блокировка файлов компьютерными вирусами, ошибки в работе программ для резервного копирования, повреждение или перезапись системных файлов, ошибки файловой системы или наличие битых секторов диска – это не далеко полный список причин удаления данных.

Исправление соответствующих ошибок восстановит работоспособность устройства, но не всегда восстановит повреждённые или утерянные файлы. Не всегда имеется возможность устранить ту или иную ошибку без форматирования диска или чистой установки операционной системы, что также может повлечь за собой утерю файлов.

Загрузите бесплатно и просканируйте ваше устройство с помощью Hetman Partition Recovery. Ознакомьтесь с возможностями программы и пошаговой инструкцией.

Запустите компьютер в «безопасном режиме»

Если ошибка «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044) возникает в момент загрузки Windows и блокирует любую возможность работы с системой, попробуйте включить компьютер в «безопасном режиме». Этот режим предназначен для диагностики операционной системы (далее ОС), но функционал Windows при этом сильно ограничен. «Безопасный режим» следует использовать только если работа с системой заблокирована.

Чтобы запустить безопасный режим сделайте следующее:

Как загрузить Windows в безопасном режиме

Обновите драйвер через Диспетчер устройств

Вы установили новое аппаратное обеспечение на компьютере? Возможно вы начали использовать новое USB-устройство с вашим компьютером. Это могло привести к ошибке «MULTIPLE_IRP_COMPLETE_REQUESTS». Если вы установили драйвер устройства используя диск, который поставляется вместе с ним, или использовали драйвер не c официального сайта Microsoft, то причина в нем. Вам придется обновить драйвер устройства, чтобы устранить эту проблему.

Вы можете сделать это вручную в диспетчере устройств Windows, для того выполните следующие инструкции:

Перезагрузите компьютер после установки драйвера.

Используйте sfc /scannow для проверки всех файлов системы

Повреждение или перезапись системных файлов может привести к ошибке «MULTIPLE_IRP_COMPLETE_REQUESTS». Команда Sfc находит поврежденные системные файлы Windows и заменяет их.

Этот процесс может занять несколько минут.

Как восстановить системные файлы Windows 10

Проверьте диск с Windows на наличие ошибок командой chkdsk c: /f

Возможно к синему экрану с «MULTIPLE_IRP_COMPLETE_REQUESTS» привела ошибка файловой системы или наличие битых секторов диска. Команда CHKDSK проверяет диск на наличие ошибок файловой системы и битых секторов. Использование параметра /f заставит программу автоматически исправлять найденные на диске ошибки, а параметр /r позволяет найти и «исправить» проблемные сектора диска. Для запуска следуйте инструкциям:

Дождитесь окончания процесса и перезагрузите компьютер.

Используйте режим совместимости со старой версией Windows

BSOD с кодом MULTIPLE_IRP_COMPLETE_REQUESTS» может вызывать «устаревшее» программное обеспечение. Если ошибка появляется после запуска программы, то использование режима совместимости Windows избавит от появления ошибки. Для устранения проблемы следуйте следующим инструкциям:

Запуск программы в режиме совместимости Windows 10, 8, 7

Отключите лишние программы из автозагрузки Windows

Программное обеспечение, вызывающее «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044), может быть прописано в автозагрузку Windows и ошибка будет появляться сразу после запуска системы без вашего участия. Удалить программы из Автозагрузки можно с помощью Диспетчера задач.

Обратитесь в поддержку Microsoft

Microsoft предлагает несколько решений удаления ошибки «голубого экрана». «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044) можно убрать с помощью Центра обновления или обратившись в Поддержку Windows.

Установите последние обновления системы

С обновлениями Windows дополняет базу драйверов, исправляет ошибки и уязвимости в системе безопасности. Загрузите последние обновления, что бы избавиться от ошибки «MULTIPLE_IRP_COMPLETE_REQUESTS» (0x00000044).

Запустить Центр обновления Windows можно следующим образом:

Рекомендуется настроить автоматическую загрузку и установку обновлений операционной системы с помощью меню Дополнительные параметры.

Чтобы включить автоматическое обновление системы необходимо запустить Центр обновления Windows:

Запустите проверку системы на вирусы

«Синий экран смерти» с ошибкой «MULTIPLE_IRP_COMPLETE_REQUESTS» 0x00000044 может вызывать компьютерный вирус, заразивший систему Windows.

Для проверки системы на наличие вирусов запустите установленную на компьютере антивирусную программу.

Современные антивирусы позволяют проверить как жесткие диски, так и оперативную память. Выполните полную проверку системы.

Выполните проверку оперативной памяти

Неполадки с памятью могут привести к ошибкам, потере информации или прекращению работы компьютера.

Прежде чем проверять оперативную память, отключите её из разъёма на материнской плате компьютера и повторно вставьте в него. Иногда ошибка MULTIPLE_IRP_COMPLETE_REQUESTS» вызвана неправильно или не плотно вставленной в разъём планкой оперативной памяти, или засорением контактов разъёма.

Если предложенные действия не привели к положительному результату, исправность оперативной памяти можно проверить средствами операционной системы, с помощью средства проверки памяти Windows.

Запустить средство проверки памяти Windows можно двумя способами:

Для начала проверки выберите один из предлагаемых вариантов, проверка оперативной памяти в соответствии с которым будет запущена после перезагрузки компьютера.

Если в результате проверки будут определены ошибки, исправить которые не представляется возможным, то такую память необходимо заменить (замене подлежит модуль памяти с неполадками).

Выполните «чистую» установку Windows

Если не один из перечисленных методов не помог избавиться от MULTIPLE_IRP_COMPLETE_REQUESTS», попробуйте переустановить Windows. Для того чтобы выполнить чистую установку Windows необходимо создать установочный диск или другой носитель с которого планируется осуществление установки операционной системы.

Загрузите компьютер из установочного диска. Для этого может понадобиться изменить устройство загрузки в BIOS или UEFI для более современных компьютеров.

Следуя пунктам меню установщика укажите диск, на который вы хотите установить Windows. Для этого понадобится отформатировать его. Если вас не устраивает количество или размер локальных дисков, их можно полностью удалить и заново перераспределить.

Помните, что форматирование, удаление или перераспределения дисков удалит все файлы с жесткого диска.

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

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

После загрузки рабочего стола чистую установку Windows можно считать законченной.

Примечание. Прежде чем приступить к выполнению чистой установки Windows заранее побеспокойтесь о наличии драйверов ко всем установленным устройствам и сохранности ваших файлов. Загрузите и сохраните драйвера на отдельный носитель информации, а для важных данных создайте резервную копию.

ПОЛНЫЙ ОБЗОР: MULTIPLE_IRP_COMPLETE_REQUESTS в Windows 10

Windows Это сложная операционная система: она обслуживает миллионы ПК по всему миру и работает на тысячах различных аппаратных комбинаций, что может быть весьма интересно для инженеров Microsoft.

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

В крайнем случае это синий экран: когда операционная система не знает, что еще делать, она решает просто аварийно завершить работу и сообщить пользователю о проблеме.

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

Мы исправили ошибки и предоставили методы для исправления ошибки NTFS_File_System в Windows 10 в прошлом, поэтому вы можете ожидать, что эти решения будут работать на вас. Если этого не произойдет, возможно, вам придется искать лучшее решение.

Как я могу исправить ошибку MULTIPLE_IRP_COMPLETE_REQUESTS?

MULTIPLE_IRP_COMPLETE_REQUESTS – ошибка синего экрана и может быть довольно неприятной. Говоря об ошибках такого рода, вот некоторые похожие проблемы, о которых сообщили пользователи:

Решение 1. Проверьте свой антивирус

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

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

Если отключение антивируса не решило проблему, следующим шагом будет полное его удаление. Имейте это в виду Windows 10 га Windows Защищайтесь как антивирус по умолчанию, поэтому даже если вы удалите антивирус, у вас все равно будет некоторая базовая защита.

После удаления антивируса проверьте, не устранена ли проблема. Если нет, возможно, вам следует подумать о переходе на другое антивирусное решение. На рынке есть много отличных антивирусных инструментов, но если вам нужен надежный антивирус, который не мешает вашей системе, вы должны рассмотреть возможность использования BullGuard,

Решение 2. Используйте средство проверки системных файлов

По словам пользователей, иногда это может привести к повреждению системных файлов. Однако вы можете решить проблему, просто выполнив сканирование SFC. Чтобы сделать это, просто выполните следующие действия:

После завершения сканирования проверьте, сохраняется ли проблема. Если проблема сохраняется, или если вы не смогли запустить сканирование SFC, мы рекомендуем вам проверить сканирование DISM. Чтобы сделать это, просто выполните следующие действия:

После завершения двух сканирований проверьте, сохраняется ли проблема.

Решение 3 – Запустите CHKDSK, чтобы исправить это

Использование CHKDSK для исправления этой ошибки – это еще один способ, поскольку вы можете легко исправить многие виды ошибок, включая ошибки, такие как KERNEL_DATA_INPAGE_ERROR в Windows 10. Давайте посмотрим, как выполнить эту команду, чтобы исправить эту конкретную ошибку.

Теперь вам просто нужно перезагрузить компьютер и позволить ему сканировать системный диск. Этот процесс может занять около 20-30 минут, но как только вы закончите, проблема должна быть полностью решена.

Решение 4 – Обновите ваши драйверы

Другой причиной MULTIPLE_IRP_COMPLETE_REQUESTS могут быть его драйверы. Иногда эта проблема может быть вызвана устаревшими драйверами, и лучший способ решить эту проблему – обновить все основные драйверы на вашем компьютере.

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

Тем не менее, вы можете использовать такие инструменты, как Обновление драйвера TweakBit обновить все ваши драйверы всего за несколько кликов. Этот инструмент автоматически обновит драйверы для вас, поэтому вам не придется искать их вручную.

После обновления драйверов проверьте, сохраняется ли проблема.

Решение 5 – Удалить проблемное программное обеспечение

Иногда сторонние приложения могут мешать работе вашей системы и вызывать ошибку MULTIPLE_IRP_COMPLETE_REQUESTS. По словам пользователей, такие приложения, как LogMeIn HamachiAsRock и EasyTune могут вызвать эту проблему.

Если вы используете какое-либо из этих приложений, мы рекомендуем удалить их и проверить, решает ли это проблему. Хотя вы можете удалить эти приложения с помощью приложения «Настройки», мы настоятельно рекомендуем использовать такое программное обеспечение, как: Revo деинсталлятор удалить их

Программное обеспечение удаления предназначено для полного удаления всех файлов и записей реестра, связанных с приложением, которое вы пытаетесь удалить. В результате приложение будет полностью удалено, и не останется доступных файлов, которые могли бы помешать работе вашей системы.

Имейте в виду, что другие приложения также могут вызывать эту проблему, поэтому обязательно проведите детальный осмотр вашей системы.

Решение 6 – Сброс BIOS на значения по умолчанию

В некоторых случаях ваши настройки BIOS могут вызвать ошибку MULTIPLE_IRP_COMPLETE_REQUESTS. Обычно это вызвано вашими настройками, но вы можете решить проблему, просто сбросив настройки BIOS на значения по умолчанию.

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

Решение 7 – Обновите свой BIOS

Другой способ исправить ошибку MULTIPLE_IRP_COMPLETE_REQUESTS – обновить BIOS. Прежде чем мы начнем, мы должны упомянуть, что обновление BIOS может быть рискованной процедурой, поэтому, если вы решите обновить его, имейте в виду, что вы делаете это на свой страх и риск.

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

Обязательно внимательно следуйте инструкциям в руководстве по эксплуатации, чтобы избежать нанесения непоправимого ущерба вашей системе. После обновления BIOS проверьте, устранена ли проблема.

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

Часто задаваемые вопросы: Узнайте больше о Ошибка MULTIPLE_IRP_COMPLETE_REQUESTS

Ошибка MULTIPLE_IRP_COMPLETE_REQUESTS – это ошибка BSoD, которая может быть вызвана различными причинами, включая неисправные драйверы, неисправный жесткий диск, настройки антивируса или другие проблемы программного обеспечения.

Чтобы устранить эту проблему, выполните следующие действия: проверьте антивирус, используйте средство проверки системных файлов, запустите CHKDSK, обновите драйверы, удалите проблемное программное обеспечение, сбросьте настройки BIOS до значений по умолчанию и, наконец, обновите BIOS.

Краткий ответ – да. В основном, ошибки BSoD вызваны проблемами с вашим оборудованием, поэтому да, ошибка BSoD также может быть вызвана неисправной материнской платой. Прочитайте код ошибки на черном экране и укажите этот конкретный код ошибки или сообщение, чтобы устранить проблему.

От редактора Note: Этот пост был первоначально опубликован в октябре 2018 года и с тех пор был обновлен и обновлен в апреле 2020 года для обеспечения свежести, точности и полноты.

Источники:

Https://byr1.ru/fix-multiple-irp-complete-requests-bsod-error

Https://tehnografi. com/%D0%BF%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-%D0%BE%D0%B1%D0%B7%D0%BE%D1%80-multiple_irp_complete_requests-%D0%B2-windows-10/

Обновлено Июнь 2023: перестаньте получать сообщения об ошибках и замедлите работу вашей системы с помощью нашего инструмента оптимизации. Получить сейчас в эту ссылку

  1. Скачайте и установите инструмент для ремонта здесь.
  2. Пусть он просканирует ваш компьютер.
  3. Затем инструмент почини свой компьютер.

Откройте приложение «Настройки» и перейдите в раздел «Обновление и безопасность».
Выберите из левого меню. BSOD
Выберите на правой панели и нажмите Запустить средство устранения неполадок.
Следуйте инструкциям на экране, чтобы завершить устранение неполадок.

Запишите код остановки синего экрана Windows.
Попробуйте конкретное исправление для вашего кода ошибки синего экрана.
Проверьте последние изменения компьютера. за
Проверьте наличие обновлений и драйверов для Windows.
Выполните восстановление системы.
Сканирование на наличие вредоносных программ.
Протестируйте оборудование вашего компьютера.
Запустите сканирование SFC.

Зайдите в Мой компьютер, щелкните по нему правой кнопкой мыши.
Зайдите в Свойства.
Щелкните вкладку «Дополнительно».
Перейдите в раздел «Загрузка и восстановление» и нажмите кнопку «Настройки».
Перейдите в раздел «Системные ошибки» и снимите флажок «Автоматический перезапуск».
Сохраните настройки, нажав OK.

Проверка ошибок NO_MORE_IRP_STACK_LOCATIONS имеет значение 0x00000035. Эта проверка ошибок происходит, когда в пакете IoCallDriver больше нет мест в стеке памяти.

NO_MORE_IRP_STACK_LOCATIONS ошибка

Эта проблема возникает из-за того, что драйвер Mup.sys предполагает, что одновременно может работать не более трех драйверов фильтров файловой системы. Драйвер Mup.sys обрабатывает запросы ввода-вывода для файлов распределенной файловой системы (DFS). Если имеется четыре или более драйверов фильтра файловой системы, буфер пакетов запроса ввода-вывода (IRP), предварительно назначенный Mup.sys, будет переполнен. В этом случае вы получите сообщение об ошибке, описанное в разделе «Симптомы».

Советы по исправлению ошибок NO_MORE_IRP_STACK_LOCATIONS на синем экране (0x00000035):

Чтобы исправить ошибку BSoD, вы должны использовать советы и рекомендации, приведенные ниже, все рассмотренные методы эффективны и способны легко решить эту проблему.

Тестирование ОЗУ с помощью средства диагностики памяти Windows

Диагностика памяти Windows

Window Memory Diagnostics Tool — это интегрированное программное обеспечение Microsoft для проверки памяти. Это интегрированное приложение позволяет тестировать системную память на наличие всех видов ошибок в Windows Vista, Windows 7, Windows 8 или 8.1 и Windows 10.

Шаг 1. Сначала откройте диалоговое окно, затем одновременно нажмите Window + R, чтобы открыть команду запуска. В окне запуска введите «Mdsched» и нажмите Enter.

Обновление за июнь 2023 г .:

Теперь вы можете предотвратить проблемы с ПК с помощью этого инструмента, например, защитить вас от потери файлов и вредоносных программ. Кроме того, это отличный способ оптимизировать ваш компьютер для достижения максимальной производительности. Программа с легкостью исправляет типичные ошибки, которые могут возникнуть в системах Windows — нет необходимости часами искать и устранять неполадки, если у вас под рукой есть идеальное решение:

  • Шаг 1: Скачать PC Repair & Optimizer Tool (Windows 10, 8, 7, XP, Vista — Microsoft Gold Certified).
  • Шаг 2: Нажмите «Начать сканирование”, Чтобы найти проблемы реестра Windows, которые могут вызывать проблемы с ПК.
  • Шаг 3: Нажмите «Починить все», Чтобы исправить все проблемы.

скачать

Шаг 2: Вы увидите окно средства диагностики окна памяти с двумя вариантами выбора.

Шаг 3: выберите тот, который соответствует вашим потребностям, и вскоре после запуска Windows вы увидите ваш экран следующим образом.

Держите Windows 10 и драйверы в актуальном состоянии.

окна-10-обновление

Чтобы избежать ошибок BSoD, важно Обновление для Windows 10 с последними патчами. Microsoft постоянно работает над улучшением Windows 10, и многие из этих исправлений предлагают новые функции и обновления безопасности. Кроме того, многие исправления устраняют проблемы несовместимости оборудования и программного обеспечения, и поскольку многие ошибки BSoD вызваны несовместимостью оборудования или программного обеспечения, вы можете понять, почему важно регулярно использовать Центр обновления Windows.

Перезагрузите компьютер только с важным оборудованием

Windows 10 зависает при перезагрузке

Попробуйте загрузить вашу систему только с необходимым оборудованием. Один из способов исправить ошибки BSOD — это использовать минимум оборудования при запуске компьютера. Если вы заметили, что ошибка исчезла, вероятно, проблема возникла на одном из удаленных вами аппаратных устройств.

Примечание. В общем, все, что вам нужно для загрузки компьютера — это оперативная память, процессор, материнская плата, клавиатура, основной жесткий диск, монитор и видеокарта.

Ошибка NO_MORE_IRP_STACK_LOCATIONS может вызвать много проблем, но эту ошибку обычно можно исправить, удалив антивирусное программное обеспечение. Если это решение не работает для вас, вы можете попробовать любое другое решение этой статьи.

ed_moyes

CCNA, веб-разработчик, ПК для устранения неполадок

Я компьютерный энтузиаст и практикующий ИТ-специалист. У меня за плечами многолетний опыт работы в области компьютерного программирования, устранения неисправностей и ремонта оборудования. Я специализируюсь на веб-разработке и дизайне баз данных. У меня также есть сертификат CCNA для проектирования сетей и устранения неполадок.

Сообщение Просмотров: 390

  • Ошибка iron not less or equal windows 10
  • Ошибка irlq not less or equal
  • Ошибка iptables no chain target match by that name
  • Ошибка ipm модуля intelligent power module
  • Ошибка ipay не отвечает