Создание VxD на Visual C++ без ассемблерных модулей

Общая схема драйвера VxD

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

Обработчик системных сообщений анализирует код сообщения, переданный в EAX, выделяет интересующие его сообщения, обрабатывает их и возвращает сброшенный флаг CF в случае успеха, и установленный — в случае неудачи. Для всех необрабатываемых сообщений должен возвращаться сброшенный флаг CF.

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

При необходимости драйвер может содержать обработчики запросов V86 и PM API. Для доступа к данным виртуальных машин DOS, указатели на которые могут передаваться в регистрах при запросе, достаточно преобразовать их в линейные 32-разрядные адреса, ибо первый мегабайт адресного пространства текущей виртуальной машины непосредственно «виден» из VxD. Для доступа к данным приложений Win16 потребуется выполнить отображение адресов посредством функции VMM _SelectorMapFlat.

В начало

В начало

Программирование VxD

Средства разработки, включаемые файлы и библиотеки

Минимально необходимый набор включаемых файлов и библиотек содержится в Windows 95 DDK (подкаталоги Inc32 и Lib). Обычно требуется включение хотя бы файлов BASEDEF.H и VMM.H.

Файлы VXDWRAPS.H, CONFIGMG.H и некоторые другие оформлены в стиле обычного языка C, поэтому при включении их в файлы типа CPP директивы #include необходимо помещать внутрь квалификатора extern "C":

extern "C" { 
  #include <vxdwraps.h> 
} 

Файлы из DDK можно включать и в тексты модулей обычных приложений, определив перед этим символическое имя Not_VxD. При этом определяются только полезные константы и типы, а определение специфических для VxD конструкций отключается.

В начало

В начало

Структуры, обычно используемые в VxD

VxD_Desc_Block - блок описателя устройства

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

ULONG DDB_Next;
USHORT DDB_SDK_Version;
USHORT DDB_Req_Device_Number;
UCHAR DDB_Dev_Major_Version;
UCHAR DDB_Dev_Minor_Version;
USHORT DDB_Flags;
UCHAR DDB_Name [8];
ULONG DDB_Init_Order;
ULONG DDB_Control_Proc;
ULONG DDB_V86_API_Proc;
ULONG DDB_PM_API_Proc;
ULONG DDB_V86_API_CSIP;
ULONG DDB_PM_API_CSIP;
ULONG DDB_Reference_Data;
ULONG DDB_Service_Table_Ptr;
ULONG DDB_Service_Table_Size;
ULONG DDB_Win32_Service_Table;
ULONG DDB_Prev;
ULONG DDB_Size;
ULONG DDB_Reserved1;
ULONG DDB_Reserved2;
ULONG DDB_Reserved3;

  • DDB_Next — поле для адреса следующего DDB в списке VMM. Инициализируется нулем.
  • DDB_SDK_Version — версия DDK, с которой построен драйвер. Инициализируется константой DDK_VERSION.
  • DDB_Req_Device_Number — идентификатор устройства. При отсутствии назначенного идентификатора задается нулевое значение.
  • DDB_Dev_Major_Version — старшая часть номера версии драйвера.
  • DDB_Dev_Minor_Version — младшая часть номера версии драйвера.
  • DDB_Flags — для служебных флагов VMM. Инициализируется нулем.
  • DDB_Name — имя устройства, дополненное пробелами до восьми символов.
  • DDB_Init_Order — позиция драйвера в списке загрузки. Если порядок загрузки не важен, используется константа UNDEFINED_INIT_ORDER (нуль).
  • DDB_Control_Proc — адрес функции диспетчера системных сообщений.
  • DDB_V86_API_Proc — адрес функции диспетчера V86 API.
  • DDB_PM_API_Proc — адрес функции диспетчера PM API.
  • DDB_V86_API_CSIP — служебное поле, инициализируется нулем.
  • DDB_PM_API_CSIP — служебное поле, инициализируется нулем.
  • DDB_Reference_Data — служебное поле, инициализируется нулем.
  • DDB_Service_Table_Ptr — указатель таблицы адресов процедур—обработчиков сервисных функций.
  • DDB_Service_Table_Size — количество сервисных функций, реализованных в драйвере.
  • DDB_Win32_Service_Table — служебный указатель таблицы функций Win32, инициализируется нулем.
  • DDB_Prev — поле для адреса предыдущего DDB в списке VMM, инициализируется константой 'Prev'.
  • DDB_Size — размер структуры описателя.
  • DDB_Reserved1 — служебное поле. Инициализируется константой 'Rsv1'.
  • DDB_Reserved2 — служебное поле. Инициализируется константой 'Rsv2'.
  • DDB_Reserved3 — служебное поле. Инициализируется константой 'Rsv3'.
В начало

В начало

Client_Reg_Struc - структура пакета регистров клиента

Описывает состояние регистров процессора в вызвавшей виртуальной машине/приложении (клиенте).

ULONG Client_EDI;
ULONG Client_ESI;
ULONG Client_EBP;
ULONG Client_res0;
ULONG Client_EBX;
ULONG Client_EDX;
ULONG Client_ECX;
ULONG Client_EAX;
ULONG Client_Error;
ULONG Client_EIP;
USHORT Client_CS;
USHORT Client_res1;
ULONG Client_EFlags;
ULONG Client_ESP;
USHORT Client_SS;
USHORT Client_res2;
USHORT Client_ES;
USHORT Client_res3;
USHORT Client_DS;
USHORT Client_res4;
USHORT Client_FS;
USHORT Client_res5;
USHORT Client_GS;
USHORT Client_res6;
ULONG Client_Alt_EIP;
USHORT Client_Alt_CS;
USHORT Client_res7;
ULONG Client_Alt_EFlags;
ULONG Client_Alt_ESP;
USHORT Client_Alt_SS;
USHORT Client_res8;
USHORT Client_Alt_ES;
USHORT Client_res9;
USHORT Client_Alt_DS;
USHORT Client_res10;
USHORT Client_Alt_FS;
USHORT Client_res11;
USHORT Client_Alt_GS;
USHORT Client_res12;

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

Для структуры введен синоним типа (typedef) с именем CRS. В файле VMM.H определены также вспомогательные структуры Client_Word_Reg_Struc и Client_Byte_Reg_Struc и объединение всех трех структур CLIENT_STRUCT.

В начало

В начало

DIOCParams - параметры запроса DeviceIoControl

DWORD  Internal1;
DWORD  VMHandle;
DWORD  Internal2;
DWORD  dwIoControlCode;
DWORD  lpvInBuffer;
DWORD  cbInBuffer;
DWORD  lpvOutBuffer;
DWORD  cbOutBuffer;
DWORD  lpcbBytesReturned;
DWORD  lpoOverlapped;
DWORD  hDevice;
DWORD  tagProcess;

  • VMHandle — идентификатор виртуальной машины, сделавшей запрос.
  • dwIoControlCode — код функции. Константы для определенных в системе кодов функций имеют префикс DIOC_, остальные функции определяются разработчиком.
GETVERSION (0) Открывание и опрос интерфейса. Если драйвер не поддерживает Win32 API, он должен вернуть в EAX ненулевое значение. В противном случае в EAX возвращается нуль, а если задан буфер результата, то в него заносится номер версии драйвера.
CLOSEHANDLE (-1) Закрывание интерфейса. Драйвер должен прервать обработку всех асинхронных запросов по этому устройству и освободить относящиеся к нему ресурсы.
  • lpvInBuffer — указатель исходного буфера.
  • cbInBuffer — размер исходного буфера в байтах.
  • lpvOutBuffer — указатель буфера результата.
  • cbOutBuffer — размер буфера результата в байтах.
  • lpcbBytesReturned — поле для объема в байтах данных, занесенных драйвером в буфер результата.
  • lpoOverlapped — указатель структуры типа OVERLAPPED (описатель адреса внутри файла и/или данных для асинхронной операции).
  • hDevice — идентификатор устройства.
  • tagProcess — идентификатор запроса. Вместе с полем hDevice образует уникальный внутри системы идентификатор запроса, по которому запрос может быть найден и аварийно прерван при получении запроса CLOSEHANDLE.
В начало

В начало

Функции VxD, вызываемые из системы

Диспетчер системных сообщений

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

Код сообщения передается в регистре EAX. При возврате флаг CF должен быть сброшен, если сообщение обработано успешно, и установлен, если произошли ошибка или отказ в обслуживании. Для всех сообщений, которые не обрабатываются данным VxD, должен возвращаться сброшенный флаг CF.

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

В начало

В начало

Обработчики сервисных функций

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

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

Все регистры, не участвующие в возврате результата, должны быть сохранены.

В начало

В начало

Обработчики вызовов API

Обработчики вызовов API получают в регистре EBX идентификатор (handle) текущей виртуальной машины (VM) клиента, а в регистре EBP — адрес структуры регистров клиента.

По стандартному соглашению, при общении к API драйвера в регистре AH передается номер функции, а в AL — номер подфункции. Остальные регистры могут передавать другие параметры запроса.

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

Обработчик API может использовать все регистры, кроме EBP и сегментных.

В начало

В начало

Системные средства поддержки VxD

Некоторые системные сообщения

DEVICE_INIT - инициализация статического драйвера

  • EBX — идентификатор системной виртуальной машины.
  • ESI — адрес командной строки VMM в его PSP. Первый байт строки определяет ее длину.

Сообщение посылается после загрузки статического драйвера для его инициализации.

В начало

В начало

SYS_DYNAMIC_DEVICE_INIT - инициализация динамического драйвера

Сообщение посылается после загрузки динамического драйвера для его инициализации.

В начало

В начало

SYS_DYNAMIC_

В начало

В начало

DEVICE_EXIT - завершение динамического драйвера

Сообщение посылается перед выгрузкой динамического драйвера для завершения его работы.

В начало

В начало

CREATE_VM - создание новой виртуальной машины

  • EBX — идентификатор создаваемой виртуальной машины.

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

В начало

В начало

VM_INIT - инициализация новой виртуальной машины

  • EBX — идентификатор виртуальной машины.

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

Флаг CF всегда должен возвращаться сброшенным.

В начало

В начало

VM_TERMINATE - завершение виртуальной машины

  • EBX — идентификатор завершаемой машины.

Посылается в начале процесса завершения виртуальной машины.

Флаг CF всегда должен возвращаться сброшенным.

В начало

В начало

DESTROY_VM - уничтожение виртуальной машины

  • EBX — идентификатор уничтожаемой машины.

Посылается в конце процесса завершения машины, перед непосредственным удалением ее из системы.

Флаг CF всегда должен возвращаться сброшенным.

В начало

В начало

CREATE_THREAD - создание новой задачи

  • EDI — идентификатор создаваемой задачи.

Посылается при создании в системе новой задачи. Создаваемая задача в этот момент еще не является текущей.

Возврат установленного флага CF предотвращает создание задачи.

В начало

В начало

THREAD_INIT - инициализация новой задачи

EDI — идентификатор задачи.

Посылается при инициализации задачи, в начале ее работы, в контексте задачи (новая задача является текущей).

Флаг CF всегда должен возвращаться сброшенным.

В начало

В начало

TERMINATE_THREAD - завершение задачи

  • EDI — идентификатор завершаемой задачи.

Посылается в начале процесса завершения задачи. Завершаемая задача еще какое-то время может оставаться в системе, пока не будут завершены все ждущие операции ввода/вывода.

Флаг CF всегда должен возвращаться сброшенным.

В начало

В начало

DESTROY_THREAD - уничтожение задачи

  • EDI — идентификатор уничтожаемой задачи.

Посылается в конце процесса завершения задачи, перед непосредственным удалением задачи из системы.

Флаг CF всегда должен возвращаться сброшенным.

В начало

В начало

SYSTEM_EXIT - завершение работы системы

  • EBX — идентификатор системной виртуальной машины.

Посылается в начале процесса завершения работы системы, при запросе закрытия системы (shutdown), перезагрузки (reboot) или при аварийном завершении.

В начало

В начало

W32_DEVICEIOCONTROL - запрос от приложения Win32

  • EBX — идентификатор текущей виртуальной машины.
  • ESI — адрес блока параметров DIOCParams.
  • Сообщение всегда посылается в контексте вызвавшей задачи Win32, так что драйверу напрямую доступно адресное пространство приложения. Драйвер обрабатывает запрос, извлекая из блока параметров и исходного буфера данные запроса, и возвращает в EAX код завершения:

0 — обработка завершена успешно;

-1 — начата асинхронная операция. Возвращается только в том случае, если параметру был задан ненулевой параметр lpoOverlapped.

код ошибки — если операция завершена неудачно.

Вместе с возвратом результата в EAX драйвер может заносить необходимую информацию в буфер результата, если он указан в блоке параметров.

В начало

В начало

Некоторые сервисные функции VMM

VMMCall, VMMJmp, VxDCall, VxDJmp - вызов сервисных функций VxD

void xxxCall (DWORD Service);

void xxxJmp (DWORD Service);

Service — код сервисной функции. Старшие 16 разрядов представляют собой идентификатор VxD, младшие 15 разрядов — номер сервисной функции.

Служит для обращения к сервисным функциям VMM и других VxD. Код функции одновременно определяет и VxD, к которому происходит обращение, и саму функцию этого VxD, так что конструкции VMMxxx и VxDxxx равнозначны. Константы для кодов функций определены во включаемых файлах соответствующих VxD; имена констант функций VxD, отличных от VMM, имеют уточняющие префиксы, например VPICD_Get_Version — запрос номера версии VPICD, драйвера виртуального контроллера прерываний.

Сервисные функции доступны только в драйверах, имеющих идентификаторы. К драйверам без идентификаторов «честный» доступ возможен только со стороны приложений. Со стороны других VxD он возможен лишь путем непосредственного поиска данного VxD в системном списке с последующим прямым доступом к таблице его сервисных функций.

Параметры для сервисной функции, как правило, передаются в регистрах; результаты возвращаются в регистрах и флагах процессора. Некоторые функции рассчитаны на вызов в стиле языков C, с помещением параметров в стек и возвратом результата в EAX. Имена функций, оформленных в стиле C, начинаются со знака подчеркивания (_).

Оба вызова оформляются в виде команды прерывания int 0x20, следом за которой размещается код функции. При первой отработке вызова VMM заменяет эту конструкцию на команду far call/jmp в соответствующий шлюз, что дает экономию времени при последующих вызовах. По этой причине конструкцию Int 20, если она не опознается отладчиком как VMMxxx/VxDxxx, нельзя проходить командой типа Step Over, так как стоп-точка будет установлена отладчиком сразу за командой Int и при этом будет испорчен расположенный за нею код функции. Отладчик SoftICE корректно опознает эти конструкции.

Различие вызовов Call и Jmp состоит в том, что вызов Call запоминает адрес возврата в стеке, а Jmp — нет. Методом Jmp вызываются «фатальные» функции, не требующие возврата, а также функции, после которых нужен возврат сразу к вызвавшей функции, без восстановления регистров и других завершающих действий. Без хорошего понимания механизма работы вызова Jmp лучше ограничиться использованием вызова Call.

В начало

В начало

_SelectorMapFlat - отображение сегментного адреса в линейный

_SelectorMapFlat (
   DWORD VMHandle,
   DWORD Sel,
   DWORD Reserved
 );
  • VMHandle — идентификатор виртуальной машины, которой принадлежит селектор. Если селектор относится к GDT, идентификатор игнорируется.
  • Sel — селектор для отображения.
  • Flags — резервный параметр, должен быть нулевым.

Функция получает параметры в стеке, в стиле языков C, и возвращает в EAX линейный адрес, соответствующий началу сегмента, селектор которого задан параметром Sel, либо — 1 в случае ошибки. При помощи этой функции возможен доступ из VxD к данным приложений Win16.

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

В начало

В начало

Некоторые функции-обертки, определенные в VXDWRAPS

Out_Debug_String - вывод отладочного сообщения

void Out_Debug_String (char *String);

  • String — указатель строки, выводимой в отладочный поток. Для перехода на новую строку используется символ '\n".

Системный отладочный поток можно просматривать отладчиками WDEB386, SoftICE, а также любым отладочным монитором.

В начало

В начало

_Sprintf - форматирование строки

ULONG _Sprintf (char *Buffer, char *Format, ...);

Функция аналогична стандартной функции sprintf языка C.

К сожалению, VMM не предоставляет функции, аналогичной vsprintf, поэтому для реализации функций целевого назначения, в основе которых лежит спецификация формата и список аргументов переменной длины (например, функций отладочного вывода или формирования строк специального вида), приходится использовать _Sprintf, копируя переменную часть списка аргументов (например, 10-20 двойных слов) из стекового кадра целевой функции.

В начало

В начало

Пример построения функции-обертки

Поскольку сервисная функция VMM _SelectorMapFlat не имеет стандартной обертки, возможная функция-обертка для нее могла бы выглядеть следующим образом:

#pragma warning (disable: 4035)  // Блокировка предупреждения о невозврате
 
 _declspec (naked)
 void *SelectorMapFlat (DWORD VM, DWORD Sel, DWORD Rsv = 0) {
 
   VMMJmp (_SelectorMapFlat)  // Передача управления VMM
 
   (void)(VM, Sel, Rsv);                   // Имитация использования параметров
 
 }
 
 #pragma warning (default: 4035)  // Восстановление предупреждений о невозврате  

Квалификатор _declspec (naked) подавляет генерацию пролога/эпилога, так что от функции остается лишь конструкция VMMJmp. При вызове этой функции-обертки параметры VM, Sel и Rsv помещаются в стек, затем туда же помещается адрес возврата, управление передается в функцию-обертку, при этом VMMJmp передает управление сервисной функции VMM _SelectorMapFlat, не запоминая нового адреса возврата в стеке. VMM использует значения параметров из стека, затем выполняет возврат по адресу, находящемуся на верхушке стека, при этом управление сразу возвращается в точку за вызовом функции-обертки, где находится код, удаляющий из стека параметры и использующий значение, возвращенное VMM в EAX.

Такая схема типична для построения функций-оберток, так как в полной мере использует особенности создания стекового кадра в языках C, а также возможности компилятора Visual C++ по оформлению функций. Без использования VMMJmp пришлось бы делать возврат дважды, а без использования квалификатора naked — дважды помещать в стек список параметров.

В начало

В начало

Пример простого VxD

В качестве примера приводится простейший VxD, отслеживающий события создания и уничтожения задач (threads). Единственная цель проекта — иллюстрация построения VxD целиком на C++. Драйвер намеренно сделан максимально простым, чтобы продемонстрировать прозрачность и относительную несложность базового VxD.

Для полного понимания всех рассмотренных вопросов, а также для разработки собственных VxD вам понадобится Windows 9x DDK — набор включаемых файлов и библиотек Microsoft. Примерно до весны 1999 года, когда появился DDK для Windows 98, DDK для Windows 95 распространялся Microsoft свободно и бесплатно; сейчас свободно можно получить только DDK для Windows 98, который гораздо менее удобен для небольших проектов. Однако DDK для Windows 95 до сих пор можно найти в Интернете при помощи поисковых систем — например, http://www.filesearch.ru/ — по ключевым словам Windows, 95, DDK. Объем архива — около 17 Мбайт (полного комплекта DDK для Windows 98 — в несколько раз больше).

При желании можно обойтись без «официальной», полной установки DDK, которая создает среду для разработки, принятую в Microsoft. Достаточно лишь распаковать подкаталоги INC32 и LIB и внести пути к ним в список путей поиска включаемых и библиотечных файлов компилятора (Tools -> Options -> Directories). После этого можно строить практически любые VxD (для некоторых — например, мультимедийных — может понадобиться добавление каталогов INC16, MMEDIA\INC и других специализированных).

В комплект файлов примера включены необходимые для построения файлы из DDK для Windows 95 (basedef.h, vmm.h, vxdwraps.h, vxdwraps.clb). Созданный VxD будет работоспособен под Windows 95, 98 и Me.

Для загрузки и тестирования драйвера можно воспользоваться утилитой Monitor, включенной в комплект примера. Для запуска утилиты достаточно распаковать архив и запустить файл Monitor.exe; в случае возникновения проблем нужно сделать изменения в реестре, запустив файл dbgmsg.reg.

КомпьютерПресс 2'2001

Наш канал на Youtube

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует