Пользователь
0,0
рейтинг
20 сентября 2008 в 22:30

Разработка → Пишем свой первый Windows-драйвер

Итак, после моей предыдущей статьи я понял что тема про программирование драйверов Windows интересна хабровчанам, поэтому продолжу. В этой статье я решил разобрать простую программу-драйвер, которая делает только то, что пишет отладочное сообщение «Hello world!» при старте драйвера и «Goodbye!» при завершении, а также опишу те средства разработки, которые нам понадобятся для того, чтобы собрать и запустить драйвер.



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

  1. // TestDriver.c
  2.  
  3. #include <ntddk.h>
  4.  
  5. NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath);
  6. VOID UnloadRoutine(IN PDRIVER_OBJECT DriverObject);
  7.  
  8. #pragma alloc_text(INIT, DriverEntry)
  9. #pragma alloc_text(PAGE, UnloadRoutine)
  10.  
  11. NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
  12. {
  13.   DriverObject->DriverUnload = UnloadRoutine;
  14.  
  15.   DbgPrint(«Hello world!\n»);
  16.  
  17.   return STATUS_SUCCESS;
  18. }
  19.  
  20. VOID UnloadRoutine(IN PDRIVER_OBJECT DriverObject)
  21. {
  22.   DbgPrint(«Goodbye!\n»);
  23. }
* This source code was highlighted with Source Code Highlighter.


Итак, теперь сначала разберемся, что делает каждая инструкция. Перво-наперво мы подключаем заголовочный файл ntddk.h. Это один из базовых подключаемых файлов во всех драйверах: в нем содержатся объявления типов NTSTATUS, PDRIVER_OBJECT, PUNICODE_STRING, а также функции DbgPrint.

Далее идет объявление двух функций: DriverEntry и UnloadRoutine. Расскажу о первой поподробнее. Итак, как уважаемые читатели знают, в каждой программе есть точка входа, в программах на языке C это функция main или WinMain. В драйвере роль точки входа выполняет функция DriverEntry, которая получает на вход указатель на структуру DriverObject, а также указатель на строку реестра, соответствующую загружаемому драйверу.

Структура DriverObject содержит множество полей, которые определяют поведение будущего драйвера. Наиболее ключевые из них — это указатели на так называемые вызываемые (или callback) функции, то есть функции, которые будут вызываться при наступлении определенного события. Одну из таких функций мы определяем: это функция UnloadRoutine. Указатель на данную функцию помещается в поле DriverUnload. Таким образом при выгрузке драйвера сначала будет вызвана функция UnloadRoutine. Это очень удобно, когда драйвер имеет какие-то временные данные, которые следует очистить перед завершением работы. В нашем примере эта функция нужна только чтобы отследить сам факт завершения работы драйвера.

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

В этом простом примере мы использовали также директивы #pragma alloc_text(INIT, DriverEntry) и #pragma alloc_text(PAGE, UnloadRoutine). Объясню что они означают: первая помещает функцию DriverEntry в INIT секцию, то есть как бы говорит, что DriverEntry будет выполнена один раз и после этого код функции можно спокойно выгрузить из памяти. Вторая помечает код функции UnloadRoutine как выгружаемый, т.е. при необходимости, система может переместить его в файл подкачки, а потом забрать его оттуда.

Вы можете задуматься, мол ну с первой-то директивой понятно, типа оптимизация и все такое, но зачем мы используем вторую директиву, зачем помечать код как возможный к выгрузке в файл подкачки? Поясню этот вопрос: каждый процесс в системе имеет такой параметр, как IRQL (подробнее читаем по ссылке Interrupt request level ибо это материал отдельной статьи), то есть некоторый параметр, отвечающий за возможность прерывания процесса: чем выше IRQL тем меньше шансов прервать выполнение процесса. Возможности процесса так же зависят от IRQL: чем выше IRQL тем меньше возможности процесса, это вполне логично, т.е. такой подход побуждает разработчиков выполнять только самые необходимые операции при высоком IRQL, а все остальные действия делать при низком. Вернемся к основной теме, о том, почему мы делаем для функции UnloadRoutine возможность выгрузки в файл подкачки: все опять же сводится к оптимизации: работа с файлом подкачки недоступна при высоком IRQL, а процедура выгрузки драйвера гарантированно выполняется при низком IRQL, поэтому мы специально указываем руками что код функции выгрузки драйвера можно поместить в своп.

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

Для этого нам понадобится:
  • WDK или DDK
  • Текстовый редактор
  • Программа DbgView — бесплатная программа для просмотра отладочных сообщений, получаемых от драйверов, ее можно найти на сайте sysinternals
  • Программа KmdManager — бесплатная программа для регистрации, запуска и тестирования драйвера, ее можно найти на сайте wasm.ru


Теперь последовательность действий: сначала мы пишем два файла, один называется MAKEFILE, с таким содержимым

##################################################
# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#

!INCLUDE $(NTMAKEENV)\makefile.def
##################################################


а второй называется sources и содержит в себе следующее:

##################################################
TARGETNAME=TestDriver
TARGETTYPE=DRIVER

SOURCES=TestDriver.c
##################################################


Эти файлы нужны для сборки драйвера. Да, забыл сказать, что в WDK нет встроенной среды разработки, поэтому и нужен текстовый редактор, чтобы набирать текст драйверов. Для этой цели можно использовать и Visual Studio (некоторые даже интегрируют возможность сборки драйверов из VS), и любой другой текстовый редактор.

Сохраняем код драйвера в файл TestDriver.c и кладем его в ту же директорию, что и файлы MAKEFILE и souces. После этого запускаем установленный build environment (это командная строка с заданными переменными окружения для компиляции драйвера; она входит в WDK, и запустить ее можно как-то так: «Пуск->Программы->Windows Driver Kits->....->Build Environments->WindowsXP->Windows XP x86 Checked Build Environment»). Переходим в директорию, куда мы положили файл с драйвером (у меня это C:\Drivers\TestDriver) с помощью команды cd (у меня команда выглядит следующим образом: cd C:\Drivers\TestDriver) и набираем команду build.

Данная команда соберет нам драйвер TestDriver.sys и положит его в папку «objchk_wxp_x86\i386».

Теперь нам нужно запустить программу DbgView чтобы увидеть сообщения, которые будет выдавать драйвер. После запуска данной программы нам нужно указать, что мы хотим просматривать сообщения из ядра (Capture->Capture Kernel).

Теперь запукаем программу KmdManager, указываем путь к нашему драйверу (файл TestDriver.sys) нажимаем кнопку Register, затем Run. Теперь драйвер зарегистрирован в системе и запущен. В программе DbgView мы должны увидеть наше сообщение «Hello World!». Теперь завершаем работу драйвера кнопкой Stop и убираем регистрацию драйвера кнопкой Unregister. Кстати, в DbgView дожна появиться еще одна строка.

Итак, чего же мы достигли: мы написали, скомпилировали и запустили свой первый Windows-драйвер! Добавлю только, что при написании сложный драйверов для отладки используется двухмашинная конфигурация, когда на одном компьтере ведется написание драйвера, а на другом — запуск и тестирование. Это делается из-за того, что неправильно написанный драйвер может обрушить всю систему, а на ней может быть очень много ценных данных. Часто в качестве второго компьютера используется виртуальная машина.

Егор Баранов @Goganchic
карма
89,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (35)

  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Спасибо, учту :)
  • +1
    Goodbye оба раза будет выводится :)
    • 0
      Спасибо, исправил :)
  • 0
    Ну это не драйвер, а кусок кода способного загрузится в кернел мод. Если рассказывать о методе написания драйверов, то уже нужно было что-то полноценное, создать симболик линки, организовать диспатчинг элементарный :)
    • НЛО прилетело и опубликовало эту надпись здесь
      • +2
        Тогда и название статье нужно было дать другое. Да и рассказать в конце концов, что такое IRQL, как никак, одно из фундаментальных понятий.
        • +5
          Хорошо, напишу следующую статью про IRQL :)
          • 0
            И где?
  • –1
    Не-не-не! Первый драйвер для виндоуз был другим — это был дравер коврика для мышки!
    После установки которого появлялось новое устройство — «коврик для мышки» :)))

    Если конечно еще кто-то про это помнит ;)
    • –1
      петросян: D
      • –1
        Учите юмор ©
        • 0
          я вам сударь карму не минусовал, самоутверждаетесь?
          • –3
            Спасибо, что не минусовали. Самоутверждаюсь в чем?
    • +2
      было дело ^_^
  • НЛО прилетело и опубликовало эту надпись здесь
  • +2
    Спасибо за статью. :) Я думаю имеет смысл продолжать раскрывать тему.

    Теперь замечания:

    1)
    «… почему мы делаем для функции DriverEntry возможность выгрузки в файл подкачки...»
    Имелась ввиду UnloadRoutine?

    2)
    «Итак, после моей предыдущей статьи...»
    «Итак, для начала...»
    «Итак, теперь сначала...»
    «Итак, как уважаемые читатели...»
    «Итак, чего же мы достигли...»

    Избавтесь от этого паразита. :)
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Допустим мне.
      Автору спасибо, очень разборчиво и толково написано.
  • +2
    ну и что это? что это за народное творчество? (С)

    попытка уместить огромный материал (по которому пишут тома) в одной статье закончится лишь неразберихой в головах новичков. Лучше бы написал хорошую вводную статью. Про архитектуру Windows, в том числе и ядерной ее части, как что работает и что к чему. А потом уже к конкретике. И постепенно. А не так вот, сразу.
    • 0
      PS. Да только не надо излюбленных народом комментариев вида «не нравится — пиши сам». Писал статьи и не раз. Поэтому и комментирую. Это так, к слову и на всякий случай.
      • 0
        Огромное спасибо за критику, в следующий раз последую вашему совету и буду концентрироваться на какой-нибудь небольшой проблеме
        • 0
          я думаю, что стоит одну статью посвещать одному вопросу. чем плотнее и быстрее ты излагаешь, тем хуже усваивается, как правило.
    • +1
      Ага, и мир пополнится еще одной пространной статьей/трудом который будет пылиться на полке у автора… Цель данной статьи, в первую очередь, заинтересовать, а не научить. Попытаться внушить читателю мысль, что написание драйверов — это не так страшно как кажется. А уж если человек заинтересовался, то поискать более конкретный материал по теме он всегда успеет.

      Несмотря на всю «неакадимичность», люди предпочитают, почему-то, читать именно туториалы, а не «серьезные труды».
      • –3
        Мда. А карму зачем портить? Или нынче так модно? :)
      • +2
        Да, людям нравится фастфуд, потому что его можно быстро сьесть и не задумываться о его содержании. Так и тут, чем больше и детальнее статья — тем меньше у нее популярность. Уже не раз убедился на своих статьях, когда в комментариях пишут «Да все отлично», а по оценкам видно, что мало кому это нужно. Однако фастфуд никогда не заменит домашней еды. Именно по этому, и потому, что мне нравится порой отвлечься и пографоманствовать, а также поделится знаниями и расположить по полочкам свои, я буду продолжать писать длинные, подробные, хоть и скушные статьи. Кому надо — тот оценит. По этому статьи по архитектуре винды будут, макос — тоже ;)
      • +1
        заинтересовать людей и уж тем более дать понять что это не так «страшно» невозможно такой концентрацией нового материала. человек прочтет, найдет много новых слов и просто его заломает разбираться.

        уже проверено на «крысах», так сказать, поэтому это не просто пустые слова.
        • +1
          Ну не знаю… Исходя из моего опыта преподавания как программирования вообще, так и Linux-а в частности, могу сказать, что подобные «атомарные» знания дают гораздо больше эффекта, нежели пространные витийствования на тему. Людям надо дать зацепку. Пусть даже они не поймут все и сразу, но зато что-то останется в памяти, и при случае можно дать этому ход.

          Подход «а-ля матан» мне категорически не нравится. Человека грузят 5-10 леммами (с доказательствами) только для того, чтобы потом доказать основную теорему: «как очевидно следует из бла и бла бла» (которые были 15 страниц назад). В результате, 90% людей вообще не помнят и не понимают о чем речь.

          Цель — научиться писать драйвера а не читать лекции по устройству современных ОС. В данном случае, мы идем от поставленной задачи, а не от «идеи».

          P.S: Я не говорю что «только так и никак иначе». Истина, как всегда, находится где-то посередине. То есть, некоторые вещи можно подкинуть так, другие выводить планомерно.

          P.P.S: Может быть, автор данного топика слишком углубился в технологию, тогда как можно было описать то же самое на более уровне, а уже потом подводить к собственно сабжу. Но это не нам судить, а как минимум тем людям что поставили теме 80 плюсов. Наверное, кому-то все таки интересно? ;)
          • –1
            >>Подход «а-ля матан» мне категорически не нравится. Человека грузят 5-10 леммами (с доказательствами) только для того, чтобы потом доказать основную теорему: «как очевидно следует из бла и бла бла» (которые были 15 страниц назад).

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

            >> В результате, 90% людей вообще не помнят и не понимают о чем речь
            значит, не дано им понять. «на зеркало неча пенять, коли рожа крива». в смысле, что если человек, извиняюсь, тупой, то как не излагай, все равно не поймет.
            • 0
              По-моему, вполне нормальный подход.

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

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

              Тогда не называйте себя учителем. Еще Эйнштейн говорил, что объяснять надо так, чтобы вас поняла глуховатая 80ти летняя бабка. Вы демонстрируете типичный подход преподавателя музыки: «музыка для одаренных, и не фиг сюда соваться всяким бездарям». Благодаря чему, огромное количество людей, имеющих желание изучить это дело, но не обладающих «талантом» теряется еще «на дальних подступах».

              Вообще довольно странно вести эту беседу. С одной стороны вы утверждаете что имеете опыт, с другой демонстрируете полнейшее равнодушие к слушателю. Не представляю, как с таким подходом можно хоть чему-то научить. Вернее научить то можно, но это произойдет скорее вопреки а не благодаря вашим усилиям.
              • 0
                :)
                мы отвлеклись от темы.
                ты утверждаешь, что изллагать надо предельно ясно, я согласен, но когда я прочитал статью выше, поймал себя на мысли, что если бы я не знал изложенного материала ранее, я бы ничего не понял. мысль часто сбивается и не совсем понятно что же хотели донести. ощущение, что автору уреазли объем и сказали — впихни сюда максимум информации. и он впихнул.
  • +4
    По поводу сборки: можно не пользоваться build из ddk (хотя MS это крайне рекомендуют, но не будет ничего страшного, если собирать драйвер средствами Visual Studio).
    можно создать обычный проект в студии, например консольного приложения, и произвести там некоторые манипуляции, например выставить тип подсистемы Native, линковку с ntoskrnl.lib, hal.lib и проч.

    >> Для того, чтобы выводить отладочные сообщения мы используем функцию DbgPrint, которая имеет синтаксис, аналогичной функции printf из пользовательского режима (userspace).
    Не совсем — у нее есть одна очень прикольная фенечка для вывода UNICODE_STRING: %wZ
    Есть аналогичная вроде для ANSI_STRING, но ее я не запоминал ввиду не особой её необходимости.

    Для отладки еще потребуется Debugging tools for Windows и символы для компонентов ядра.
    И виртуальная машина. Дебажить дрова на той же тачке я бы крайне не рекомендовал. Ну и соотв. DbgView тогда не потребуется
  • +2
    ДА! Даёшь статьи по WDK!
  • 0
    кстати, стоит заметить:

    >> В этом простом примере мы использовали также директивы #pragma alloc_text(INIT, DriverEntry) и #pragma alloc_text(PAGE, UnloadRoutine). Объясню что они означают:… Вторая помечает код функции UnloadRoutine как выгружаемый, т.е. при необходимости, система может переместить его в файл подкачки, а потом забрать его оттуда.

    … заметить, что выгружаемыми по умолчанию считаются все секции, имена которых начинаются на 'PAGE'
    Например, PAGEKD, PAGEMY, PAGE,…
  • 0
    Спасибо за статью, очень полезно и понятно :-)

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