Библиотека Интернет Индустрии I2R.ru |
|||
|
Драйверы режима ядра Windows 2000: Часть 2: Службы"Ну вот! Начали за здравие, кончили за упокой. При чем тут службы?" - спросите вы... и будете неправы. Очень даже при чем. Я было начал эту статью с описания простейшего драйвера, но, по ходу дела, был вынужден отвлекаться на то, чтобы объяснять, как его зарегистрировать, запустить и т.д. и т.п. Тогда я решил, что будет логичнее, сначала поведать о том, как драйверы регистрируются, запускаются: Но тут возникла похожая проблема. Мне пришлось говорить о том, к каким действиям еще не написанного драйвера, приведет тот или иной вызов диспетчера управления службами или диспетчера ввода-вывода. Тут уж ничего не поделаешь. Слишком тесно эти компоненты связаны друг с другом. Как бы там ни было, но я остановился на втором варианте: сначала я глаголю о диспетчере управления службами, потом, в следующей статье, о простейшем драйвере, затем о диспетчере ввода-вывода, и наконец, разберем полнофункциональный драйвер. Когда вы прочитаете последующие статьи, будет неплохо вернуться к предыдущим - тогда многое встанет на свои места. Так что запаситесь терпением. Поскольку у меня нет ни малейшего желания выступать в качестве
переводчика официальной документации Microsoft, по крайней мере
безвозмездно, то информацию о функциях, которыми мы будем пользоваться,
принимаемых ими параметрах и их значениях, я буду давать лишь в объеме,
необходимом для реализации наших целей. За подробностями обращайтесь к
MSDN, API Reference и DDK. Службы Службы (Services)- это процессы пользовательского режима, для запуска и функционирования которых регистрация интерактивного пользователя в системе не требуется. И поэтому, подавляющее большинство служб не имеют пользовательского интерфейса. Это единственная категория пользовательских приложений, которые могут работать в таком режиме. Службы используются, например, для реализации серверов. Они могут запускаться, как при загрузке операционной системы, так и после нее. И в этом смысле драйверы, действительно, очень похожи на службы. Кстати сказать, это понятие часто переводят и как "сервис" (если это можно назвать переводом), но я буду употреблять именно слово "служба", т.к. являюсь противником привнесения в наш "великий и могучий" всякого заморского мусора - менеджер вместо управляющий, офис вместо контора и т.п. Термином "служба" обозначаются не только собственно службы, но и драйверы устройств. Microsoft свалила, зачем-то в одну кучу службы, выполняющиеся в режиме пользователя, и драйверы устройств, работающие в режиме ядра. В связи с этим дальнейшее повествование может показаться несколько запутанным, т.к. я буду говорить то "драйвер", то "служба". Но, в контексте этой статьи, можно считать эти два слова синонимами. Там, где необходимо отделить эти два понятия друг от друга, я буду делать явную оговорку. Также имейте в виду, что документация, описывающая функции управления службами, порой, весьма неоднозначна. Нас в этой неразберихе выручает то обстоятельство, что нам потребуется всего несколько функций. Причем, набор передаваемых им параметров, будет практически один и тот же. Достаточно написать прототип программы управления, и тупо использовать его для управления всеми драйверами, внося в код лишь микроскопические изменения. Для запуска и управления драйвером необходимы три компонента:
Как я уже сказал, драйвер мы рассмотрим в следующий раз, а сейчас
остановимся на первых двух компонентах. Диспетчер управления службами На конечном этапе загрузки системы, перед появлением диалога регистрации пользователя, запускается SCM (\%SystemRoot%\System32\Services.exe), который, просматривая раздел реестра HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\, создает свою внутреннюю базу данных (ServicesActive database или SCM database). Далее SCM находит в созданной базе все драйверы устройств и службы, помеченные для автоматического запуска, и загружает их. Чтобы получить кое-какое представление об этом, запустите редактор реестра (\%SystemRoot%\regedit.exe), откройте раздел HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ и изучите его содержимое. Теперь запустите оснастку Администрирование > Службы (Administrative Tools > Services). Вы увидите список установленных служб (именно служб, а не драйверов). Чтобы просмотреть список загруженных драйверов, запустите Администрирование > Управление компьютером (Administrative Tools > Computer Management) и в левом окне откройте ветвь Служебные программы > Сведения о системе > Программная среда > Драйверы (System Tools > System Information > Software Environment > Drivers). Проанализировав содержимое этих трех окон, вы заметите, что они во многом совпадают. Вышеупомянутый раздел реестра содержит подразделы, обозначенные внутренним именем драйвера или службы. Соответственно, каждый подраздел содержит сведения о конфигурации драйвера или службы. Рассмотрим минимально возможный набор параметров, необходимых для запуска драйвера. Более подробно можно почитать тут: Windows 2000 DDK > Setup, Plug Play, Power Management > Design Guide > Reference > Part3: Setup > 1.0 INF File Sections and Directives > INF AddService Directive. В качестве примера, возьмем простейший драйвер режима ядра beep.sys (о нем самом мы поговорим в следующий раз). Подраздел реестра соответствующий этому драйверу и его содержимое представлен на рис 2-1.
Рис. 2-1. Подраздел реестра для драйвера beep.sys
В подразделе Security хранится контекст безопасности выполнения службы. По умолчанию контекст безопасности соответствует LocalSystem. Содержимое подраздела Enum используется при перечислении драйверов и устройств. Эти подразделы создаются автоматически, но нам они не интересны. Таким образом, из рис 2-1 можно извлечь следующую информацию: драйвер режима ядра beep.sys, находящийся в каталоге C:\masm32\mProgs\Ring0\Kmd\Article2\beep и имеющий экранное имя "Nice Melody Beeper", запускается по требованию, возможные ошибки игнорируются и не заносятся в журнал событий системы. Что такое \??, в начале пути к файлу драйвера, я расскажу в следующих статьях. Не следует думать, что если после загрузки системы этих записей в
реестре не окажется, то все пропало. Ничего подобного. Если мы хотим
запустить драйвер, сведения о котором отсутствуют в реестре, то тут нет
никаких проблем. Это можно сделать и динамически, в любой момент, с
помощью программы управления службой (правильнее было бы назвать ее
программой управления драйвером, но такого понятия в терминологии
Microsoft нет), к рассмотрению которой мы и переходим. Программа управления службой Как следует из самого названия, программа управления службой (далее SCP) призвана выполнять некие действия по отношению к драйверу. Делает она это под наблюдением SCM, вызывая соответствующие функции. Все они экспортируются модулем \%SystemRoot%\System32\advapi.dll (Advanced API). Вот код простейшей SCP, которая будет управлять драйвером beep.sys. Находится в файле scp.asm.
Первое, что нам необходимо сделать - это, пользуясь терминологией Microsoft, установить канал связи с SCM, используя функцию OpenSCManager, прототип которой выглядит так:
Мы устанавливаем канал связи с SCM таким образом:
Если канал связи с SCM успешно установлен, функция OpenSCManager вернет описатель (handle), предоставляющий доступ к активной базе данных SCM, который мы сохраняем в переменной hSCManager для дальнейшего использования. Кстати, совсем забыл сказать. Чтобы убедиться на практике, что все о чем я буду говорить правда, нам потребуются права администратора. Ведь, чтобы только получить доступ к SCM для регистрации драйвера, не говоря уже обо всем остальном, естественно надо иметь некоторые привилегии. Так что будем считать, что они у нас есть. Получив доступ к базе SCM, мы регистрируем в ней свой драйвер beep.sys, с помощью функции CreateService. Вот прототип этой функции. Выглядит пугающе, с первого взгляда, но на самом деле все довольно просто.
Для того, чтобы с функцией CreateService совсем все стало ясно, я позволю себе подытожить. Последние пять параметров мы сразу устанавливаем в NULL, и напрочь забываем об их существовании. Первым параметром является описатель базы данных SCM, полученный на предыдущем этапе. С четвертым параметром dwDesiredAccess, тоже, надеюсь, все ясно. Для чего нужны остальные параметры, я думаю, вы уже догадались...
Правильно. Они соответствуют параметрам в подразделе реестра, которые мы
разобрали выше. Для наглядности я свел их в таблицу.
Таблица 2-1. Соответствие некоторых параметров функции CreateService, ключам реестра. Как видите, все не так уж сложно. Вернемся к исходному коду.
Вызовом тривиальной функции GetFullPathName, мы формируем строку с полным путем к файлу драйвера, состоящую из текущего каталога и имени файла драйвера, и передаем ее в функцию CreateService. CreateService регистрирует в базе данных SCM новый драйвер, и заполняет соответствующий подраздел реестра. Посмотрев еще раз на рис. 2-1, вы увидите там результаты работы CreateService. Если вы закомментарите вызов функции DeleteService, перекомпилируете csp.asm и запустите, то можно даже будет посмотреть на этот раздел реестра вживую, на вашей машине. Не следует думать, что воспользовавшись стандартными функциями по работе с реестром, можно достичь того же самого результата. Записи в реестр будут добавлены, но нет никакой гарантии, что они окажутся во внутренней базе SCM. Если драйвер, который мы пытаемся зарегистрировать, уже существует, то вызов CreateService, естественно, завершится неудачей. Последующий вызов функции GetLastError вернет ERROR_SERVICE_EXISTS. Если же все ОК, то мы получим описатель вновь созданной службы, и поместим его в переменную hService. Он понадобится для дальнейших манипуляций, первой из которых будет запуск драйвера функцией StartService, прототип которой выглядит следующим образом:
И запускаем драйвер таким образом:
Функция StartService заставляет систему произвести действия, очень
сильно напоминающие загрузку обыкновенной DLL. Образ файла драйвера
проецируется на системное адресное пространство. При этом, возможности
управлять адресом загрузки нет никакой. Да это и не нужно.
Предопределенный адрес загрузки (preferred base address) у всех
наших драйверов будет равен 10000h, что значительно ниже начала системного
диапазона адресов. Пытаться установить его в какое-то другое значение не
имеет смысла, т.к. система, все равно, будет загружать драйвер по
случайному (для нас) адресу. Поскольку фактический адрес загрузки не
совпадает с предопределенным, система производит настройку адресов
пользуясь таблицей перемещений (relocation table), находящейся в
секции .reloc файла драйвера. Затем производится связывание
(fix-up) импорта. Вызов StartService синхронный. Это значит, что она не вернет управление до тех пор, пока не отработает процедура DriverEntry в драйвере. Если инициализация драйвера прошла успешно, DriverEntry вернет STATUS_SUCCESS, а функция StartService вернет значение отличное от нуля. И мы вновь окажемся в контексте потока вызвавшего StartService, т.е. в контексте нашей SCP. Вызов StartService может завершиться неудачей, если база данных SCM заблокирована. Последующий вызов функции GetLastError вернет ERROR_SERVICE_DATABASE_LOCKED. Как написано в документации, в этом случае, следует подождать несколько секунд, и повторить попытку, но мы этого делать не будем, т.к. это крайне маловероятно. И вообще, нас не интересует возвращаемое функцией StartService значение, т.к. beep.sys уже проиграл свою дивную мелодию и вернул код ошибки. Так что, мы заранее знаем, что вызов StartService даст ошибку.
Осталось привести систему в исходное состояние. Надеюсь, что впечатление от столь виртуозного исполнения на системном динамике, останется с вами навсегда ;-) Вызовом функции DeleteService мы удаляем сведения о драйвере из базы данных SCM. Странно, но передавать описатель самой базы данных SCM в функцию DeleteService не нужно. Прототип функции DeleteService прост:
На самом деле, функция DeleteService ничего ниоткуда не удаляет. Она только сообщает системе, что это можно сделать, когда наступит благоприятный момент. А он наступит тогда, когда все описатели службы будут закрыты. Т.к. мы все еще держим описатель hService открытым, то удаления не происходит. Если попытаться вызвать DeleteService повторно, то он завершится неудачей, а последующий вызов функции GetLastError вернет ERROR_SERVICE_MARKED_FOR_DELETE. Вызовом функции CloseServiceHandle мы закрываем описатель hService. Прототип функции CloseServiceHandle также тривиален:
Поскольку больше открытых описателей службы нет, то именно в этот
момент система приводит базу данных SCM в исходное состояние. Второй вызов
CloseServiceHandle закрывает описатель hSCManager самого SCM.
Макросы для определения строк Теперь разберемся, что такое $CTA0. Это макро-функция, позволяющая определять строки. Masm обладает мощным препроцессором, возможностями которого грех не воспользоваться. Этот макрос не единственный. В файле Strings.mac (\Macros\Strings.mac) находится целая коллекция подобных макросов, на все случаи жизни. Ну... почти на все. Поскольку это не имеет непосредственного отношения к драйверам, я не буду особо растекаться мыслью по древу. В самом начале Strings.mac находятся достаточно подробные инструкции, как пользоваться макросами, правда, на английском языке. Если вы с ним не знакомы, то большое количество примеров позволит вам ухватить суть. Я начал создавать эти макросы довольно давно. Прототипом послужили идеи
Свена Шрайбера (когда то давно, и он кодил на асме, но потом пересел на
с). Постепенно мои макросы обрастали разного рода усовершенствованиями,
пока не приобрели достаточно законченный вид. Я до сих пор, изредка, к ним
возвращаюсь и что-нибудь улучшаю (как мне кажется ;-) ). Приглядитесь к
ним. Они действительно очень удобны. И чтобы доказать это, я буду ими
усиленно пользоваться. Ну, и самое последнее на сегодня. Я, конечно, не могу заставить вас мучиться ожиданием следующей статьи. Поэтому, в архиве к этой статье, помимо исходных кодов SCP, содержится также и откомпилированный драйвер beep.sys. Несмотря на наличие числа 2000 в заголовке статьи, этот драйвер прекрасно работает и под XP. Думаю будет работать и под NT4.0, но возможности проверить это у меня нет. Four-F |
|
2000-2008 г. Все авторские права соблюдены. |
|