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

ОГЛАВЛЕНИЕ

 

Особенности разработки VxD на C++

Среда выполнения

Среда выполнения (working environment) драйвера VxD сильно отличается от среды, в которой выполняются приложения Win32. VxD работают в режиме ядра операционной системы на нулевом уровне привилегий (ring 0), в отличие от приложений, работающих на уровне 3. Для VxD в общем случае недоступны функции Windows API; вместо них необходимо пользоваться специальными сервисными функциями VMM и других VxD.

Стандартные библиотеки

Наличие специализированной среды выполнения означает, что VxD, написанный на C или C++, в общем случае не может пользоваться функциями стандартной библиотеки исполнения (RTL). Возможно использование лишь тех функций, которые заведомо не содержат ссылок к Windows API. Например, в VxD возможно использование функций strlen, strcpy, strset или strcmp, однако функция strcoll в Visual C++ содержит обращения к API, чтобы определить параметры языка, и потому для использования в VxD не годится. То же относится к функциям sprintf, time и многим другим. Среди сервисных функций VMM содержится немало аналогичных по смыслу, но отличных по формату вызова и схеме работы операций.

Вспомогательные функции (wrappers)

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

Обертки для ряда часто используемых сервисных функций VMM и других стандартных VxD определены в соответствующих включаемых файлах из DDK — VMM.H, VTD.H, SHELL.H и пр., а также в файле VXDWRAPS.H. Написание остальных предоставляется разработчику VxD. В конце статьи приведен пример написания обертки SelectorMapFlat.

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

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

Некоторые внутренние процедуры VxD, оформленные в виде функций C++, должны вызываться системой (callback). К ним относятся, например, диспетчер системных сообщений, обработчики сервисных функций, прерываний, событий и т.п. Соглашения о связях в таких вызовах часто рассчитаны на использование языка ассемблера, когда параметры передаются в регистрах, а результат возвращается в регистрах и/или флагах процессора. В таком случае стандартное оформление функции, принятое в C++, может стать существенной помехой.

Расширение Visual C++ содержит квалификатор __declspec (naked), помещение которого в заголовке определяемой функции запрещает генерацию пролога/эпилога функции — начальной (сохранение регистров, установка указателя кадра) и завершающей (восстановление регистров, команда возврата) последовательностей команд. Результат компиляции будет содержать только код, присутствующий в теле функции в явном виде. Это позволяет программисту выполнить нужные действия в необходимой последовательности, однако налагает требования по соблюдению внутренних правил языка. В частности, должны быть сохранены регистры EBX, ESI, EDI, EBP; при использовании параметров в такой функции должен быть явно установлен указатель кадра в регистре EBP, а при использовании локальных переменных — зарезервировано достаточное количество байтов в стеке.

Для возврата из naked — функции в ее тело должна быть явно помещена команда _asm ret, если только функция не использует средств вроде VxDJmp, которые сами выполняют возврат в функцию, из которой был сделан вызов.

Неявные обращения к функциям поддержки

Visual C++ может генерировать неявные обращения к функциям из стандартной библиотеки при использовании некоторых конструкций языка, например исключений (exceptions) и RTTI, поэтому для применения этих возможностей необходимо написание собственной подсистемы их поддержки в VxD.

При использовании виртуальных функций компилятор назначает каждой чисто виртуальной (pure virtual) функции ссылку на библиотечную функцию _purecall. Это делается для того, чтобы отловить все ошибочные обращения к чисто виртуальной функции, для которой не определена реальная функция-адресат. Стандартная функция _purecall выводит диагностическое сообщение и завершает работу программы, используя Windows API; чтобы сделать возможным применение виртуальных функций в VxD, необходимо в одном из его модулей определить свой вариант:

int _purecall (void) {
return 0;
}

При желании можно поместить внутрь тела функции вывод диагностического сообщения средствами, доступными для VxD.

Экспорт ссылки на DDB

Экспорт ссылки на DDB по номеру обычно принято делать в DEF-файле, однако в Visual C++ для этого есть удобный квалификатор __declspec (dllexport), который достаточно поместить в заголовке определения структуры DDB.

Структура DEF-файла для построения VxD

DEF-файл для построения VxD может содержать опции VXD, DESCRIPTION и SECTIONS.

Опция VXD имеет вид:

VXD имя тип

  • имя — имя модуля драйвера, а типDEV386 для статического или DYNAMIC — для динамического драйвера.

Опция DESCRIPTION:

DESCRIPTION строка_описания

  • строка_описания — произвольная строка, описывающая драйвер, заключена в апострофы (') или двойные кавычки (").

Опция SECTIONS:

SECTIONS

имя [CLASS 'класс'] список_атрибутов

  • имя — имя секции (сегмента).
  • класс — класс секции (имя набора секций, внутри которой они компонуются подряд).
  • список_атрибутов — атрибуты секции: EXECUTE — исполняемая, READWRITE — доступная для записи, PRELOAD — загружаемая без явного запроса, DISCARDABLE — автоматически выгружаемая при отсутствии обращения. Названия атрибутов разделяются пробелами.

Установки компилятора и компоновщика

В установках компилятора в среде Visual C++ необходимо запретить обработку исключений и RTTI, установить однозадачную (single-threaded) стандартную библиотеку (RTL). Для корректной компиляции включаемых файлов из DDK, которые предназначены не только для VxD (например, MMSYSTEM.H) необходимо определить (в установках препроцессора или в тексте программы) символическое имя Win32_VxD.

В установках компоновщика (linker) необходимо убрать все библиотеки Windows API, ибо они предназначены только для стандартной среды выполнения. При использовании стандартных функций-оберток из DDK необходимо подключить библиотеку VXDWRAPS.CLB из DDK.

В командной строке компоновщика, отображаемой внизу окна настроек, необходимо вручную добавить опцию /VXD. Также нужно внести в список файлов проекта DEF-файл, управляющий построением модуля и содержащий название драйвера и описание используемых секций (сегментов). В среде Visual C++ имеется возможность обойтись без использования DEF-файла, внося все необходимые опции в командную строку компоновщика, однако это менее удобно, так как строка быстро загромождается и становится трудной для восприятия и редактирования.

Параметры секций

Компилятор Visual C++ по умолчанию создает секции четырех типов:

  • .text — код программы;
  • .data — данные программы;
  • .rdata — константы программы (данные только для чтения);
  • .bss — неинициализированные данные.

В DEF-файле этим секциям должны быть приписаны атрибуты EXECUTE, PRELOAD (как для класса резидентного кода LCODE).

При необходимости можно помещать код и данные в другие секции при помощи директив #pragma code_seg, #pragma data_seg и #pragma alloc_text, приписав им необходимые атрибуты. Это может понадобиться, например, для выделения части кода/данных, используемых только при инициализации или разрешенных для откачки (атрибут DISCARDABLE).

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

Функции, импортируемые из библиотеки VXDWRAPS.CLB, используют в основном секции _LTEXT и _LDATA.