6.4. Примеры драйверов устройств. Драйверы примеры


6.4. Примеры драйверов устройств

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

Драйвер системных часов

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

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

  • модифицировать системные структуры данных службы времени и даты;

  • увеличивать счетчик виртуального времени активного процесса;

  • если планирование процессов ведется с квантованием времени, уменьшать счетчик кванта активного процесса и, если счетчик обратился в ноль, вызывать планировщик;

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

Драйвер клавиатуры

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

  • чтение кода клавиши и перевод его в код символа;

  • запоминание кодов символов в своем буфере;

  • распознавание специальных клавиш или/и комбинаций клавиш (например, Ctrl+Break) и вызов специальных их обработчиков;

  • обработку специальных клавиш редактирования содержимого буфера (например, Backspace).

Большинство драйверов позволяют пользователю терминала производить упреждающий ввод данных – до того, как на них поступит запрос из программы. Введенные данные становятся доступными для чтения при нажатии клавиши ввода (например, Enter). Код этой клавиши сохраняется в буфере как признак конца строки. При поступлении запроса на чтение данных с клавиатуры драйвер выбирает из буфера строку – до признака конца строки. Если этот признак отсутствует, то процесс, выдавший запрос, блокируется до появления законченной строки в буфере драйвера. Некоторые драйверы запоминают введенные строки в стеке и обрабатывают также специальные клавиши, позволяющие выбирать строки из стека.

Драйверы дисковых запоминающих устройств

Обычной функцией такого драйвера является перевод виртуального адреса на диске в реальный (физический). Физический адрес на диске состоит из трех компонент: головка, дорожка, сектор (в дисковых архитектурах без разбиения на сектора – смещение на дорожке). Драйвер же формирует для процессов виртуальный диск, представляемый, как линейная последовательность секторов, виртуальным адресом является номер сектора.

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

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

  • SSTF (shortest seek time first – с наименьшим временем поиска – первый) – обслуживается запрос к ближайшей дорожке; эта стратегия обеспечивает весьма высокую пропускную способность, но при высоких нагрузках с высокой вероятностью допускает бесконечное откладывание запросов, обращенных к крайним на диске дорожкам;

  • Scan (сканирование) – головка движется в одном направлении, применяя на этом направлении SSTF, то есть обслуживается ближайший запрос на выбранном направлении, когда в этом направлении не остается запросов, направление меняется; стратегия обеспечивает высокую пропускную способность и исключает бесконечное откладывание, но при высоких нагрузках время ожидания запросов, обращенных к крайним дорожкам, существенно превышает среднее;

  • N-Scan (многошаговое сканирование) – головка движется в одном направлении, применяя на этом направлении FCFS, обслуживаются те запросы на выбранном направлении, которые поступили на момент начала движения, запросы, поступившие после этого момента, будут обслужены при обратном движении; стратегия обеспечивает лучшие показатели справедливости обслуживания при некотором увеличении среднего времени обслуживания;

  • C-Scan (циклическое сканирование) – такая модификация стратегии Scan, в которой головка движется всегда в одном направлении, а после обслуживания последнего на направлении запроса скачком перемещается к самому дальнему запросу; стратегия полностью исключает дискриминацию крайних дорожек даже при высоких нагрузках.

Авторы, приводящие результаты исследования функционирования этих стратегий на моделях [12, 36], рекомендуют стратегию Scan при малых нагрузках и C-Scan – при больших.

Совместно с любым методом сокращения времени выбора дорожки может применяться алгоритм минимизации задержки от вращения диска SLTF (shortest latency time next – с наименьшим временем задержки – первый): при наличии нескольких запросов к одной дорожке они упорядочиваются таким образом, чтобы все они могли быть обслужены за один оборот диска.

Другим примером функций, возлагаемых на драйвер дисков может быть поддержка RAID-технологий – использование избыточных дисковых устройств для обеспечения возможности восстановления данных при сбоях. В настоящее время имеется широкий спектр реализаций RAID-технологий – от полного переноса их на аппаратуру ввода-вывода до полной реализации их в драйвере.

studfiles.net

Лекция № 1. Введение

  1. Что необходимо знать. Структура курса. Список литературы.

  2. Зачем нужны драйверы. Примеры использования драйверов.

  3. Что такое драйвер, типы драйверов и характеристики

  4. Среда разработки.

  5. Утилита BUILD. Checkedи Free Build. Жизненный цикл разработки драйвера.

  6. С и С++. Интегрированная среда разработки.

  7. Установка, удаление, запуск и остановка драйвера.

[1.0] Что необходимо знать. Структура курса. Список литературы.

Необходимо знать:

Крайне желательно знать:

  • Win32 API

  • Интегрированная среда Developer Studio

  • Visual С++

  • Ассемблер

  • Работа процессора в защищенном режиме, особенно – организация памяти

Настоятельная рекомендация: На л/р иметь:

  • справочник по Visual C++

  • системное программирование в Windows

  • справочник по командам ассемблера

Структура курса.

Курс рассчитан на 15 лекций (30 часов) + 4 лабораторных занятия по 4 часа, каждое разбито на 2 л/р

Форма отчетности по курсу – экзамен.

Лекция 1. Введение

Лекция 2. Общая архитектура ОС NT. Архитектура ядра. Основные компоненты ядра и предоставляемые ими сервисы.

Лекция 3. Система приоритетов. Как используются IRQL. Диспетчеризация и планирование.

Лекция 4. Структура драйвера. Точка входа DriverEntry. Точки входа Dispatch.

Лекция 5. Подробнее об установке драйвера. Открытие драйвера, взаимосвязь объектов, передача данных в драйвер, IRP.

Лекция 6. Архитектура памяти. Управление памятью. MDL. Выделение памяти. Отображение памяти.

Лекция 7. Многоуровневая модель драйверов. Уровневые драйверы и драйверы-фильтры

Лекция 8. Сериализация. System Queuingи Driver Queuing

Лекция 9. Механизмы синхронизации.

Лекция 10. Рабочие потоки (Worker Threadsи Work Queue).

Лекция 11. Таймеры (IoTimerи Timer DPC). ISRи DPC.

Лекция 12. Программируемый в/в. DMA.

Лекция 13. Сетевая архитектура. NDISи TDI

Лекция 14. FSD

В процессе изложения могут даваться ссылки на следующую литературу (доступна в электронном виде в комп. зале):

  1. Windows NT Device Driver Development. Peter G. Viscarola, W. Antony Mason

  2. Developing Windows NT Device Driver. Edward N. Dekker, Joseph M. Newcomer

[1.1] Зачем нужны драйверы. Примеры использования драйверов

Для чего нужно знание архитектуры ОС и понимание драйверов применительно к сфере защиты информации? Этот вопрос можно рассматривать с различных точек зрения:

  • С точки зрения разработчика средств защиты информации

  • С точки зрения преодоления средства защиты

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

Реализация почти всех перечисленных элементов системы защиты для ОС NT возможна только с применением драйверов:

  • Защита локальных данных – либо FSD шифрующей файловой системы, либо драйвер-фильтр для прозрачного шифрования, либо перехват вызовов системных сервисов.

  • Защита сетевых данных – драйвер протокола, NDIS-драйвер промежуточного уровня, собственная сетевая служба, фильтр стандартной сетевой службы (такие службы реализованы как файловые системы - FSD), перехват вызовов системных сервисов.

  • Выявление нарушителя – для всех вышеприведенных вариантов – анализ событий, регистрация в журнале, запрос на подтверждение подозрительных действий.

Для специализированного оборудования должны быть разработаны драйверы для интеграции в эту схему.

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

  1. критерий максимальной простоты реализации

  2. критерий максимальной универсальности

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

В качестве примера можно привести задачу реализации защиты сетевого трафика. Наиболее универсальным (с точки зрения полноты контролируемого трафика) и на первый взгляд простым в этом случае будет являться NDIS-драйвер промежуточного уровня. Однако, по мере усложнения системы и приближения ее к коммерческой реализации, будут возникать все более серьезные проблемы. Например, такая система должна знать форматы всех протоколов в системе, в том числе и тех, которые в данный момент даже не созданы.

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

Примеры драйверов.

Драйверы ядра можно разбить на 2 больших класса: драйверы аппаратных устройств и драйверы виртуальных устройств:

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

  2. Драйвер виртуального устройства не работает с каким либо специализированным аппаратным устройством, однако предоставляет прикладным программам такие возможности по работе со стандартными ресурсами компьютера и ОС (процессор, память, порты, регистры, служебные структуры ОС), которые без драйвера были бы недоступны.

Что конкретно можно сделать с помощью драйвера виртуального устройства?

Как будет видно из следующих разделов, системная архитектура NT представляет собой набор модулей, связанных друг с другом стандартными, но далеко не всегда документированными интерфейсами. Благодаря этим интерфейсам можно производить как замену стандартных модулей на собственные, так и вставлять новые модули в «разрыв» связей между старыми. Такое устройство ОС позволяет разрабатывать новые модули (драйверы) для различных целей:

  • «прозрачная», то есть невидимая для прикладных программ, обработка данных, например, шифрование и компрессия данных на диске или в компьютерной сети;

  • расширение набора предоставляемых ОС сервисов;

  • «прозрачное» сканирование на наличие вирусов;

  • написание вирусов и закладок;

  • средства сбора статистики о событиях для различных компонентов системы.

Следует иметь ввиду, что помимо общих правил разработки и взаимодействия драйверов существуют специальные правила для особых типов драйверов. В качестве примера можно привести драйверы файловой системы – FSD и сетевые драйверы – архитектура NDIS и TDI.

На уровне ядра можно создавать не только драйвер, но и какой либо компонент ОС, например, HAL. Существуют специальные модули HAL, реализующие системы реального времени. В принципе, можно задуматься над возможностью придания ОС NT черт распределенной ОС.

studfiles.net

6.4. Примеры драйверов устройств

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

Драйвер системных часов

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

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

  • модифицировать системные структуры данных службы времени и даты;

  • увеличивать счетчик виртуального времени активного процесса;

  • если планирование процессов ведется с квантованием времени, уменьшать счетчик кванта активного процесса и, если счетчик обратился в ноль, вызывать планировщик;

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

Драйвер клавиатуры

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

  • чтение кода клавиши и перевод его в код символа;

  • запоминание кодов символов в своем буфере;

  • распознавание специальных клавиш или/и комбинаций клавиш (например, Ctrl+Break) и вызов специальных их обработчиков;

  • обработку специальных клавиш редактирования содержимого буфера (например, Backspace).

Большинство драйверов позволяют пользователю терминала производить упреждающий ввод данных – до того, как на них поступит запрос из программы. Введенные данные становятся доступными для чтения при нажатии клавиши ввода (например, Enter). Код этой клавиши сохраняется в буфере как признак конца строки. При поступлении запроса на чтение данных с клавиатуры драйвер выбирает из буфера строку – до признака конца строки. Если этот признак отсутствует, то процесс, выдавший запрос, блокируется до появления законченной строки в буфере драйвера. Некоторые драйверы запоминают введенные строки в стеке и обрабатывают также специальные клавиши, позволяющие выбирать строки из стека.

Драйверы дисковых запоминающих устройств

Обычной функцией такого драйвера является перевод виртуального адреса на диске в реальный (физический). Физический адрес на диске состоит из трех компонент: головка, дорожка, сектор (в дисковых архитектурах без разбиения на сектора – смещение на дорожке). Драйвер же формирует для процессов виртуальный диск, представляемый, как линейная последовательность секторов, виртуальным адресом является номер сектора.

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

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

  • SSTF (shortest seek time first – с наименьшим временем поиска – первый) – обслуживается запрос к ближайшей дорожке; эта стратегия обеспечивает весьма высокую пропускную способность, но при высоких нагрузках с высокой вероятностью допускает бесконечное откладывание запросов, обращенных к крайним на диске дорожкам;

  • Scan (сканирование) – головка движется в одном направлении, применяя на этом направлении SSTF, то есть обслуживается ближайший запрос на выбранном направлении, когда в этом направлении не остается запросов, направление меняется; стратегия обеспечивает высокую пропускную способность и исключает бесконечное откладывание, но при высоких нагрузках время ожидания запросов, обращенных к крайним дорожкам, существенно превышает среднее;

  • N-Scan (многошаговое сканирование) – головка движется в одном направлении, применяя на этом направлении FCFS, обслуживаются те запросы на выбранном направлении, которые поступили на момент начала движения, запросы, поступившие после этого момента, будут обслужены при обратном движении; стратегия обеспечивает лучшие показатели справедливости обслуживания при некотором увеличении среднего времени обслуживания;

  • C-Scan (циклическое сканирование) – такая модификация стратегии Scan, в которой головка движется всегда в одном направлении, а после обслуживания последнего на направлении запроса скачком перемещается к самому дальнему запросу; стратегия полностью исключает дискриминацию крайних дорожек даже при высоких нагрузках.

Авторы, приводящие результаты исследования функционирования этих стратегий на моделях [12, 36], рекомендуют стратегию Scan при малых нагрузках и C-Scan – при больших.

Совместно с любым методом сокращения времени выбора дорожки может применяться алгоритм минимизации задержки от вращения диска SLTF (shortest latency time next – с наименьшим временем задержки – первый): при наличии нескольких запросов к одной дорожке они упорядочиваются таким образом, чтобы все они могли быть обслужены за один оборот диска.

Другим примером функций, возлагаемых на драйвер дисков может быть поддержка RAID-технологий – использование избыточных дисковых устройств для обеспечения возможности восстановления данных при сбоях. В настоящее время имеется широкий спектр реализаций RAID-технологий – от полного переноса их на аппаратуру ввода-вывода до полной реализации их в драйвере.

studfiles.net

[1.2] Что такое драйвер, типы драйверов и характеристики [1.2.1] Что такое драйвер

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

Код прикладной программы исполняется в пользовательском режиме работы процессора. В этом случае имеется ряд серьезных ограничений на доступ к памяти, аппаратному обеспечению и привилегированным инструкциям процессора. Когда возникает необходимость в преодолении этих ограничений, прикладная программа обращается к ядру ОС, код которого исполняется процессором в режиме ядра. Режим ядра лишен всех упомянутых ограничений. Для расширения функциональных возможностей ядра как раз и служат драйверы ядра (kernel mode drivers). Как они работают?

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

Под вызовом драйвера здесь подразумевается не обычный вызов функции, а передача так называемого запроса в/в. Более подробно см. лекцию 6.

Различают несколько классов драйверов:

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

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

[1.2.2] Типы драйверов и характеристики

[Дополнительная информация] Ifs kit Help\Kernel-Mode Drivers\Design Guide\Part1 General Kernel Mode\Windows 2000 and WDM drivers\Kinds of driversили в DDK.

В NTсуществует 2 типа драйверов: драйверы пользовательского режима и драйверы режима ядра. В дальнейшем, говоря «драйвер», мы будем подразумевать драйверы режима ядра. Такие драйверы являются частью исполнительной системы, а более точно – элементами диспетчера в/в (архитектура NT и ее компоненты будут обсуждаться в лекции 2). Как следует из названия, при работе драйвера режима ядра процессор находится в режиме ядра (RING 0) – см. любой справочник по защищенному режиму работы процессора.

Драйвер NT располагается в файле с расширением .sys и имеет стандартный PE-формат (PE - Portable Executable).

Драйверы реализованы как самостоятельные модули с четко определенным интерфейсом взаимодействия с ОС. Все драйверы имеют определенный системой набор стандартных функций драйвера (standard driver routines) и некоторое число внутренних функций, определенных разработчиком.

Все драйверы режима ядра можно разбить на 3 типа:

  • Драйверы высшего уровня (highest level drivers)

  • Драйверы промежуточного уровня (intermediate drivers)

  • Драйверы низшего уровня (lowest level drivers)

Как мы увидим в дальнейшем, такое разбиение обусловлено многоуровневой моделью драйверов (layered driver model). Для сохранения общности изложения, монолитный драйвер можно включить в эту схему, хотя он не использует многоуровневую архитектуру. В этом случае он будет «гибридом» – драйвером, принадлежащим одновременно к нескольким типам. Например, монолитный драйвер, имеющий интерфейс с приложением и осуществляющий доступ к оборудованию, будет одновременно и драйвером высшего, и драйвером низшего уровня.

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

Отдельно необходимо упомянуть архитектуру WDM – Windows Driver Model. Эта архитектура позволяет создавать драйверы для Windows 98и Windows 2000, совместимые на уровне двоичного кода.

Можно привести следующие характеристики драйверов:

  • Поддержка динамической загрузки и выгрузки (однако могут быть исключения)

  • Необходимость следовать определенным протоколам взаимодействия с системой, нарушение которых чаще всего ведет к BSOD

  • Возможность «наслоения» драйверов поверх друг друга. В Win2K эта возможность возведена в абсолют, хотя монолитные драйверы все еще поддерживаются

  • Поскольку драйверы являются частью ядра ОС, они могут сделать с системой абсолютно все. Основная проблема – закрытость архитектуры ОС.

studfiles.net

Драйвер — это просто / Хабр

Многие считают что самому создать драйвер для Windows это что-то на грани фантастики. Но на самом деле это не так. Конечно, разработка драйвера для какого-то навороченного девайса бывает не простой задачей. Но ведь тоже самое можно сказать про создание сложных программ или игр. В разработке простого драйвера нет ничего сложного и я попытаюсь на примерах это показать. Сперва нам нужно определится в чем мы же будем создавать наш первый драйвер. Поскольку материал ориентирован на новичков, то язык программирования был выбран один из простых, и это не Си или ассемблер, а бейсик. Будем использовать один из диалектов бейсика — PureBasic. Из коробки он не обучен создавать драйверы, но у него удачный набор файлов, используемых для компиляции и небольшое шаманство позволяет добавить эту возможность. Процесс компиляции состоит из нескольких этапов. Если кратко, то он происходит следующим образом: Сначала транслятор «перегоняет» basic-код в ассемблер, который отдается FASM'у (компилятор ассемблера), который создает объектный файл. Далее в дело вступает линкер polink, создающий исполняемый файл. Как компилятор ассемблера, так и линкер могут создавать драйверы и если немного изменить опции компиляции, то получим не исполняемый файл, типа EXE или DLL, а драйвер режима ядра (SYS).

Скачать немного модифицированную бесплатную демо версию PureBasic 4.61 x86 можно на файлопомойке, зеркало. Если нужно создать драйвер для x64 системы, качайте эту версию, зеркало.

Дистрибутивы имеют небольшие размеры, около 3 МБ каждый. С помощью этой версии можно создавать только драйвера. Скачиваем, распаковываем и запускаем, кликнув по файлу «PureBasic Portable». При этом запустится IDE и вылезет окошко с сообщением что это демо-версия и списком ограничений. Из него наиболее существенным является ограничение числа строк кода, равное 800, а для создания простых драйверов этого может хватить. Остальные ограничения в нашем случае, не существенны.

Окно IDE с загруженным кодом драйвера показано на скрине.

image

Компиляция драйвера выполняется через меню «Компилятор» (это если кто не понял).image

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

Код драйвера:

Declare DriverEntry(*DriverObject, *RegistryPath) !public PureBasicStart !section '.code' code readable executable align 8 !PureBasicStart: *[email protected]() !jmp [p_A] ; Переход в процедуру DriverEntry(). #IOCTL_MyPlus = $200 !extrn PB_PokeL CompilerSelect #PB_Compiler_Processor CompilerCase #PB_Processor_x86 !extrn [email protected] ; Объявление импортируемых функций ядра. !extrn [email protected] !extrn [email protected] !extrn [email protected] !extrn [email protected] !extrn [email protected] !extrn [email protected] Import "ntoskrnl.lib" CompilerCase #PB_Processor_x64 !extrn IoCompleteRequest; Объявление импортируемых функций ядра. !extrn RtlInitUnicodeString !extrn IoCreateDevice !extrn IoDeleteDevice !extrn IoCreateSymbolicLink !extrn IoDeleteSymbolicLink !extrn PB_PeekI ImportC "ntoskrnl.lib" CompilerEndSelect ; Импорт функций ядра системы. IoCompleteRequest(*IRP, PriorityBoost) RtlInitUnicodeString(*UString, *String) IoCreateDevice(*DriverObject, DeviceExtensionSize, *UDeviceName, DeviceType, DeviceCharacteristics, Exclusive, *DeviceObject) IoDeleteDevice(*DeviceObject) IoCreateSymbolicLink(*SymbolicLinkName, *DeviceName) IoDeleteSymbolicLink(*SymbolicLinkName) EndImport Structure MyData ; Данные, передаваемые в драйвер. Plus_1.l Plus_2.l EndStructure ; Прцедура обмена данными с программой. Procedure DeviceIoControl(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP) Protected *Stack.IO_STACK_LOCATION Protected *InpBuff, *OutBuff Protected InBuffSize, OutBuffSize Protected ntStatus, *MyData.MyData ntStatus = #STATUS_SUCCESS ; Все ОК. *Stack = *pIrp\Tail\Overlay\CurrentStackLocation ; Размеры буферов (см. WinAPI функцию DeviceIoControl()) InBuffSize = *Stack\Parameters\DeviceIoControl\InputBufferLength OutBuffSize = *Stack\Parameters\DeviceIoControl\OutputBufferLength If InBuffSize >= SizeOf(Integer) And OutBuffSize >= 4 Select *Stack\Parameters\DeviceIoControl\IoControlCode Case #IOCTL_MyPlus *Point = *pIrp\SystemBuffer If *Point *MyData = PeekI(*Point) If *MyData Result.l = *MyData\Plus_1 + *MyData\Plus_2 PokeL(*pIrp\SystemBuffer, Result) *pIrp\IoStatus\Information = 4 Else ntStatus = #STATUS_BUFFER_TOO_SMALL *pIrp\IoStatus\Information = 0 EndIf EndIf Default ntStatus = #STATUS_UNSUCCESSFUL *pIrp\IoStatus\Information = 0 EndSelect Else ntStatus = #STATUS_BUFFER_TOO_SMALL ; Размер буфера слишком мал. *pIrp\IoStatus\Information = 0 EndIf *pIrp\IoStatus\Status = ntStatus IoCompleteRequest(*pIrp, #IO_NO_INCREMENT) ProcedureReturn ntStatus EndProcedure ; Выгрузка драйвера. Вызывается при завершении работы драйвера. Procedure UnloadDriver(*DriverObject.DRIVER_OBJECT) Protected uniDOSString.UNICODE_STRING ; Инициализация объектов-строк. RtlInitUnicodeString(@uniDOSString, ?DosDevices) ; Удаление символьной связи. IoDeleteSymbolicLink (@uniDOSString) ; Удаление устройства. IoDeleteDevice(*DriverObject\DeviceObject) EndProcedure ; Вызывается при доступе к драйверу с помощью функци CreateFile(). Procedure CreateDispatch(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP) *pIrp\IoStatus\Information = 0 *pIrp\IoStatus\Status = #STATUS_SUCCESS IoCompleteRequest(*pIrp, #IO_NO_INCREMENT) ProcedureReturn #STATUS_SUCCESS EndProcedure ; Вызывается при осовбождении драйвера функцией CloseHandle(). Procedure CloseDispatch(*DeviceObject.DEVICE_OBJECT, *pIrp.IRP) *pIrp\IoStatus\Information = 0 *pIrp\IoStatus\Status = #STATUS_SUCCESS IoCompleteRequest(*pIrp, #IO_NO_INCREMENT) ProcedureReturn #STATUS_SUCCESS EndProcedure ; Процедура загрузки драйвера. Вызывается однократно при его запуске. Procedure DriverEntry(*DriverObject.DRIVER_OBJECT, *RegistryPath.UNICODE_STRING) Protected deviceObject.DEVICE_OBJECT Protected uniNameString.UNICODE_STRING Protected uniDOSString.UNICODE_STRING ; Инициализация объектов-строк. RtlInitUnicodeString(@uniNameString, ?Device) RtlInitUnicodeString(@uniDOSString, ?DosDevices) ; Создание устройства. status = IoCreateDevice(*DriverObject, 0, @uniNameString, #FILE_DEVICE_UNKNOWN, 0, #False, @deviceObject) If status <> #STATUS_SUCCESS ProcedureReturn status EndIf ; Создане символьной связи между именем этого устройства и именем, ; находящимся в видимой области для user-mode, для того, чтобы ; приложение могло получить доступ к этому устройству. status = IoCreateSymbolicLink(@uniDOSString, @uniNameString) If status <> #STATUS_SUCCESS IoDeleteDevice(@deviceObject) ProcedureReturn status EndIf ; Указатель на функцию выгрузки драйвера. *DriverObject\DriverUnload = @UnloadDriver() *DriverObject\MajorFunction[#IRP_MJ_CREATE] = @CreateDispatch() *DriverObject\MajorFunction[#IRP_MJ_CLOSE] = @CloseDispatch() ; Указываем какая функция будет обрабатывать запросы WinAPI DeviceIoControl(). *DriverObject\MajorFunction[#IRP_MJ_DEVICE_CONTROL] = @DeviceIoControl() ProcedureReturn #STATUS_SUCCESS EndProcedure ; Имя драйвра (юникод). DataSection Device: !du '\Device\pbDrPlus', 0, 0 DosDevices: !du '\DosDevices\pbDrPlus', 0, 0 EndDataSection

Может показаться что это куча бессмысленного кода, но это не так.

У каждого драйвера должна быть точка входа, обычно у нее имя DriverEntry() и выполнена она в виде процедуры или функции. Как видите, в этом драйвере есть такая процедура. Если посмотрите на начало кода, то в первых строках увидите как ей передается управление. В этой процедуре происходит инициализация драйвера. Там же назначается процедура завершения работы драйвера, которая в нашем случае имеет имя UnloadDriver(). Процедуры CreateDispatch() и CloseDispatch() назначаются обработчиками соединения и отсоединения проги из юзермода. Процедура DeviceIoControl() будет обрабатывать запросы WinAPI функции DeviceIoControl(), являющейся в данном драйвере связью с юзермодом. В конце кода расположена так называемая ДатаСекция (DataSection), в которой находятся имена драйвера, сохраненные в формате юникода (для этого использована одна из фишек ассемблера FASM).

Теперь рассмотрим как драйвер будет взаимодействовать с внешним миром. Это происходит в процедуре DeviceIoControl(). В ней отслеживается одно сообщение, а именно — #IOCTL_MyPlus, которое отправляет юзермодная прога, когда ей нужно сложить два числа в режиме ядра (круто звучит, правда?). Когда такое сообщение получено, то считываем из системного буфера, адрес указателя на структуру со слагаемыми, производим сложение и результат помещаем в системный буфер. Собственно это основная задача нашего первого драйвера.

Видите сколько понадобилось кода для выполнения простейшей математической операции — сложения двух чисел?

А теперь рассмотрим программу, работающую с этим драйвером. Она написана на том же PureBasic.

#DriverName = "pbDrPlus" #IOCTL_MyPlus = $200 XIncludeFile "..\DrUserModeFramework.pbi" Structure MyData ; Данные, передаваемые в драйвер. Plus_1.l Plus_2.l EndStructure ; Абсолютный путь к файлу-драйверу. DrFile.s = GetPathPart(ProgramFilename())+#DriverName+".sys" ; Загружает драйвер и если успешно, то порлучаем его хэндл. hDrv=OpenDriver(DrFile, #DriverName, #DriverName, #DriverName) If hDrv=0 ; Деинсталляция драйвера из системы. Driver_UnInstall(#DriverName) MessageRequester("", "Ошибка загрузки драйвера") End EndIf ; Обмен данными с драйвером. Procedure.q Plus(hDrv, x1, x2) Protected MyData.MyData, Result, *Point MyData\Plus_1=x1 MyData\Plus_2=x2 *Point = @MyData DeviceIoControl_(hDrv, #IOCTL_MyPlus, @*Point, SizeOf(MyData), @Result, 4, @BytesReturned, 0) ProcedureReturn Result EndProcedure OpenWindow(1,300,300,140,90,"Title",#PB_Window_SystemMenu|#PB_Window_ScreenCentered) StringGadget(1,10,10,50,20,"") StringGadget(2,10,40,50,20,"") TextGadget(3,70,30,70,20,"") Repeat ev=WaitWindowEvent() If ev=#PB_Event_Gadget op1=Val(GetGadgetText(1)) op2=Val(GetGadgetText(2)) Result = Plus(hDrv, op1, op2) SetGadgetText(3,Str(Result)) EndIf Until ev=#PB_Event_CloseWindow ; Если драйвер загружен, то закрываем связь с ним. If hDrv CloseHandle_(hDrv) hDrv=0 EndIf ; Деинсталляция драйвера из системы. Driver_UnInstall(#DriverName)

При старте программы вызывается функция OpenDriver(), которая загружает драйвер. Для упрощения, имя драйвера, имя службы и описание службы заданы одинаковыми — «pbDrPlus». Если загрузка неудачная, то выводится соответствующее сообщение и программа завершает свою работу.

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

Далее следует код простейшего GUI калькулятора, скопированного из википедии.

Когда закроют окно, то перед завершением работы программы, закрывается связь с драйвером и производится его деинсталляция из системы.

Результат сложения чисел 8 и 2 на скриншоте.

image

Исходные коды драйвера и программы, можно найти в папке «Examples», PureBasic на файлопомойке, ссылку на который давал в начале статьи. Там так же найдете примеры драйвера прямого доступа к порам компа и пример работы с памятью ядра.

PS. Помните, работа в ядре чревата мелкими неожиданностями аля, BSOD (синий экран смерти), поэтому экспериментируйте осторожно и обязательно всё сохраняйте перед запуском драйвера.

За возможную потерю данных, я ответственности не несу!

habrahabr.ru


Смотрите также