РАСЧЕТНО-ПОЯСНИТЕЛЬНАЯ ЗАПИСКА
к курсовому проекту на тему:
Клавиатурные макроопределения
СОДЕРЖАНИЕ
ВВЕДЕНИЕ
1. АНАЛИТИЧЕСКИЙ РАЗДЕЛ
1.1. Техническое задание
1.2. Архитектура Windows NT 5
1.3. Классификация драйверов
1.4. Модель WDM
1.5. Общая структура драйвера
1.5.1. Точки входа драйвера
1.5.2. Процедура DriverEntry
1.5.3. Процедура AddDevice
1.5.4. Процедура Unload
1.5.5. Процедура обработки IRP пакетов
1.5.5.1. Заголовок IRP пакета
1.5.5.2. Стек IRP пакета
1.5.5.3. Функция обработки пакетов IRP_MJ_DEVICE_CONTROL
1.5.5.4. Функция обработки пакетов IRP_MJ_READ
1.5.5.5. Функция обработки пакетов IRP_MJ_PNP
1.5.6. ISR - процедура обработки прерываний
1.5.7. DPC - процедура отложенного вызова
2. КОНСТРУКТОРСКИЙ РАЗДЕЛ
2.1. Стек драйверов клавиатуры
2.2. Структура разрабатываемого драйвера. Интерфейс
2.2.1. Процедура DriverEntry
2.2.2. Процедура AddDevice
2.2.3. Процедура DriverUnload
2.2.4. Процедуры обработки IRP пакетов
2.2.4.1. Функция обработки пакетов IRP_MJ_DEVICE_CONTROL
2.2.4.2. Функция обработки пакетов IRP_MJ_READ
2.2.4.3. Call-back функция MyReadBack
2.2.4.4. Функция обработки пакетов IRP_MJ_PNP
2.2.4.5. Обработка остальных пакетов IRP
2.3. Размещение драйвера в памяти
2.4. Алгоритм работы драйвера
2.4.1. Управление режимом работы драйвера-фильтра из пользовательского приложения
2.4.2. Алгоритм распознавания «горячих клавиш»
2.4.2.1. Информация о нажатии и отпускании клавиш
2.4.2.2. Анализ содержимого IRP пакета
2.4.3. Алгоритм воспроизведения макроса
2.5. Структура данных драйвера-фильтра
2.6. Установка драйвера в системе
3. ТЕХНОЛОГИЧЕСКИЙ РАЗДЕЛ
3.1. Выбор языка программирования и средств разработки
3.1.1. Драйвер-фильтр
3.1.2. Пользовательское приложение для установки драйвера
3.1.3. Пользовательское приложение для управления драйвером
3.2. Интерфейс пользовательского приложения
3.3. Установка и обращение к разработанным программам
ЗАКЛЮЧЕНИЕ
Приложение 1
Приложение 2
Список литературы
Введение
Очень часто возникает потребность в использовании макросов. Макрос - последовательность действий, записываемая какой-либо программой и в последствии воспроизводящаяся в этой же программе при нажатии «горячих клавиш», заданных пользователем. Это очень удобно в том случае, когда пользователь многократно повторяет какие-либо действия. Такие макросы очень распространены в текстовых редакторах, к примеру, в Microsoft Word. Но возникает потребность часто воспроизводить одну и ту же последовательность действий в нескольких приложениях, зачастую таких, где не предусмотрена возможность записи макроса. Причем, с этой проблемой сталкиваются как на профессиональном уровне использования компьютера, так и на пользовательском. К примеру, необходимо часто вводить пароль для почтового ящика и подключения к Интернету, или какую-нибудь часто употребляемую команду или путь к файлу при работе с командной строкой (где, кстати, даже знаменитая комбинация Ctrl + V не работает).
Как известно, практически все действия на компьютере можно осуществить с помощью клавиатуры, конечно, это не всегда удобно, но, в принципе, возможно. Таким образом, решением выше поставленной задачи могла бы быть программа, позволяющая записывать последовательность нажатых клавиш клавиатуры и воспроизводить их в любом приложении при нажатии заданной пользователем горячей комбинации.
Задачей данной курсовой работы является написание фильтра драйвера для клавиатуры и пользовательского приложения для работы с ним, которые позволяли бы пользователю записывать такие макросы и воспроизводить их в любом приложении.
1. Аналитический раздел
1.1 Техническое задание
Необходимо разработать:
· Драйвер-фильтр для клавиатуры, который:
ѕ Перехватывает скан-коды нажатых клавиш от нижележащего драйвера - драйвера клавиатуры;
ѕ Записывает скан-коды последовательности нажатых клавиш, если установлен режим записи;
ѕ Запоминает комбинацию «горячих скан-кодов» для воспроизведения этой последовательности;
ѕ Воспроизводит макрос при получении «горячих скан-кодов» от нижележащего драйвера.
· Приложение, которое без нанесения ущерба системе:
ѕ Инсталлирует драйвер;
ѕ Деинсталлирует драйвер.
· Пользовательское приложение для работы с драйвером, которое:
ѕ Управляет режимом работы драйвера-фильтра (обеспечивает режимы записи, паузы, останова, отмены записи);
ѕ Передает драйверу-фильтру «горячую комбинацию» клавиш для записанного макроса;
ѕ Отображает список ранее записанных макросов;
ѕ Обеспечивает возможность просмотра «содержимого» макроса - последовательности записанных клавиш;
ѕ Обеспечивает возможность удаления макроса из списка существующих макросов;
1.2 Архитектура Windows NT 5
Windows XP является следующей -- после Windows 2000 и Windows Millennium -- версией операционной системы Microsoft Windows. Данная операционная система использует защищённый режим центрального процессора, реализует механизмы виртуальной памяти и многозадачности.
Windows NT использует два уровня привилегий: уровень привилегий 0, соответствующий коду режима ядра и уровень привилегий 3, соответствующий коду прикладных задач (всего существует четыре уровня привилегий). Уровень привилегий накладывает определённые ограничения: в пользовательском режиме не могут выполняться привилегированные инструкции процессора и не разрешено обращение к защищённым страницам в памяти. Эти ограничения накладываются для обеспечения безопасности работы системы. Пользовательское приложение не должно иметь возможность (в результате ошибки или преднамеренно) вносить изменения в системные таблицы или в память других приложений. В частности, такие ограничения запрещают пользовательскому приложению напрямую управлять внешними устройствами. В Windows NT обеспечение обмена данными и управление доступом к внешнему устройству возлагается на его драйвер. Ввод и вывод в драйверах осуществляется пакетами -- IRP (Input/Output Request Packet). Запросы на ввод/вывод, посылаемые приложениями или другими драйверами, обрабатываются драйвером, после чего запрашивающей программе в том же пакете посылается статус завершения операции. Архитектура ввода/вывода в Windows NT имеет иерархическую структуру. Для осуществления операции ввода/вывода пользовательское приложение должно вызвать одну из функций API. Эта функция создает необходимый IRP пакет и направляет его подсистеме ввода/вывода. Подсистема ввода/вывода направляет IRP пакет необходимому драйверу. Драйвер осуществляет обращение к устройствам, используя функции HAL (Рис.1).
HAL (Hardware Abstraction Layer) - это слой программного обеспечения, который скрывает специфику аппаратной платформы от остальных компонентов системы. Он обеспечивает малые затраты при переносе системы или элементов программного обеспечения.
1.3 Классификация драйверов
Драйвером считается фрагмент кода операционной системы (ОС), позволяющий ей общаться с аппаратурой. Другими словами, драйвер - набор обработчиков запросов на ввод/вывод к обслуживаемому им устройству, поступающих от пользовательского приложения, других драйверов или самой ОС. Все запросы на ввод/вывод поступают Диспетчеру ввода/вывода (ДВВ), который распределяет их по назначению. Таким образом, процедуры драйвера пассивно ждут, пока к ним обратится Диспетчер ввода/вывода.
Драйверы, используемые Windows NT, можно разделить на следующие группы:
· драйверы пользовательского режима
(являются системным программным кодом, функционирующим в пользовательском режиме. В качестве примера можно привести драйверы симуляторы (виртуализаторы) для воображаемой аппаратуры или новых исполнительных подсистем. Так как Windows NT не допускает непосредственной работы с аппаратурой для кода пользовательского режима, то такие драйверы должны полагаться в этой части на драйверы, работающие в режиме ядра)
· драйверы режима ядра
(целиком состоят из системного кода, выполняющегося в режиме ядра. Такие драйверы имеют прямой доступ к управлению аппаратурой)
Драйверы режима ядра можно поделить еще на 2 категории:
· наследованные (legacy, доставшиеся как наследство от Windows NT 3.5, 4)
(предназначены для работы с теми устройствами, которые не поддерживают PnP спецификацию. После модификации унаследованного драйвера необходимо выполнить перезагрузку операционной системы для того, чтобы загрузить новую версию драйвера. Достоинством унаследованных драйверов является их простота.)
· WDM драйверы
(Способность WDM драйверов работать по PnP спецификации включает в себя участие в управление энергосбережением системы, автоматическое конфигурирование устройства и возможность его «горячего» подключения. Таким образом, достоинством WDM драйверов является возможность их выгрузки, изменения и загрузки в систему без перезапуска операционной системы благодаря механизму Plug and Play.)
Наследуемые и WDM драйверы также можно разделить на другие три категории: высокоуровневые, средне и низкоуровневые драйверы.
К высокоуровневым драйверам, например, относятся драйверы файловых систем. Такие драйверы предоставляют инициаторам запроса нефизическую абстракцию получателя, и уже эти запросы транслируются в специфические запросы к лежащим ниже драйверам. Необходимость в создании высокоуровневых драйверов возникает тогда, когда основные услуги аппаратуры уже реализованы драйверами нижнего уровня, и требуется только создать новую фигуру абстрагирования, которая необходима для предъявления инициатору запросов (клиенту драйверов).
Драйверы среднего уровня могут быть проиллюстрированы такими примерами, как драйверы зеркальных дисков, классовые драйверы, мини-драйверы и драйверы-фильтры. Эти драйверы позиционируют себя между высокоуровневыми абстракциями высокоуровневых драйверов и средствами физической поддержки на нижних уровнях. Например, драйверы-фильтры позиционируют себя во время загрузки над или под интересующим их драйвером и перехватывают запросы, идущие к нему или от него. Драйверы-фильтры, как правило, предназначены для модификации запроса к существующему драйверу или для реализации некоей дополнительной функции, изначально не заложенной в существующем драйвере.
Драйверы низкого уровня работают непосредственно с аппаратурой, они обычно разрабатываются вместе с тем устройством, для которого они предназначены.
1.4 Модель WDM
Windows Driver Model (WDM) -- это стандартная спецификация Microsoft для разработчиков драйверов устройств. Она поддерживается в операционных системах Windows 98/Me/2000/XP. Компания Microsoft требует, чтобы все новые драйверы под эти операционные системы создавались по этой спецификации. Для этого от них требуется чёткое следование структуре WDM, поддержка Plug and Play и управления электропитанием.
Драйверная модель WDM построена на организации и манипулировании слоями «Объектов физических устройств» и «Объектов функциональных устройств».
Все устройства образуют дерево устройств, которое можно рассмотреть, если обратиться к программе DeviceTree, поставляемой в составе пакета Microsoft DDK. Вершиной такого дерева можно считать PCI шину (точнее драйвер PCI шины), из этой вершины вырастают драйвер шины USB, шины FireWire, ISA шины.
Драйвер шины отвечает за обнаружение новых устройств (если устройство поддерживает механизм PnP), он создает список всех обнаруженных устройств, подключенных к данной шине. При подключении новой аппаратуры драйвер шины создает объект физического устройства PDO (Physical Device Object). PDO - некая абстракция, предназначенная для обозначения самого факта существования физического устройства, содержащая информацию, необходимую для его идентификации. В системном реестре для каждого устройства определен класс устройств, определяющий драйвер, необходимый для работы этого устройства. Если этот драйвер еще не загружен, то система выполняет загрузку (вызывает DriverEntry).
Драйвер в свою очередь создает объект функционального устройства FDO (Functional Device Object) при помощи вызова IoCreateDevice. FDO-объект, что внутри драйвера устройства, подключается к PDO-объекту, что внутри шинного драйвера. Таким образом, создается впечатление, что один драйвер подключается к другому и так появляется понятие стека драйверов (стека устройств).
Функциональным объектам устройств разрешается окружать себя объектами фильтрами (Filter Device objects). Соответственно, каждому FiDO объекту сопоставлен драйвер, который выполняет определенную работу. Объекты фильтры подразделяются на фильтры нижнего уровня и фильтры верхнего уровня. И тех и других может существовать произвольное количество. Они модифицируют процесс обработки запросов ввода/вывода.
Объекты FDO и FiDO отличаются только в смысловом отношении. FDO объект и его драйвер считаются главными. Они обычно обеспечивают управление устройством, а объекты FiDO являются вспомогательными.
Все объекты FDO и FiDO позиционируют себя в стеке устройств запросов ввода/вывода. Если необходимо перехватить и обработать запрос, непосредственно идущий от пользователя, то нужно устанавливать верхний фильтр. Если же нужно отслеживать обращение к портам ввода вывода, обрабатывать прерывания, то нужен нижний фильтр. Данная модель позволяет драйверу устанавливать call-back процедуры. Когда запрос начинает обрабатываться, то он обрабатывается последовательно всеми драйверами стека устройства (исключая ситуацию, когда какой-либо драйвер сам завершит обработку запроса). После этого диспетчер ввода/вывода передает запрос call-back процедуре каждого драйвера стека. Сначала запрос передается call-back процедуре последнего драйвера который в стеке, потом процедуре предпоследнего драйвера и.т.д. Call-back процедуры нужны для того, чтобы обработать прочитанную из устройства информацию. Если фильтр обрабатывает запросы на чтение, то когда этот запрос поступит в драйвер, информация еще не будет считана. Поэтому драйверу необходимо установить call-back функцию. При ее вызове запрос уже будет содержать считанные данные.
(Рис.2). Порядок объектов в стеке определяет порядок обработки
1.5 Общая структура драйвера
1.5.1 Точки входа драйвера
Драйвер имеет следующие основные точки входа:
§ DriverEntry
§ DriverUnload
§ AddDevice
§ Функции для обработки пакетов IRP
§ ISR - процедура обработки прерывания (Interrupt service routine)
§ DPC - процедура отложенного вызова (Deferred procedure call)
1.5.2 Процедура DriverEntry
Процедура DriverEntry должна присутствовать в любом драйвере. На данную процедуру возложена функция начальной инициализации и определение остальных точек входа в драйвер. Она выполняется в момент загрузки (инсталляции) драйвера. В драйверах WDM значение этой функции значительно уменьшилось, большая часть работы возлагается на функцию AddDevice.
Прототип функции:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObj,
IN PUNICODE_STRING RegistryPath);
PDRIVER_OBJECT DriverObj - указатель на объект драйвера, поступает от диспетчера ввода/вывода, создается шинным драйвером при обнаружении устройства. Для регистрации точек входа в драйвер DriverEntry должна заполнить соответствующие поля в этой структуре.
Поле DriverUnload необходимо заполнить адресом процедуры, вызывающейся при выгрузке драйвера.
Поле DriverExtension->AddDevice необходимо заполнить адресом процедуры AddDevice.
Массив MajorFunctions заполняется адресами процедур обработки IRP пакетов. Процедура, зарегистрированная под номером N, обрабатывает IRP пакет с кодом N. Обычно драйверы используют не все эти процедуры, а регистрируют только нужные. Остальные же элементы массива заполняются адресом процедуры, выполняющей передачу пакета ниже по стеку драйверов;
PUNICODE_STRING RegistryPath - указатель на юникод-строку, обозначающую раздел системного реестра, созданный для данного драйвера;
NTSTATUS - возвращаемое значение результата (код ошибки STATUS_XXX либо, в случае нормального завершения, STATUS_SUCCESS);
1.5.3 Процедура AddDevice
Данная функция регистрируется, если драйвер поддерживает PnP. Одна из главных обязанностей AddDevice - это создание объекта устройства FDO и если необходимо подключение его к стеку драйверов устройства. Данная функция может создать несколько объектов устройств и подключить их к разным стекам. Более того, некоторые устройства FDO могут существовать, не будучи в связке с PDO. Они часто создаются для управления драйвером.
Прототип функции:
NTSTATUS MyAddDevice(IN PDRIVER_OBJECT DriverObj,
IN PDEVICE_OBJECT PDO)
PDRIVER_OBJECT DriverObj - указатель на объект драйвера устройства, поступает от диспетчера ввода/вывода, создается шинным драйвером при обнаружении устройства;
PDEVICE_OBJECT PDO - указатель на объект физического устройства, к которому подключается данный драйвер, создается шинным драйвером.
Функция AddDevice вызывается каждый раз при построении стека устройств (к примеру, при перезагрузке системы) или непосредственно при инсталляции драйвера, поэтому здесь также обычно резервируют память, необходимую для работы, и инициализируют структуры данных, если это необходимо.
1.5.4 Процедура DriverUnload
Как правило, однажды загруженный драйвер остается в системе до перезагрузки. Для того чтобы сделать драйвер выгружаемым, необходимо написать и зарегистрировать процедуру выгрузки Unload.
Прототип функции:
VOID MyDriverUnload(IN PDRIVER_OBJECT DriverObj);
В драйверах «в стиле NT» на эту процедуру возложен весь процесс выгрузки. Она обязана:
· для некоторых типов аппаратуры сохранить ее состояние в системном реестре, при последующей загрузке драйвера эти данные могут быть использованы;
· если прерывания разрешены для обслуживаемого устройства, то запретить их и произвести отключение от объекта прерываний. (ситуация, когда устройство будет порождать прерывания для несуществующего объекта прерывания, неминуемо приведет к краху системы);
· удалить символьную ссылку из пространства имен, видимого пользовательскими приложениями;
· отсоединить объект устройства от стека устройств вызовом IoDetachDevice;
· удалить объект устройства вызовом IoDeleteDevice;
· освободить память, выделенную драйверу в процессе работы.
В PnP драйверах все эти действия возложены на обработчик пакетов IRP_MN_REMOVE_DEVICE [1].
1.5.5 Процедуры обработки IRP пакетов
Для загрузки драйвера выполняется вызов его функции DriverEntry. В этой функции драйвер регистрирует свои рабочие процедуры путем заполнения специального массива MajorFunction, указатель на начало которого DriverEntry получает через аргументы своего вызова. Индексами в этом массиве являются номера IRP запросов (все типы IRP запросов описаны числами). Если драйверу надо обрабатывать определенный тип IRP запроса, то он устанавливает в соответствующем элементе массива адрес своего обработчика. Диспетчер ввода/вывода, ориентируясь на этот массив, вызывает нужные функции драйвера. Эти обработчики называются точками входа.
При любом запросе Диспетчер формирует IRP. Память для структуры IRP выделяется в нестраничной памяти. В IRP записывается код операции ввода/вывода. Пакет IRP состоит из заголовка, который имеет постоянный размер и стека IRP. Стек имеет переменную длину.
1.5.5.1 Заголовок IRP пакета
Структура заголовка IRP пакета имеет следующие поля:
· Поле IoStatus типа IO_STATUS_BLOCK содержит два подполя
· Status содержит значение, которое устанавливает драйвер после обработки пакета.
· В Information чаще всего помещается число переданных или полученных байт.
· Поле AssociatedIrp.SystemBuffer типа PVOID содержит указатель на системный буфер для случая, если устройство поддерживает буферизованный ввод/вывод.
· Поле MdlAddress типа PMDL содержит указатель на MDL список, если устройство поддерживает прямой ввод вывод.
· Поле UserBuffer типа PVOID содержит адрес пользовательского буфера для ввода/вывода.
· Cancel типа BOOLEAN - это индикатор того, что пакет IRP должен быть аннулирован.
1.5.5.2 Стек IRP пакета
Основное значение ячеек стека IRP пакета состоит в том, чтобы хранить функциональный код и параметры запроса на ввод/вывод. Для запроса, который адресован драйверу самого нижнего уровня, соответствующий IRP пакет имеет только одну ячейку стека. Для запроса, который послан драйверу верхнего уровня, Диспетчер ввода/вывода создает пакет IRP с несколькими стековыми ячейками - по одной для каждого FDO.
Каждая ячейка стека IRP содержит:
· MajorFunction типа UCHAR - это код, описывающий назначение операции;
· MinorFunction типа UCHAR - это код, описывающий суб-код операции;
· DeviceObject типа PDEVICE_OBJECT - это указатель на объект устройства, которому был адресован данный запрос IRP;
· FileObject типа PFILE_OBJECT - файловый объект для данного запроса;
Диспетчер ввода/вывода использует поле MajorFunction для того, чтобы извлечь из массива MajorFunction нужную для обработки запроса процедуру.
Каждая процедура обработки IRP пакетов должна в качестве параметров принимать:
· Указатель на объект устройства, для которого предназначен IRP запрос;
· Указатель на пакет IRP, описывающий этот запрос;
Возвращает такая функция значение типа NTSTATUS, содержащее результат обработки.
1.5.5.3 Функция обработки пакетов IRP_MJ_DEVICE_CONTROL
Эти функции позволяют обрабатывать расширенные запросы от клиентов пользовательского режима, служат чаще всего для обмена данными между приложением и драйвером. Такой запрос может быть сформирован посредством вызова функции DeviceIoControl из пользовательского режима.
Функция DeviceIoControl имеет следующий прототип:
BOOL DeviceIoControl(HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped);
HANDLE hDevice - дескриптор драйвера, которому посылается запрос. Дескриптор драйвера можно получить, вызвав функцию CreateFile;
DWORD dwIoControlCode - код операции, IOCTL, которую необходимо исполнить драйверу;
Значения IOCTL, передаваемые в драйвер, могут быть определены разработчиком драйвера и имеют фиксированную внутреннюю структуру. Пакет DDK имеет в своем составе макроопределение CTL_CODE, которое обеспечивает приемлемый механизм генерации значений IOCTL.
Аргументы макроопределения CTL_CODE [1]:
DeviceType - значения, передаваемые в IoCreateDevice:
· 0x0000 - 0x7FFF - зарезервировано Microsoft,
· 0x8000 - 0xFFFF - определяется пользователем.
ControlCode - определяемые драйвером IOCTL значения:
· 0x000 - 0x7FF - зарезервировано Microsoft,
· 0x800 - 0xFFF - определяется пользователем.
TransferType - способ получения доступа к буферу
· METHOD_BUFFERED
Входной пользовательский буфер копируется в системный, а по окончании обработки системный копируется в выходной пользовательский буфер.
· METHOD_IN_DIRECT
Необходимые страницы пользовательского буфера загружаются с диска в оперативную память и блокируются. И с помощью DMA осуществляется передача данных между устройством и пользователем.
· METHOD_OUT_DIRECT
Необходимые страницы пользовательского буфера загружаются с диска в оперативную память и блокируются. И с помощью DMA осуществляется передача данных между устройством и пользователем.
· METHOD_NEITHER
При данном методе передачи не производится проверка доступности памяти, не выделяются промежуточные буфера и не создаются MDL. В пакете IRP передаются виртуальные адреса буферов в пространстве памяти инициатора запроса ввода/вывода.
В данном случае флаги, определяющие тип буферизации в объекте устройства (pDeviceObject->Flags), не имеют значения при работе с IOCTL запросами. Механизм буферизованного обмена определяется при каждом задании значения IOCTL в специально предназначенном для этого фрагменте этой структуры данных. Данный подход обеспечивает максимальную гибкость при работе с вызовом пользовательского режима DeviceIoControl.
RequiredAccess - требования инициатора относительно типа доступа [1]:
· FILE_ANY_ACCESS
· FILE_READ_DATA
· FILE_WRITE_DATA
· FILE_ READ_DATA | FILE_WRITE_DATA
LPVOID lpInBuffer - указатель на буфер входных данных;
DWORD nInBufferSize - размер входного буфера;
LPVOID lpOutBuffer - указатель на буфер выходных данных;
DWORD nOutBufferSize - размер выходного буфера;
LPDWORD lpBytesReturned - указатель на переменную, в которую драйвер запишет количество реально записанных байт;
LPOVERLAPPED lpOverlapped - указатель на структуру OVERLAPPED.
С точки зрения драйвера, доступ к буферным областям, содержащим данные или предназначенным для данных, осуществляется с помощью следующих полей структур [1]:
|
|
METHOD_BUFFERED
|
METHOD_IN_DIRECT или METHOD_OUT_DIRECT
|
METHOD_NEITHER
|
|
Input
Буфер с данными
|
Использует буферизацию (системный буфер)
Адрес буфера в системном адресном пространстве указан в pIrp->AssociatedIrp.SystemBuffer
|
Клиентский виртуальный адрес в Parameters. DeviceIoControl. Type3InputBuffer
|
|
|
Длина указана в Parameters.DeviceIoControl.InputBufferLength
|
|
Output
Буфер для данных
|
Использует буферизацию (системный буфер)
Адрес буфера в системном адресном пространстве указан в pIrp-> AssociatedIrp. SystemBuffer
|
Использует прямой доступ, клиентский буфер преобразован в MDL список, указатель на который размещен в pIrp->MdlAddress
|
Клиентский виртуальный адрес в pIrp->UserBuffer
|
|
|
Длина указана в Parameters.DeviceIoControl.OutputBufferLength
|
|
|
1.5.5.4 Функция обработки пакетов IRP_MJ_READ
Данная функция должна обрабатывать запросы на чтение информации из устройства.
Прототип функции:
NTSTATUS MyRead(IN PDEVICE_OBJECT DeviceObj, IN PIRP IRP);
PDEVICE_OBJECT DeviceObj - указатель на объект устройства;
PIRP IRP - указатель на IRP пакет;
Когда пакет IRP_MJ_READ идет вниз по стеку, то в нем еще нет данных, которые надо считать из устройства. Если же, к примеру, фильтру-драйверу необходимо анализировать или изменять данные, считанные из устройства, то он должен зарегистрировать свою call-back функцию с помощью вызова IoSetCompletionRoutine в функции, обрабатывающей пакеты с кодом IRP_MJ_READ. Call-back функция вызывается, когда пакет возвращается вверх по стеку от аппаратуры. Каждый драйвер стека устройств может зарегистрировать свою call-back функцию.
1.5.5.5 Функция обработки пакетов IRP_MJ_PNP
Данная функция должна обрабатывать запросы от менеджера PnP.
Прототип функции:
NTSTATUS MyPnpHandler(IN PDEVICE_OBJECT DeviceObj,
IN PIRP IRP);
Данная функция обычно состоит из оператора switch, в котором выбираются определенные действия для нужного кода подфункции. Код подфункции пакета IRP_MJ_PNP хранится в поле MinorFunction ячейки IRP пакета. Указатель на ячейку стека IRP, предназначенную для данного драйвера стека устройств, можно получить с помощью вызова IoGetCurrentIrpStackLocation. Одним из самых важных кодов подфункции данного IRP для WDM драйверов является код IRP_MN_REMOVE_DEVICE, при получении которого драйвер должен обеспечить корректную выгрузку, то есть выполнить все необходимые действия, которые описаны в функции Unload.
1.5.6 ISR - процедура обработки прерываний (Interrupt service routine)
На данную точку входа передастся управление, когда произойдет прерывание, на которое зарегистрирована эта ISR функция. Вызов может произойти в любом контексте: как ядра, так и пользовательского процесса. Здесь драйвер может либо дожидаться следующего прерывания (с какой-либо целью), либо запросить отложенный вызов процедуры (Deferred Procedure Call), DPC [3].
1.5.7 DPC - процедура отложенного вызова (Deferred procedure call)
Эта процедура выполняется при более низком уровне запроса прерывания (IRQL), чем ISR, что позволяет не блокировать другие прерывания. Процедура выполняет основную часть обработки прерывания, оставшуюся после выполнения ISR. Она инициирует завершение текущей операции ввода-вывода и выполнение следующей операции ввода-вывода из очереди на данном устройстве [3].
2. Конструкторский раздел
2.1 Стек драйверов клавиатуры
Физическую связь клавиатуры с шиной осуществляет микроконтроллер клавиатуры Intel 8042. На современных компьютерах он интегрирован в чипсет материнской платы. Этот контроллер может работать в двух режимах: AT-совместимом и PS/2-совместимом. Почти все клавиатуры уже давно являются PS/2-совместимыми. В PS/2-совместимом режиме микроконтроллер клавиатуры также связывает с шиной и PS/2-совместимую мышь. Данным микроконтроллером управляет функциональный драйвер i8042prt. Драйвер i8042prt создает два безымянных объекта «устройство» и подключает один к стеку клавиатуры, а другой к стеку мыши. Поверх драйвера i8042prt, точнее, поверх его устройств, располагаются именованные объекты «устройство» драйверов Kbdclass и Mouclass. Драйверы Kbdclass и Mouclass являются так называемыми драйверами класса и реализуют общую функциональность для всех типов клавиатур и мышей, т.е. для всего класса этих устройств. Оба эти драйвера устанавливаются как высокоуровневые драйверы.
Стек клавиатуры обрабатывает несколько типов IRP пакетов. В данной курсовой работе необходимо рассмотреть только IRP типа IRP_MJ_READ, которые несут с собой скан-коды клавиш. Генератором этих IRP является поток необработанного ввода RawInputThread системного процесса csrcc.exe. Этот поток открывает объект «устройство» драйвера класса клавиатуры для эксклюзивного использования и направляет ему IRP с кодом IRP_MJ_READ. Получив IRP, драйвер Kbdclass отмечает его как ожидающий завершения (pending), ставит в очередь и возвращает STATUS_PENDING. Потоку необработанного ввода придется ждать завершения IRP. Подключаясь к стеку, драйвер Kbdclass регистрирует у драйвера i8042prt процедуру обратного вызова KeyboardClassServiceCallback, направляя ему IRP IOCTL_INTERNAL_KEYBOARD_CONNECT. Драйвер i8042prt тоже регистрирует у системы свою процедуру обработки прерывания (ISR) I8042KeyboardInterruptService, вызовом функции IoConnectInterrupt. Когда будет нажата или отпущена клавиша, контроллер клавиатуры выработает аппаратное прерывание. Его обработчик вызовет I8042KeyboardInterruptService, которая прочитает из внутренней очереди контроллера клавиатуры необходимые данные. Т.к. обработка аппаратного прерывания происходит на повышенном IRQL, ISR делает только самую неотложную работу и ставит в очередь вызов отложенной процедуры (DPC). DPC работает при IRQL = DISPATCH_LEVEL. Когда IRQL понизится до DISPATCH_LEVEL, система вызовет процедуру I8042KeyboardIsrDpc, которая вызовет зарегистрированную драйвером Kbdclass процедуру обратного вызова KeyboardClassServiceCallback (также выполняется на IRQL = DISPATCH_LEVEL). KeyboardClassServiceCallback извлечет из своей очереди ожидающий завершения IRP, заполнит структуру KEYBOARD_INPUT_DATA, несущую всю необходимую информацию о нажатиях/отпусканиях клавиш, и завершит IRP. Поток необработанного ввода пробуждается, обрабатывает полученную информацию и вновь посылает IRP типа IRP_MJ_READ драйверу класса, который опять ставится в очередь до следующего нажатия/отпускания клавиши. Таким образом, у стека клавиатуры всегда есть, по крайней мере, один, ожидающий завершения IRP и находится он в очереди драйвера Kbdclass.
Разрабатываемый драйвер-фильтр устанавливается над фильтром Kbdclass. Так как IRP типа IRP_MJ_READ является фактически запросом на чтение данных, то когда он идет вниз по стеку его буфер, естественно пуст. Прочитанные данные буфер будет содержать только после завершения IRP. Для того чтобы эти данные увидеть, фильтр должен установить в каждый IRP (точнее в свой блок стека) процедуру завершения call-back. В этой процедуре как раз и будут осуществляться операции по анализу и записи скан-кодов в случае необходимости.
2.2 Структура разрабатываемого драйвера. Интерфейс
Поскольку разрабатываемый драйвер-фильтр является драйвером PnP, то должен иметь следующие точки входа:
§ DriverEntry
§ DriverUnload
§ AddDevice
§ Функции для обработки пакетов IRP
Функции для обработки прерываний в данной работе не регистрируются, поскольку драйвер не работает с прерываниями.
2.2.1 Процедура DriverEntry
В данной работе процедура DriverEntry выполняет следующие действия:
· Заполнение массива MajorFunctions.
Регистрируются:
ѕ процедура обработки пакета на чтение IRP_MJ_READ,
DriverObj->MajorFunction[IRP_MJ_READ] = MyRead;
ѕ процедура обработки IOCTL запросов IRP_MJ_DEVICE_CONTROL,
DriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyIOCTL;
ѕ процедуры обработки запросов от менеджера PNP (IRP_MN_REMOVE_DEVICE),
DriverObj->MajorFunction[IRP_MJ_PNP] = MyPnpHandler;
ѕ остальные элементы массива заполняются адресом функции MyPassIrpDown, которая пропускает пакеты ниже по стеку.
ULONG i;
for(i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
DriverObj->MajorFunction[i] = MyPassIrpDown;
· Регистрация процедуры AddDevice.
DriverObj->DriverExtension->AddDevice = MyAddDevice;
· Регистрация процедуры DriverUnload,
DriverObj->DriverUnload = MyDriverUnload;
DriverEntry регистрирует только необходимые процедуры. Поскольку проект представляет собой драйвер-фильтр верхнего уровня, и в нем нет необходимости обрабатывать прерывания, то не производится регистрация DriverStartIo, процедур ISR и DPC.
2.2.2 Процедура AddDevice
В данной работе на функцию MyAddDevice возложены следующие действия:
· создание одного функционального устройства с именем DeviceKeyFilter. При создании устройства происходит резервирование места для хранения адреса устройства, расположенного ниже в стеке драйверов. Это сделано для того, чтобы при разрушении стека драйверов передать запрос PnP на демонтаж нижестоящему драйверу.
RtlInitUnicodeString(&devName, L"DeviceKeyFilter");
· подключение созданного устройства к стеку драйверов клавиатуры. Это делается с помощью функции IoAttachDeviceToDeviceStack. Это стандартная функция Windows, она принимает PDO и указатель на структуру подключаемого FDO. FDO занимает место в стеке драйверов сразу после объекта, находящегося в вершине стека. Теперь подключаемый FDO становится вершиной стека. Нельзя подключится к стеку когда он уже сформирован, поэтому необходимо подключится к нему в определенный момент. Очередность загрузки драйверов описана в реестре Windows. Программа установки производит необходимую регистрацию. Структура этой программы описана ниже.
status = IoCreateDevice(DriverObj,
sizeof(DEVICE_EXTENSION),
&devName,
FILE_DEVICE_KEYBOARD,
0,
FALSE,
&DeviceObj);
· создание символьной ссылки. Для того чтобы пользовательское приложение смогло обратиться к драйверу (для передачи в драйвер режима работы или для получения списка «горячих комбинаций» уже записанных макросов) для FDO должно быть зарегистрировано DOS имя. Используя это имя, приложение сможет послать драйверу IOCTL запрос. Для регистрации такого имени создается строка юникод со значением DosDevicesMyFilter и применяется функция IoCreateSymbolicLink. Ее параметрами является только что созданная строка и имя FDO, которое обслуживает наш драйвер. Теперь DosDevicesMyFilter - это DOS имя созданного FDO устройства.
RtlInitUnicodeString(&symLinkName, L"DosDevicesKeyFilter");
IoCreateSymbolicLink(&symLinkName, &devName);
· выделение памяти для хранения массива клавиш, зажатых на текущий момент - необходимо знать для определения факта нажатия «горячей комбинации». Один элемент массива типа KEYBOARD_INPUT_DATA занимает 10 байт. Не имеет смысла экономить и выделять память динамически, поскольку 10 байт - это очень мало, и очень не правильно было бы тратить время на освобождение и выделение памяти при каждом нажатии клавиши. Память выделяется в нестраничном пуле.
Combination = (PKEYBOARD_INPUT_DATA) ExAllocatePool( NonPagedPool,MAX*sizeof(KEYBOARD_INPUT_DATA));
RtlZeroMemory(Combination,MAX*sizeof(KEYBOARD_INPUT_DATA))
· инициализация некоторых глобальных переменных:
// инициализация
kol_pressed = 0;
FLAG = _NEITHER;
List_of_change = NULL;
2.2.3 Процедура DriverUnload
Поскольку данный фильтр является PnP драйвером, то на процедуру DriverUnload ничего не возложено.
2.2.4 Процедуры обработки IRP пакетов
Разрабатываемый драйвер-фильтр осуществляет обработку следующих пакетов IRP:
· IRP_MJ_DEVICE_CONTROL
· IRP_MJ_READ
· IRP_MJ_PNP
Остальные IRP пакеты пропускаются ниже по стеку драйверов.
2.2.4.1 Функция обработки пакетов IRP_MJ_DEVICE_CONTROL
В данной работе пользовательское приложение должно иметь возможность посылать IOCTL запросы драйверу для обмена данными.
Обмен данными между драйвером и приложением осуществляется в случае, когда:
· Приложение получает от драйвера текущий список «горячих комбинаций» всех записанных макросов;
· Приложение управляет режимом работы драйвера-фильтра, путем передачи драйверу-фильтру команд «Начать запись», «Приостановить запись», «Отменить запись» и «Закончить запись»;
· Приложение передает драйверу комбинацию «горячих клавиш», которые ввел пользователь для дальнейшего вызова по ним записанного макроса.
· Приложение посылает драйверу команду удаления макроса с заданным номером;
· Приложение получает от драйвера «содержание» макроса с заданным номером;
Для этого в теле драйвера определены пять 32-битных констант:
Используется для считывания списка «горячих комбинаций» всех записанных драйверов. Вызывается приложением при необходимости обновления списка «горячих комбинаций» записанных макросов или при нажатии пользователем на кнопку «Обновить список»:
#define GetHotKeys CTL_CODE(FILE_DEVICE_KEYBOARD,
0x810,
METHOD_BUFFERED,
FILE_ANY_ACCESS)
Используется для установки режима работы драйвера («записи», «паузы», «отмены», «останова», «удаления»). Вызывается приложением при нажатии пользователем на соответсвующие кнопки:
#define SetMode CTL_CODE(FILE_DEVICE_KEYBOARD,
0x811,
METHOD_BUFFERED,
FILE_ANY_ACCESS)
Используется для получения приложением скан-кода последней нажатой клавиши (при назначении пользователем «горячей комбинации»). Вызывается приложением при появлении события OnKeyDown на поле ввода. Приложение, получив значение скан-кода, подставляет в поле ввода константу, несущую текстовое представление этой клавиши:
#define GetLast CTL_CODE(FILE_DEVICE_KEYBOARD,
0x812,
METHOD_BUFFERED,
FILE_ANY_ACCESS)
Используется при передачи приложением драйверу «горячей комбинации», назначенной пользователем. Вызывается при нажатии пользователем кнопки «ОК» на форме сохранения макроса:
#define SetComb CTL_CODE(FILE_DEVICE_KEYBOARD,
0x813,
METHOD_BUFFERED,
FILE_ANY_ACCESS)
Используется для получения «содержания» макроса с заданным номером. Вызывается при выборе пользователем пункта «Просмотреть» из всплывающего меню списка с «горячими комбинациями» записанных макросов:
#define GetMacros CTL_CODE(FILE_DEVICE_KEYBOARD,
0x814,
METHOD_BUFFERED,
FILE_ANY_ACCESS)
Это коды IOCTL запросов не использующиеся драйверами стека клавиатуры. Поэтому в данном проекте они могут быть использованы безо всяких опасений.
Для обеспечения обмена данными между приложением и драйвером необходимо использовать один из четырех способов передачи данных. В проекте применяется способ METHOD_BUFFERED. Поскольку, к примеру, «содержание» макроса в 25 нажатых клавиш занимает всего 50 байт, то при передаче его приложению размер буфера не повредит системному пулу, и копироваться буфер драйвера в системный будет очень быстро. Нет необходимости применять более сложные методы METHOD_IN_DIRECT или METHOD_NEITHER использующиеся при передаче больших объемов данных.
При обработке запроса приложения на получение данных (к примеру, списка «горячих комбинаций» всех записанных макросов или «содержания» определенного макроса) процедура MyIOCTL скопирует данные из буфера драйвера в системный буфер. После завершения обработки запроса менеджер ввода/вывода скопирует системный буфер в выходной пользовательский. Таким образом, приложение сможет получить запрошенные данные.
При передаче данных приложением драйверу (к примеру, «горечей комбинации», заданной пользователем) менеджер скопирует пользовательский буфер в системный, а функция обработки IOCTL скопирует системный буфер в буфер драйвера. После этого драйвер может продолжать работать с полученными данными.
2.2.4.2 Функция обработки пакетов IRP_MJ_READ
Данная функция осуществляет обработку пакетов на чтение. IRP пакет сначала будет попадать в разрабатываемый драйвер-фильтр. Вызовется зарегистрированная в DriverEntry функция MyRead. К моменту вызова MyRead, буфер не содержит кодов считанных клавиш. Для того чтобы получить доступ к ним, функция MyRead должна зарегистрировать call-back процедуру, которая получит управление, когда IRP пакет будет подниматься вверх по стеку драйверов. Тогда уже буфер IRP пакета будет содержать информацию о нажатых клавишах.
Call-back процедура устанавливается с помощью вызова IoSetCompletionRoutine:
IoSetCompletionRoutine(IRP, // указатель на
отслеживаемый IRP пакет
MyReadBack, // call-back функция
DeviceObj, // параметр, который получит
call-back функция
TRUE, // вызывать call-back в
случае успешного
завершения обработки IRP
TRUE, // вызывать call-back в
случае завершения
обработки IRP с ошибкой
TRUE ); // вызывать call-back в
случае прерванной
обработки IRP
Далее в функции MyRead проверяется флаг «Замены». ...........
Страницы: [1] | 2 |
|