Интернет-магазин

Просмотр корзины
В корзине:

товаров - 0 шт.



§ 23. Динамическая загрузка драйвера в память с помощью SСManager

Дмитрий Иванов, Январь 2007
Статья впервые опубликованна 14 Мая 2014

Файлы к статье скачать

При разработке драйверов постоянно возникает необходимость их тестирования. Но к сожалению, при каждом изменении в драйвере приходится перезагружать компьютер, чтобы обновленная версия драйвера была загружена в память. Это мягко скажем не удобно, т.к. из-за 2-3 строчек кода приходится "простаивать" несколько минут, пока компьютер не перезагрузится.


Но есть интересное решение этой проблемы - использование функций менеджера служб ОС Windows (Service Control Manager). С его помощью можно из пользовательского приложения динамически загрузить драйвер в память, и так же по требованию выгрузить. Например, запускаем приложение, загружаем драйвер в память. Проводим необходимые испытания. Далее выгружаем драйвер из памяти. Делаем какие-либо изменения в коде драйвера, компилируем его. Опять стартуем приложение, снова загружаем драйвер и т.д. Хочу напомнить что в текущем варианте способа загрузки драйвера (через реестр) он загружается в память один раз при запуске ОС и присутствует там в течение всей работы ОС, пока компьютер не выключат или перезагрузят.

Ну что же, давайте приступим к осовоению это SСManager`а. Первым делом, нам надо избавиться от сидящего на данный момент в памяти драйвера Port.sys, который Вы наверняка уже откомпилировали и установили с ситеме (см. предыдущие статьи). Для этого удалите файл Port.sys из папки C:\Windows\system32\drivers\ и обязательно записи из системного реестра. В программе редактора реестра regedit.exe удалите ветвь HKEY_LOCAL_MACHINE\System\CurrentControlSet \Services\Port. Перезагрузите компютер. Все, теперь драйвер "ликвидирован" и мешать нам не будет. Если бы мы этих операций не сделали, то при попытке загружать драйвер динамически поверх уже существующего в памяти в лучшем случае ничего просто не сработает, а в худшем произойдет системный сбой с аварийным выходом.

Двигаемся дальше. Давайте напишем приложение, которое будет использовать функции SСManager для загрузки драйвера. Обязательно скачайте файлы к этой статье. Там Вы найдете готовый проект приложения. Сначала рассмотрим файл TestDriver.cpp. В начале работы программы вызывается функция InstallDriverWithSCManager() которая проводит все необходимые манипуляции с драйвером по его загрузке. Она расположена в другом файле и о ней мы поговорим попозже. Далее, если установка прошла удачно, работает DOS-меню, где вызывается либо функция записи в порт или функция выгрузки драйвера (реализованы в другом файле).

#include <windows.h>    
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
#include "h.h"

int main()
{
 int Address=888;
 cout<<"---Test SCManager with Port.sys---"<<endl;
 cout<<" "<<endl;
	
 bool status=InstallDriverWithSCManager();


 if (status==false)
 {
	cout<<"Error! Can't open driver! 
                 Press any key to exit... "<<endl;
	getch();
	return false;
 }
	
 cout<<"Press '1' to activate and '0' to disactivate lightdiod."<<endl;
 cout<<"To exit press 'e'."<<endl;
 cout<<" "<<endl;

 while(1)
 {		
	switch(getch())
	{

	case '0':	cout<<"Disactivate...."<<endl;
					WriteToPort(Address, 0);
					break;	 
						
	case '1': 	cout<<"Activate...."<<endl;
					WriteToPort(Address, 1);
					break;		

	case 'e':	cout<<" Press any key to exit..."<<endl;
					getch();
					StopDriver();
					return true;
					break;
	}
 }
	
 return true;
}

Здесь ничего сложного нет, а вот следующий файл CSManager.cpp немного понавороченнее. В нем реализованы обращения к SCManager, позволяющие динамически загружать и выгружать наш драйвер Port.sys, написанный нами в прошлых статьях. Основной функцией является InstallDriverWithSCManager(), которая содержит последовательную цепочку вызовов вспомогательных функций. Когда Вы будете изучать данный код, обратите внимание на имя MYDRIVER, которое переодически встречается в вызовах функций. Именно таким мы определили символическое имя драйвера в его коде. Основа кода по загрузке/выгрузке драйвера позаимствована из книги В.Несвижского "Программирование аппаратных средств в Windows" с некоторыми изменениями и адаптацией к нашему примеру.

#include <windows.h>    
#include <windowsx.h>  
#include <stdio.h> 

#define IOCTL_READ (0x800<<2)|(0x22<<16)
#define IOCTL_WRITE (0x801<<2)|(0x22<<16)

bool _loadService ( PSTR pszDriver );
bool _goService ( );   // запуск сервиса
bool _stopService ( ); // остановка сервиса
bool _freeService ( ); // закрытие сервиса
bool InstallDriverWithSCManager();
bool StopDriver();
void WriteToPort(USHORT PortAdr, USHORT PortValue);
USHORT ReadFromPort(USHORT PortAdr);
HANDLE hDrv;
DWORD cbRet;

bool StopDriver()
{
	CloseHandle(hDrv);
	_stopService ( ); // остановка сервиса
	_freeService ( ); // закрытие сервиса
	return true;
}

bool InstallDriverWithSCManager()
{
    bool bResult;
    PSTR pszTemp;
    char szExe[MAX_PATH];
    

    // открываем драйвер
    hDrv = CreateFile ( "\\\\.\\MYDRIVER", GENERIC_READ | GENERIC_WRITE,
                  0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
    // если не удалось, инициализируем службу сервисов
    if ( hDrv == INVALID_HANDLE_VALUE )
    {
      // получаем имя программы
      if ( !GetModuleFileName ( GetModuleHandle ( NULL ), szExe,
                                sizeof ( szExe ) ) )
            return false;

      // ищем указатель на последнюю косую черту
      pszTemp = strrchr ( szExe, '\\' );

      // убираем имя программы
      pszTemp[1] = 0;

      // а вместо него добавляем имя драйвера
      strcat ( szExe, "Port.sys" );

      // загружаем сервис
      bResult = _loadService ( szExe ); 

      // если ошибка, выходим из функции
      if ( !bResult ) return false;

      // запускаем наш сервис
      bResult = _goService ( );

      // если ошибка, выходим из функции
      if ( !bResult ) return false;

      // открываем драйвер
      hDrv = CreateFile ( "\\\\.\\MYDRIVER", GENERIC_READ |
    GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

       // если не удалось, выходим из функции
       if ( hDrv == INVALID_HANDLE_VALUE ) return false;
    }
    return true;	
}



bool  _loadService ( PSTR pszDriver )
{
  SC_HANDLE hSrv;
  SC_HANDLE hMan;

  // на всякий случай выгружаем открытый сервис
  _freeService ( ); 

  // открываем менеджер сервисов
  hMan = OpenSCManager ( NULL, NULL, SC_MANAGER_ALL_ACCESS );
  // создаем объект сервиса из нашего драйвера
  if ( hMan )
  {
   hSrv = CreateService ( hMan, "MYDRIVER", "MYDRIVER",
   SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START,
   SERVICE_ERROR_NORMAL, pszDriver, NULL, NULL, NULL, NULL, NULL );

   // освобождаем менеджер объектов
   CloseServiceHandle ( hMan );
   if ( hSrv == NULL ) return false;
  }
  else
   return false;

  CloseServiceHandle ( hMan );
  return true;
}

bool  _goService () 
{
   bool bRes;
   SC_HANDLE hSrv;
   SC_HANDLE hMan;

   // открываем менеджер сервисов
   hMan = OpenSCManager ( NULL, NULL, SC_MANAGER_ALL_ACCESS );
   if ( hMan )
   {
     // открываем сервис
     hSrv = OpenService ( hMan, "MYDRIVER", SERVICE_ALL_ACCESS );

     // закрываем менеджер сервисов
     CloseServiceHandle ( hMan );
     if ( hSrv )
     {
      // запускаем сервис
      bRes = StartService ( hSrv, 0, NULL );

      // в случае ошибки закрываем дескриптор
      if( !bRes )
           CloseServiceHandle ( hSrv );
     }
     else
      return false;
   }
   else
    return false;

   return bRes;
}

bool  _stopService ( )
{
   bool bRes;
   SERVICE_STATUS srvStatus;
   SC_HANDLE hMan;
   SC_HANDLE hSrv;

   // открываем менеджер сервисов
   hMan = OpenSCManager ( NULL, NULL, SC_MANAGER_ALL_ACCESS );
   if ( hMan )
   {
     // открываем сервис
     hSrv = OpenService ( hMan, "MYDRIVER", SERVICE_ALL_ACCESS );

     // закрываем менеджер сервисов
     CloseServiceHandle ( hMan );
     if ( hSrv )
     {
      // останавливаем сервис
      bRes = ControlService ( hSrv, SERVICE_CONTROL_STOP, &srvStatus );

      // закрываем сервис
      CloseServiceHandle ( hSrv );
     }
     else
      return false;
   }
   else
    return false;
   return bRes;
}

bool _freeService ( )
{
   bool bRes;
   SC_HANDLE hSrv;
   SC_HANDLE hMan;

   // останавливаем наш сервис
   _stopService ( );

   // открываем менеджер сервисов
   hMan = OpenSCManager ( NULL, NULL, SC_MANAGER_ALL_ACCESS );
   if ( hMan )
   {
     // открываем сервис
     hSrv = OpenService ( hMan, "MYDRIVER", SERVICE_ALL_ACCESS );

     // закрываем менеджер сервисов
     CloseServiceHandle ( hMan );
     if ( hSrv )
     {
       // удаляем наш сервис из системы и освобождаем ресурсы
       bRes = DeleteService ( hSrv );

       // закрываем дескриптор нашего сервиса
       CloseServiceHandle ( hSrv );
     }
     else
       return false;
   }
   else
    return false;
   return bRes;
}


void WriteToPort(USHORT PortAdr, USHORT PortValue)
{
	USHORT	DataToDriver[2];
		DataToDriver[0]=PortAdr;   //адрес порта куда писать
		DataToDriver[1]=PortValue; //что писать в порт
	DeviceIoControl(hDrv,IOCTL_WRITE,DataToDriver,4,NULL,0,&cbRet,NULL);
}

USHORT ReadFromPort(USHORT PortAdr)
{
	USHORT	DataToDriver[1];
		DataToDriver[0]=PortAdr;   //адрес порта откуда читать

	USHORT DataFromDriver; //куда записать результат чтения	
	DeviceIoControl(hDrv,IOCTL_READ,DataToDriver,2,
                                   &DataFromDriver,2,&cbRet,NULL);
	return DataFromDriver;
}

Особенно вникать в функционирование этих функций не обязательно, они выполняют много муторной черновой работы, и непосредственно применять следует только StopDriver() и InstallDriverWithSCManager(). Также, в этом файле реализованы две функции, непосредственно предназначенные для практической работы с драйвером. Это WriteToPort() и ReadFromPort() (я думаю, их функциональное назначение очевидно из названия). Их код аналогичен коду из прошлого примера, когда мы проводили первое тестирование драйвера Port.sys.

Итак, давайте протестируем это приложение. Откомпилируйте его, но пока не запускайте. Обязательно положите в оду директорию с откомпилированной программой наш драйвер Port.sys (как видно из кода загрузчика драйвера, предполагается, что он находится в той же директории что и программа). Запускаем приложение, и если все в порядке, мигаем светодиодом:

Можно посмотреть, что наш драйвер действительно загружен в память. Зайдите Пуск -> Программы -> Стандарные -> Служебные -> Сведения о системе. В ветке Программная среда -> Системные драйверы можно найти наш запущенный драйвер с символическим имененм MYDRIVER.

Теперь давайте выгрузим драйвер из памяти. Для этого в программе надо нажать клавишу "e". При этом будет вызвана функция StopDriver(), которая проведет все необходимые действия с драйвером по его корректной выгрузке из памяти. Если теперь посмотреть Сведения о системе, то там записей о нашем драйвере не окажется (нажмите Вид -> Обновить). Обращаю Ваше внимание на то, что завершение работы нашего приложения обязательно должно заканчиваться вызовом функции по остановке драйвера. Если Вы например, закроете окно программы нажав на "крестик" в верхнем правом углу, то StopDriver() не отработает и драйвер останется в памяти. При этом, при повторном запуске приложения, драйвер в память загружаться не будет, т.к. он там уже есть.

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



© Дмитрий Иванов
Январь 2007
http://www.kernelchip.ru



© KERNELCHIP 2006 - 2017