Мое почтение всем. Пробую драйвера-фильтры, есть несколько вопросов. 1. Насколько я понимаю, IoAttachDevice возвращает указатель DEVICE_OBJECT, который был самым верхним до вызова и стал вторым (lower device) относительно добавленного с помощью ф-ии устройства. Так ли это? 2. Что надо передавать в IoDetachDevice? В примере в книге написано, что в нее надо передавать DEVICE_OBJECT, полученный из IoAttachDevice. Но у меня сомнения. Я сделал так, но после выгрузки драйвера, при нажатии кнопки на клавиатуре I/O manager все равно вызывает completion routine выгруженного драйвера (это видно в отладчике). Вероятно, в нее надо передавать DEVICE_OBJECT самого верхнего устройства, адрес которого находится в DRIVER_OBJECT:eviceObject? 3. Попробовал передать DRIVER_OBJECT:eviceObject, получил IRQL_NO_LESS_OR_EQUAL. IRQL == 2. Откуда? Я не поднимал IRQL. Дамп: Код (Text): 0: kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* IRQL_NOT_LESS_OR_EQUAL (a) An attempt was made to access a pageable (or completely invalid) address at an interrupt request level (IRQL) that is too high. This is usually caused by drivers using improper addresses. If a kernel debugger is available get the stack backtrace. Arguments: Arg1: 000000b0, memory referenced Arg2: 00000002, IRQL Arg3: 00000000, bitfield : bit 0 : value 0 = read operation, 1 = write operation bit 3 : value 0 = not an execute operation, 1 = execute operation (only on chips which support this level of status) Arg4: 804ef13c, address which referenced memory Debugging Details: ------------------ READ_ADDRESS: 000000b0 CURRENT_IRQL: 2 FAULTING_IP: nt!IoDetachDevice+2a 804ef13c 8b80b0000000 mov eax,dword ptr [eax+0B0h] DEFAULT_BUCKET_ID: DRIVER_FAULT BUGCHECK_STR: 0xA PROCESS_NAME: System TRAP_FRAME: f7a48cd0 -- (.trap 0xfffffffff7a48cd0) ErrCode = 00000000 eax=00000000 ebx=00000000 ecx=80552582 edx=00000000 esi=864f81d0 edi=eeaf0b84 eip=804ef13c esp=f7a48d44 ebp=f7a48d50 iopl=0 nv up ei pl zr na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246 nt!IoDetachDevice+0x2a: 804ef13c 8b80b0000000 mov eax,dword ptr [eax+0B0h] ds:0023:000000b0=???????? Resetting default scope LAST_CONTROL_TRANSFER: from 804f8afd to 8052a5d8 STACK_TEXT: f7a48884 804f8afd 00000003 f7a48be0 00000000 nt!RtlpBreakWithStatusInstruction f7a488d0 804f96e8 00000003 000000b0 804ef13c nt!KiBugCheckDebugBreak+0x19 f7a48cb0 805436d0 0000000a 000000b0 00000002 nt!KeBugCheck2+0x574 f7a48cb0 804ef13c 0000000a 000000b0 00000002 nt!KiTrap0E+0x238 f7a48d50 f7ccb4b5 864f81d0 eeaf0b84 f7a48d7c nt!IoDetachDevice+0x2a f7a48d60 8058014d 8641f458 eeaf0b84 8056375c kbdfilter!Unload+0x29 [d:\src\c\kbdfilter\src\core\core.c @ 73] f7a48d7c 80537757 eeaf0b84 00000000 865c28b8 nt!IopLoadUnloadDriver+0x19 f7a48dac 805ce794 eeaf0b84 00000000 00000000 nt!ExpWorkerThread+0xef f7a48ddc 805450ce 80537668 00000001 00000000 nt!PspSystemThreadStartup+0x34 00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16 STACK_COMMAND: kb FOLLOWUP_IP: kbdfilter!Unload+29 [d:\src\c\kbdfilter\src\core\core.c @ 73] f7ccb4b5 ff7604 push dword ptr [esi+4] FAULTING_SOURCE_CODE: 69: { 70: DBG_TRACE("Unload", "Unloading"); 71: 72: IoDetachDevice(DriverObject ->DeviceObject); > 73: IoDeleteDevice(DriverObject ->DeviceObject); 74: } 75: 76: NTSTATUS IrpMjRead(PDEVICE_OBJECT DeviceObject, PIRP Irp) 77: { 78: IoCopyCurrentIrpStackLocationToNext(Irp); SYMBOL_STACK_INDEX: 5 SYMBOL_NAME: kbdfilter!Unload+29 FOLLOWUP_NAME: MachineOwner MODULE_NAME: kbdfilter IMAGE_NAME: kbdfilter.sys DEBUG_FLR_IMAGE_TIMESTAMP: 4d473c29 FAILURE_BUCKET_ID: 0xA_kbdfilter!Unload+29 BUCKET_ID: 0xA_kbdfilter!Unload+29 Followup: MachineOwner --------- Заранее благодарен за ответы.
Да, и этот указатель надо сохранить в device extension твоего девайса-фильтра. Всё верно, тот указатель, который сохранили в device extension. Неправильно. Судя по всему, у тебя legacy-фильтр, так просто его выгружать нельзя, в отличие от PnP-фильтров. Представь ситуацию: тебе пришёл сверху запрос, ты повесил на него completion routine и послал ниже. Затем вдруг ты решил выгрузить свой фильтр, но проблема в том, что запрос-то ещё не был завершён нижележащим драйвером. А когда тот вызовет IoCompleteRequest(), то будет вызвана твоя comlpetion routine, которой в памяти уже нет. Разумеется, так делать нельзя. Эту проблему можно решить, используя IoSetCompletionRoutineEx(), однако следует помнить, что если сверху твоего legacy-фильтра приаттачится другой фильтр, то твой фильтр автоматически станет невыгружаемым, понятно почему? Чтобы избежать всех этих проблем, тебе следует переписать клавиатурный фильтр на PnP. Системные функции могут повышать IRQL неявно. Вообще, падение с IRQL_NOT_LESS_OR_EQUAL не всегда указывает на некорректное повышение IRQL, чаще оно указывает лишь на то, что где-то произошло обращение к невалидной памяти. В твоём случае это естественно, т.к. ты передал не тот указатель.
Я сохранил в глобальной переменной. Судя по MSDN, это позволительно. У меня только одной устройство, путаницы не будет. Мне показалось странным, что в IoDetachDevice передается низлежащее устройство, а не то, которое мы отцепляем. Ну да ладно. В том-то и дело, что я вызвал IoSetCompletionRoutineEx, передав в нее DEVICE_OBJECT моего драйвера. Как я представляю, IoDetachDevice не вернется до тех пор, пока не выполнятся все успевшие зарегистрироваться completion routines отсоединяемого устройства. Или не так? И можно ли избежать этих проблем, не переписывая все на PnP? Не хотелось бы сильно отклоняться от книги. Видимо IoDetachDevice поднимает IRQL до DISPATCH_LEVEL, но не опускает его, если указатель указывает не на то устройство.
Не так, схема немного другая. Во-первых, IoDetachDevice() никогда ничего не ждёт, она быстренько делает своё дело и отваливает. Во-вторых, вызов unload routine совершенно не означает, что драйвер сейчас будет выгружен, это всего уведомление о том, что был запрос на выгрузку и необходимо подчистить за собой всё, что можно. В этот момент драйвер всего лишь помечен для выгрузки, но его образ в памяти будет жить до тех пор, пока не будет уничтожен его DRIVER_OBJECT, а тот, в свою очередь, не будет уничтожен до тех пор, пока не будут удалены всего его девайсы. В-третьих, функция IoDeleteDevice() не удаляет объект девайса немедленно, вместо этого она лишь отмечает его для удаления. Кроме того, функция IoDeleteDevice() гарантирует, что девайс не будет открыт после вызова этой функции (т.е. во всех последующих NtCreateFile() на \Device\KeyboardClass0 будет отказано). И, наконец, в-четвёртых, сама по себе по себе функция IoSetCompletionRoutineEx() не является панацеей, она гарантирует лишь то, что драйвер не будет выгружен во время выполнения твоей completion routine, а о том, чтобы драйвер не был выгружен к моменту завершения запроса, который прошёл через твой фильтр, ты должен позаботиться самостоятельно. Например, с помощью remove locks или собственной реализации подсчёта ссылок. Верно, IoDetachDevice() захватывает глобальный спинлок, поднимая таким образом IRQL до DISPATCH_LEVEL. Далее происходит обращение к некорректным данных в переданном девайсе и, как результат, падение.
x64 Спасибо за объяснение, но хочу уточнить такую деталь: удаляет ли IoDetachDevice мою completion routine из IO_STACK_LOCATION (я так понял, указатель именно там хранится)? Просто у меня сложилась примерно такая картина: поток raw input (кажется так называется) шлет IRP стеку драйверов клавиатуры. Вызов проходит через мою IRP_MJ_READ, я регистрирую completion routine. IRP дошел до PDO, в этот момент я вызываю выгрузку драйвера. Выгрузка драйвера начинается, вызывается Unload, которая делает IoDetachDevice и IoDeleteDevice. После этого начинают вызываться completione routine стека. И вот тут вопрос: была ли удалена моя completion routine или нет. Если нет, то весьма логично, что она будет вызвана и будет BSoD. Вообще, в примере в книге использовался счетчик, котоый в IRP_MJ_READ увеличивался, а в completion routine уменьшался. И выгрузка происходила только тогда, когда он был равен нулю. Я подумал, что IoSetCompletionRoutineEx будет достаточно и убрал его. Видимо, это не правильно. И еще вопрос: как выгружать (или не выгружать вообще) драйвер, если кто-то добавил устройство "поверх" моего? Надо это как-то проверить, вероятно?