Страница 7 из 13 Особенности разработки 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. |