Драйверы режима ядра: Часть 2: Службы — Архив WASM.RU

Все статьи

Драйверы режима ядра: Часть 2: Службы — Архив WASM.RU

"Ну вот! Начали за здравие, кончили за упокой. При чем тут службы?" - спросите вы... и будете неправы. Очень даже при чем.

Я было начал эту статью с описания простейшего драйвера, но, по ходу дела, был вынужден отвлекаться на то, чтобы объяснять, как его зарегистрировать, запустить и т.д. и т.п. Тогда я решил, что будет логичнее, сначала поведать о том, как драйверы регистрируются, запускаются… Но тут возникла похожая проблема. Мне пришлось говорить о том, к каким действиям еще не написанного драйвера, приведет тот или иной вызов диспетчера управления службами или диспетчера ввода-вывода. Тут уж ничего не поделаешь. Слишком тесно эти компоненты связаны друг с другом. Как бы там ни было, но я остановился на втором варианте: сначала я глаголю о диспетчере управления службами, потом, в следующей статье, о простейшем драйвере, затем о диспетчере ввода-вывода, и наконец, разберем полнофункциональный драйвер. Когда вы прочитаете последующие статьи, будет неплохо вернуться к предыдущим - тогда многое встанет на свои места. Так что запаситесь терпением.

Поскольку у меня нет ни малейшего желания выступать в качестве переводчика официальной документации Microsoft, по крайней мере безвозмездно, то информацию о функциях, которыми мы будем пользоваться, принимаемых ими параметрах и их значениях, я буду давать лишь в объеме, необходимом для реализации наших целей. За подробностями обращайтесь к MSDN, API Reference и DDK.


Службы

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

Кстати сказать, это понятие часто переводят и как "сервис" (если это можно назвать переводом), но я буду употреблять именно слово "служба", т.к. являюсь противником привнесения в наш "великий и могучий" всякого заморского мусора - менеджер вместо управляющий, офис вместо контора и т.п.

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

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

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

  • Диспетчер управления службами (Service Control Manager, SCM). Именно благодаря ему мы будем иметь возможность легко и просто загружать наши драйверы;
  • Программа управления службой (Service Control Program, SCP). Работает в тесной связке с SCM;
  • Собственно сам драйвер.

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


Диспетчер управления службами

На конечном этапе загрузки системы, перед появлением диалога регистрации пользователя, запускается 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



DisplayName

- Так называемое экранное имя, отображаемое служебными приложениями на экране.
Этот параметр не обязателен. При его отсутствии экранным именем считается имя подраздела.


ErrorControl

- Определяет, каким образом реагировать на ошибки. Для нас могут представлять интерес два значения:

SERVICE_ERROR_IGNORE (0)

- возвращаемый драйвером код ошибки игнорируется. Предупреждения не выводятся и не регистрируются;

SERVICE_ERROR_NORMAL (1)

- если драйвер, в ответ на команду запуска SCM, сообщает об ошибке, выводится предупреждение и ошибка регистрируется в журнале событий системы (system event log) с указанием причины сбоя.

Журнал событий системы можно просмотреть стандартными средствами Windows 2000: Администрирование > Просмотр событий (Administrative Tools > Event Viewer).

Драйвер beep делает всю полезную работу на этапе инициализации (в процедуре DriverEntry), после чего возвращает код ошибки, чтобы быть удаленным из памяти, т. к. делать ему там больше нечего. Поскольку параметр ErrorControl для него равен SERVICE_ERROR_IGNORE (0), то никаких записей в журнале событий системы не производится. Зачем нам лишние логи.


ImagePath

- Путь к исполняемому файлу драйвера или службы.
В отличие от служб, для драйверов не обязательно указывать значение этого параметра, но тогда файл драйвера должен находиться в каталоге \%SystemRoot%\System32\Drivers.


Start

- Указывает как нужно запускать драйвер.
Нам могут оказаться полезными только два значения:

SERVICE_AUTO_START (2)

- драйвер запускается автоматически на этапе загрузки системы. Но запуск такого драйвера не обязателен для самой системы, поэтому происходит на позднем этапе загрузки;

SERVICE_DEMAND_START (3)

- драйвер запускается по требованию.

Все драйверы устройств, у которых параметр Start равен SERVICE_AUTO_START (2), автоматически запускаются диспетчером управления службами при загрузке системы. Это автоматически запускаемые службы (auto-start services). При этом запускаются и все драйверы устройств, от которых данный драйвер зависит, даже если параметр Start у них не указывает на автоматическую загрузку. (Для определения порядка своей загрузки драйверы устройств могут использовать параметры Group и Tag, а сервисы DependOnGroup и DependOnService, но нам они не понадобятся.)
Есть и другие значения, указывающие на автоматический запуск, например SERVICE_BOOT_START (0). Это значение могут использовать только драйверы. В этом случае драйвер будет загружен загрузчиком операционной системы, еще до того, как будет запущен SCM, и даже диспетчер ввода-вывода (I/O Manager). Но это нам тоже не интересно.

Автоматический запуск драйвера иногда называют статической загрузкой, по аналогии со статически загружаемыми VxD в Windows 9x. Это не правильно, т.к. автоматически загруженный драйвер можно выгрузить и удалить из базы данных SCM в любой момент.

Драйверы, которые имеют параметр Start равным SERVICE_DEMAND_START (3), попадают в базу данных SCM, и считаются зарегистрированными (installed services). Это службы запускаемые по требованию (demand-start services). Их можно запустить в любой момент, вызвав функцию ServiceStart.


Type

- Определяет тип службы.
Нас интересует только значение SERVICE_KERNEL_DRIVER (1).

В подразделе 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.


 .386
 .model flat, stdcall
 option casemap:none

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                  I N C L U D E   F I L E S                                        
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 include \masm32\include\windows.inc

 include \masm32\include\kernel32.inc
 include \masm32\include\user32.inc
 include \masm32\include\advapi32.inc

 includelib \masm32\lib\kernel32.lib
 includelib \masm32\lib\user32.lib
 includelib \masm32\lib\advapi32.lib

 include \masm32\Macros\Strings.mac

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                         C O D E                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .code

 start proc

 LOCAL hSCManager:HANDLE
 LOCAL hService:HANDLE
 LOCAL acDriverPath[MAX_PATH]:CHAR

     invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
     .if eax != NULL
         mov hSCManager, eax

         push eax
         invoke GetFullPathName, $CTA0("beep.sys"), sizeof acDriverPath, addr acDriverPath, esp
         pop eax

         invoke CreateService, hSCManager, $CTA0("beep"), $CTA0("Nice Melody Beeper"), \
                 SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
                 SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
         .if eax != NULL
             mov hService, eax
             invoke StartService, hService, 0, NULL
             invoke DeleteService, hService
             invoke CloseServiceHandle, hService
         .else
             invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_ICONSTOP
         .endif
         invoke CloseServiceHandle, hSCManager
     .else
         invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), \
                            NULL, MB_ICONSTOP
     .endif

     invoke ExitProcess, 0

 start endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                                                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 end start

Первое, что нам необходимо сделать - это, пользуясь терминологией Microsoft, установить канал связи с SCM, используя функцию OpenSCManager, прототип которой выглядит так:


 OpenSCManager proto lpMachineName:LPSTR, lpDatabaseName:LPSTR, dwDesiredAccess:DWORD

lpMachineName

- указатель на завершающуюся нулем строку, содержащую имя компьютера.
Этот параметр мы сразу устанавливаем в NULL, т. к. будем открывать канал связи с SCM только на локальном компьютере.


lpDatabaseName

- указатель на завершающуюся нулем строку с именем открываемой базы данных.
Может быть либо SERVICES_ACTIVE_DATABASE, либо NULL, что то же самое, т. к. в этом случае будет открыта база SERVICES_ACTIVE_DATABASE.

Символьная константа SERVICES_ACTIVE_DATABASE не определена в файле masm32\include\windows.inc. Это можно сделать так:


 SERVICES_ACTIVE_DATABASE equ $CTA0("ServicesActive")

О том, что такое $CTA0 и что находится в файле Strings.mac поговорим позже.

Т.к. мы не собираемся открывать никакую другую базу данных SCM, кроме активной в данный момент, просто, установим этот параметр в NULL.


dwDesiredAccess

- запрашиваемые права доступа.
Сообщает SCM, что мы собственно намереваемся делать с его базой данных. Нам могут быть полезны три значения:

SC_MANAGER_CONNECT

- доступ на установку канала связи с SCM.
По умолчанию (т.е. если просто передать в этом параметре 0), устанавливается именно это значение. Очень странно, но в документации ничего не говорится, что же конкретно мы можем делать с этим уровнем доступа. А делать можно многое: запускать драйвер, останавливать, и что совсем не понятно, даже удалять сведения о нем из базы данных SCM и из реестра;

SC_MANAGER_CREATE_SERVICE

- доступ на занесение в базу данных SCM нового драйвера. Т. к. мы собираемся занести туда своего представителя, то именно это значение и используем. Можно подумать, что никаких других прав, кроме регистрации нового драйвера, этот флаг не дает. Это не так. Т.к. флаг SC_MANAGER_CONNECT считается установленным по умолчанию, то и соответствующие права нам тоже предоставляются. Что тоже совсем не очевидно.

SC_MANAGER_ALL_ACCESS

- позволяет получить максимальный доступ.

Мы устанавливаем канал связи с SCM таким образом:


 invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
 .if eax != NULL
     mov hSCManager, eax

Если канал связи с SCM успешно установлен, функция OpenSCManager вернет описатель (handle), предоставляющий доступ к активной базе данных SCM, который мы сохраняем в переменной hSCManager для дальнейшего использования.

Кстати, совсем забыл сказать. Чтобы убедиться на практике, что все о чем я буду говорить правда, нам потребуются права администратора. Ведь, чтобы только получить доступ к SCM для регистрации драйвера, не говоря уже обо всем остальном, естественно надо иметь некоторые привилегии. Так что будем считать, что они у нас есть.

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


 CreateService proto hSCManager:HANDLE,     lpServiceName:LPSTR,    lpDisplayName:LPSTR, \
                     dwDesiredAccess:DWORD, dwServiceType:DWORD,    dwStartType:DWORD, \
                     dwErrorControl:DWORD,  lpBinaryPathName:LPSTR, lpLoadOrderGroup:LPSTR, \
                     lpdwTagId:LPDWORD,     lpDependencies:LPSTR,   lpServiceStartName:LPSTR, \
                     lpPassword:LPSTR

hSCManager

- описатель базы данных SCM.
Определяет в какую именно базу мы добавляем сведения о новом драйвере.

lpServiceName

- указатель на завершающуюся нулем строку, содержащую внутреннее имя.
Максимальная длина строки 256 символов. Не допускаются символы "/" и "\". Соответствует имени подраздела в реестре.

lpDisplayName

- указатель на завершающуюся нулем строку, содержащую экранное имя.
Максимальная длина строки 256 символов. Соответствует параметру DisplayName в реестре.

dwDesiredAccess

- тип доступа.
Определяет, какие права по манипулированию вновь созданным сервисом нам будут предоставлены. Для нас могут оказаться полезными следующие значения:

SERVICE_ALL_ACCESS

- позволяет получить максимальный доступ;

SERVICE_START

- доступ на запуск драйвера вызовом функции StartService;

SERVICE_STOP

- доступ на останов драйвера вызовом функции ControlService

DELETE

- доступ на удаление сведений о драйвере из базы данных SCM вызовом функции DeleteService;

Нам потребуется выполнить всего два действия: запустить драйвер и удалить сведения о нем из базы данных SCM, а следовательно, и из реестра. Поэтому в этом параметре мы передаем комбинацию флагов SERVICE_START и DELETE. Останавливать запущенный драйвер нам не потребуется, т. к. его инициализация завершится ошибкой.

dwServiceType

- тип службы (SERVICE_KERNEL_DRIVER).
Соответствует параметру Type в реестре.

dwStartType

- тип запуска (SERVICE_DEMAND_START).
Соответствует параметру Start в реестре.

dwErrorControl

- степень контроля ошибок (SERVICE_ERROR_IGNORE).
Соответствует параметру ErrorControl в реестре.

lpBinaryPathName

- указатель на завершающуюся нулем строку, содержащую полный путь к исполняемому файлу драйвера.
Соответствует параметру ImagePath в реестре.

lpLoadOrderGroup

- указатель на завершающуюся нулем строку, содержащую имя группы, членом которой является драйвер. Если драйвер не является членом группы, то можно указать NULL или указатель на пустую строку. Пусть так и будет.

lpdwTagId

- указатель на переменную, в которую будет записано уникальное значение тэга, которое идентифицирует группу, указанную в параметре lpLoadOrderGroup. Можно указать в этом параметре NULL. Что мы и делаем.

lpDependencies

- указатель на завершающийся двумя нулями массив разделённых нулями имён драйверов или групп драйверов, которые система должна запустить до запуска этого драйвера. Если драйвер не зависит от других драйверов, то в этом параметре можно указать NULL или указатель на пустую строку. Ставим в NULL

lpServiceStartName

- указатель на завершающуюся нулем строку, которая содержит имя аккаунта, с правами которого будет запущен драйвер. Если тип службы SERVICE_KERNEL_DRIVER, то этот параметр должен содержать имя объекта драйвера, которое используется системой для загрузки. Если используется имя объекта драйвера, созданное подсистемой ввода-вывода, то этот параметр устанавливается равным NULL. Так и сделаем. А что такое имя объекта драйвера, нам еще предстоит узнать.

lpPassword

- указатель на завершающуюся нулем строку, которая содержит пароль к аккаунту, указанному в параметре lpServiceStartName. Если тип службы SERVICE_KERNEL_DRIVER, то этот параметр игнорируется. Ну и хорошо - нам меньше проблем. Раз игнорируется, то тоже передаем NULL.

Для того, чтобы с функцией CreateService совсем все стало ясно, я позволю себе подытожить.

Последние пять параметров мы сразу устанавливаем в NULL, и напрочь забываем об их существовании. Первым параметром является описатель базы данных SCM, полученный на предыдущем этапе. С четвертым параметром dwDesiredAccess, тоже, надеюсь, все ясно.

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

CreateService

Реестр

lpServiceName

Имя подраздела

lpDisplayName

DisplayName

dwServiceType

Type

dwStartType

Start

dwErrorControl

ErrorControl

lpBinaryPathName

ImagePath

Таблица 2-1. Соответствие некоторых параметров функции CreateService, ключам реестра.

Как видите, все не так уж сложно. Вернемся к исходному коду.


 push eax
 invoke GetFullPathName, $CTA0("beep.sys"), sizeof acDriverPath, addr acDriverPath, esp
 pop eax

 invoke CreateService, hSCManager, $CTA0("beep"), $CTA0("Nice Melody Beeper"), \
         SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
         SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
 .if eax != NULL
     mov hService, eax

Вызовом тривиальной функции GetFullPathName, мы формируем строку с полным путем к файлу драйвера, состоящую из текущего каталога и имени файла драйвера, и передаем ее в функцию CreateService.

CreateService регистрирует в базе данных SCM новый драйвер, и заполняет соответствующий подраздел реестра. Посмотрев еще раз на рис. 2-1, вы увидите там результаты работы CreateService. Если вы закомментарите вызов функции DeleteService, перекомпилируете csp.asm и запустите, то можно даже будет посмотреть на этот раздел реестра вживую, на вашей машине.

Не следует думать, что воспользовавшись стандартными функциями по работе с реестром, можно достичь того же самого результата. Записи в реестр будут добавлены, но нет никакой гарантии, что они окажутся во внутренней базе SCM.

Если драйвер, который мы пытаемся зарегистрировать, уже существует, то вызов CreateService, естественно, завершится неудачей. Последующий вызов функции GetLastError вернет ERROR_SERVICE_EXISTS. Если же все ОК, то мы получим описатель вновь созданной службы, и поместим его в переменную hService. Он понадобится для дальнейших манипуляций, первой из которых будет запуск драйвера функцией StartService, прототип которой выглядит следующим образом:


 StartService proto hService:HANDLE, dwNumServiceArgs:DWORD, lpServiceArgVectors:LPSTR

hService

- описатель службы. Этот описатель также можно получить с помощью функции OpenService.

dwNumServiceArgs

- количество строк-аргументов в массиве lpServiceArgVectors. Если lpServiceArgVectors равен NULL, то этот параметр может быть нулевым. Для драйверов это всегда так. И именно так у нас и будет.

lpServiceArgVectors

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

И запускаем драйвер таким образом:


 invoke StartService, hService, 0, NULL

Функция StartService заставляет систему произвести действия, очень сильно напоминающие загрузку обыкновенной DLL. Образ файла драйвера проецируется на системное адресное пространство. При этом, возможности управлять адресом загрузки нет никакой. Да это и не нужно. Предопределенный адрес загрузки (preferred base address) у всех наших драйверов будет равен 10000h, что значительно ниже начала системного диапазона адресов. Пытаться установить его в какое-то другое значение не имеет смысла, т.к. система, все равно, будет загружать драйвер по случайному (для нас) адресу. Поскольку фактический адрес загрузки не совпадает с предопределенным, система производит настройку адресов пользуясь таблицей перемещений (relocation table), находящейся в секции .reloc файла драйвера. Затем производится связывание (fix-up) импорта.
Кстати, импорт в файле драйвера раскинут в секции INIT и .idata. В .idata находится таблица адресов импорта (import address table, IAT). В ней содержатся адреса функций во внешних модулях. Она нужна драйверу постоянно. А в секции INIT содержится остальная часть импорта, необходимая только на этапе загрузки (имена внешних модулей и имена импортируемых функций), после которой, память занимаемая этой секцией освобождается. Когда образ драйвера подготовлен, управление передается на точку входа (entry point) в драйвер, которая находится в процедуре DriverEntry (тут почти полная аналогия с DllMain в обычной DLL). Принципиальная разница, не считая уровня привилегий, в том, что код процедуры DriverEntry всегда выполняется одним из потоков процесса System, и, естественно, в контексте этого процесса.

Вызов StartService синхронный. Это значит, что она не вернет управление до тех пор, пока не отработает процедура DriverEntry в драйвере. Если инициализация драйвера прошла успешно, DriverEntry вернет STATUS_SUCCESS, а функция StartService вернет значение отличное от нуля. И мы вновь окажемся в контексте потока вызвавшего StartService, т.е. в контексте нашей SCP.

Вызов StartService может завершиться неудачей, если база данных SCM заблокирована. Последующий вызов функции GetLastError вернет ERROR_SERVICE_DATABASE_LOCKED. Как написано в документации, в этом случае, следует подождать несколько секунд, и повторить попытку, но мы этого делать не будем, т.к. это крайне маловероятно. И вообще, нас не интересует возвращаемое функцией StartService значение, т.к. beep.sys уже проиграл свою дивную мелодию и вернул код ошибки. Так что, мы заранее знаем, что вызов StartService даст ошибку.


     invoke DeleteService, hService
     invoke CloseServiceHandle, hService
 .endif
 invoke CloseServiceHandle, hSCManager

Осталось привести систему в исходное состояние. Надеюсь, что впечатление от столь виртуозного исполнения на системном динамике, останется с вами навсегда ;-)

Вызовом функции DeleteService мы удаляем сведения о драйвере из базы данных SCM. Странно, но передавать описатель самой базы данных SCM в функцию DeleteService не нужно. Прототип функции DeleteService прост:


 DeleteService proto hService:HANDLE

hService

- описатель службы, подлежащей удалению. Естественно нужно иметь право доступа на удаление. Мы его имеем.

На самом деле, функция DeleteService ничего ниоткуда не удаляет. Она только сообщает системе, что это можно сделать, когда наступит благоприятный момент. А он наступит тогда, когда все описатели службы будут закрыты. Т.к. мы все еще держим описатель hService открытым, то удаления не происходит. Если попытаться вызвать DeleteService повторно, то он завершится неудачей, а последующий вызов функции GetLastError вернет ERROR_SERVICE_MARKED_FOR_DELETE.

Вызовом функции CloseServiceHandle мы закрываем описатель hService. Прототип функции CloseServiceHandle также тривиален:


 CloseServiceHandle proto hSCObject:HANDLE

hSCObject

- описатель SCM или службы, подлежащий закрытию.

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


Макросы для определения строк

Теперь разберемся, что такое $CTA0. Это макро-функция, позволяющая определять строки. Masm обладает мощным препроцессором, возможностями которого грех не воспользоваться. Этот макрос не единственный. В файле Strings.mac (\Macros\Strings.mac) находится целая коллекция подобных макросов, на все случаи жизни. Ну... почти на все. Поскольку это не имеет непосредственного отношения к драйверам, я не буду особо растекаться мыслью по древу. В самом начале Strings.mac находятся достаточно подробные инструкции, как пользоваться макросами, правда, на английском языке. Если вы с ним не знакомы, то большое количество примеров позволит вам ухватить суть.

Я начал создавать эти макросы довольно давно. Прототипом послужили идеи Свена Шрайбера (когда то давно, и он кодил на асме, но потом пересел на с). Постепенно мои макросы обрастали разного рода усовершенствованиями, пока не приобрели достаточно законченный вид. Я до сих пор, изредка, к ним возвращаюсь и что-нибудь улучшаю (как мне кажется ;-) ). Приглядитесь к ним. Они действительно очень удобны. И чтобы доказать это, я буду ими усиленно пользоваться.


Ну, и самое последнее на сегодня. Я, конечно, не могу заставить вас мучиться ожиданием следующей статьи. Поэтому, в архиве к этой статье, помимо исходных кодов SCP, содержится также и откомпилированный драйвер beep.sys. Несмотря на наличие числа 2000 в заголовке статьи, этот драйвер прекрасно работает и под XP. Думаю будет работать и под NT4.0, но возможности проверить это у меня нет.

2002-2013 (c) wasm.ru