Основы разработки прикладных виртуальных драйверов

Часть 1. Виртуальные драйверы и виртуальные машины Windows

К.Г.Финогенов

Работа операционных систем Windows 95/98 (как и Windows 3.1) в значительной степени основана на использовании специального рода программ — так называемых виртуальных драйверов устройств (или просто виртуальных драйверов, virtual device). Основное назначение виртуального драйвера — виртуализация устройства, то есть возможность нескольким приложениям одновременно использовать одно и то же физическое устройство. Например, виртуальный драйвер дисплея (VDD) обеспечивает многооконный режим, в котором каждое приложение, выводя информацию на экран, считает, что весь физический экран находится в его распоряжении, в то время как в действительности вывод приложения поступает в выделенное для него окно. Виртуальный контроллер прерываний (VPICD) дает возможность нескольким приложениям совместно использовать единую систему прерываний компьютера. Виртуальный драйвер клавиатуры (VKD) позволяет вводить с клавиатуры символьные строки в любую из выполняемых программ. Разработка нового виртуального драйвера может понадобиться при установке на компьютер новой аппаратуры (или нового программного обеспечения, предназначенного для обслуживания других приложений), которая будет использоваться в многозадачном режиме и для которой в системе Windows не предусмотрено средств виртуализации.

Другое, возможно, более важное для прикладного программиста приложение виртуальных драйверов состоит в использовании их в качестве универсального инструментального средства. Дело в том, что виртуальный драйвер работает в плоской модели памяти на нулевом уровне привилегий. Плоской моделью памяти называется такая организация адресного пространства, когда в дескрипторе указаны нулевой базовый линейный адрес сегмента и предел, соответствующий максимальному размеру сегмента, — 4 Гбайт. Такой дескриптор может входить как в локальную, так и в глобальную таблицу дескрипторов. В первом случае речь идет о приложении, работающем в плоской модели памяти на уровне привилегий 3 (это характерно для 32-разрядных приложений Windows); во втором — о системных компонентах, в частности о виртуальных драйверах. Для виртуального драйвера доступно все линейное адресное пространство (4 Гбайт), все программные составляющие Windows (в частности, системные виртуальные драйверы) и все программно-управляемые аппаратные средства процессора и компьютера в целом. Виртуальный драйвер представляет собой идеальную среду для исследования системы Windows и контролируемого вмешательства в ее работу. Поэтому решение каких-либо нестандартных задач, например разработка программ управления измерительным оборудованием, подключенным к компьютеру, может потребовать написания специфических виртуальных драйверов, смысл использования которых состоит совсем не в виртуализации аппаратно-программных средств, а в возможности выполнения действий, допустимых лишь на нулевом уровне привилегий (например, обращения к запрещенным портам или к системе прерываний).

Разработка прикладных виртуальных драйверов требует основательного знакомства с особенностями работы современных процессоров в защищенном режиме, а также с некоторыми внутренними алгоритмами функционирования системы Windows. Читатели, не знакомые с этими вопросами, могут обратиться к книге П.И. Рудакова и К.Г. Финогенова «Программируем на языке ассемблера IBM PC», изд. «Принтер», 1999 г.; ниже будет дан краткий обзор тех специальных понятий и средств Windows, знание которых необходимо для написания программ виртуальных драйверов.

Одним из базовых понятий системы Windows является понятие виртуальной машины. Согласно документации Microsoft, виртуальной машиной (VM) называется выполняемая задача, состоящая из приложения, поддерживающего программное обеспечение (например, программы DOS и BIOS), памяти и регистров процессора. Каждая виртуальная машина обладает собственным адресным пространством, пространством ввода-вывода и таблицей векторов прерываний. В адресное пространство VM отображаются ПЗУ BIOS, драйверы и другие программы DOS, а также загруженные до запуска Windows резидентные программы, что обеспечивает доступ к ним прикладных программ, выполняемых под управлением Windows. В состав виртуальной машины входят виртуальные аппаратные регистры, в частности виртуальные маски прерываний, виртуальные флаги процессора и другие.

Первая виртуальная машина, создаваемая после загрузки Windows и называемая системной виртуальной машиной, в Windows 95/98 предназначена для выполнения 16- и 32-разрядных приложений Windows. Для каждого сеанса DOS создается своя виртуальная машина, и таким образом одновременно в системе может существовать несколько виртуальных машин.

Управление виртуальными машинами возлагается на менеджера виртуальных машин (Virtual machine manager, VMM), являющегося ядром операционных систем Windows 95/98. VMM представляет собой 32-разрядную систему защищенного режима, работающую на нулевом уровне привилегий в плоской модели памяти. В функции VMM входит создание, управление и завершение виртуальных машин, а также управление памятью, процессами, прерываниями и нарушениями защиты. Так, если приложение делает попытку записи или чтения по адресам памяти, не принадлежащим данной виртуальной машине, или выполняет команды ввода-вывода в запрещенные порты, процессор возбуждает исключение и управление передается VMM. VMM анализирует причину исключения и либо с помощью виртуальных драйверов обеспечивает выполнение затребованной операции, либо аварийно завершает приложение.

Для обеспечения функционирования VMM и виртуальных драйверов система создает два глобальных селектора: 28h — для программных кодов и 30h — для системных полей данных. Дескрипторы этих селекторов почти одинаковы: в них указано нулевое значение DPL, нулевой базовый линейный адрес и граница 4 Гбайт (рис. 1). В регистр CS загружается селектор 28h, во все остальные сегментные регистры (DS, SS, ES, FS и GS) — селектор 30h. Ни VMM, ни виртуальные драйверы никогда не изменяют содержимое сегментных регистров.

VMM вместе с системными виртуальными драйверами предоставляет большое количество служебных функций, позволяющих виртуальным драйверам активизировать по ходу своего выполнения те или иные системные процедуры и алгоритмы. Например, функция VMM _MapPhysToLinear отображает заданный участок физических адресов на линейное адресное пространство, что дает возможность виртуальному драйверу выполнять чтение или запись в физической памяти; функции Install_Handler и Install_Mult_IO_Handlers устанавливают в системе прикладные процедуры обслуживания заданных портов; функция Simulate_Push помещает указанный параметр в стек приложения. Служебные функции имеются и у системных виртуальных драйверов. Так, виртуальный контроллер прерываний VPICD позволяет с помощью функции VPICD_Virtualize_IRQ организовать прикладную обработку аппаратных прерываний, а виртуальный таймер VTD предоставляет функции VTD_Begin_Min_Period и VTD_End_Min_Period для управления частотой системного таймера. Служебные функции VMM и системных виртуальных драйверов, в отличие от функций DOS или Windows, не могут вызываться непосредственно из приложения; обращение к ним возможно только из виртуальных драйверов.

Взаимодействуя с VMM и виртуальными машинами, виртуальные драйверы используют целый ряд системных полей и структур данных. Одним из важнейших системных идентификаторов, создаваемых VMM для каждой виртуальной машины, является ее дескриптор. Он однозначно идентифицирует виртуальную машину и используется при вызовах функций VMM для задания VM, к которой обращено требуемое действие. Заметим, что дескриптор виртуальной машины является базовым адресом, от которого ведется отсчет адресов ряда важных полей VMM. Так, в двойном слове по адресу [дескриптор VM — 8] располагается адрес таблицы векторов прерываний защищенного режима.

Дескриптор VM фактически является линейным 32-разрядным адресом управляющего блока данной VM. Управляющий блок представляет собой структуру из пяти двойных слов, содержащую информацию о виртуальной машине. Эта структура определена в файле VMM.INC и имеет следующий вид:

cb_s    struc  
CB_VM_Status
CB_High_Linear
CB_Client_Pointer
CB_VMID
CB_Signature
cb_s   ends
dd
dd
dd
dd
dd
?  ; Слово состояния VM
?  ; Старший линейный адрес 
?  ; Адрес структуры клиента
?  ; Идентификатор VM
?  ; Сигнатура

В слове состояния VM каждый бит закреплен за определенной характеристикой текущего состояния VM. Так, установленный бит 1 (символическое обозначение VMStat_Background) говорит о том, что VM выполняется в фоновом режиме; бит 6 (VMStat_PM_App) свидетельствует о наличии в VM приложения защищенного режима, бит 7 (VMStat_PM_Use32) характеризует 32-разрядное приложение защищенного режима и т.д. Иногда в процессе отладки виртуального драйвера приходится обращаться к этому слову для определения состояния виртуальной машины.

Старший линейный адрес заслуживает отдельного рассмотрения. При запуске в рамках Windows одного или нескольких сеансов DOS (и создания соответствующих виртуальных машин) происходит копирование первого мегабайта физического адресного пространства в различные участки расширенной памяти. В каждой копии DOS имеется своя таблица векторов прерываний, своя видеопамять и даже своя копия ПЗУ BIOS, что дает возможность приложениям DOS различных сеансов параллельно и независимо работать каждому с собственной памятью, не разрушая память других сеансов. Для того чтобы с сеансами DOS могли взаимодействовать VMM или виртуальные драйверы, физическая память сеансов DOS отображается на некоторые линейные адреса (4-го гигабайта линейного адресного пространства), которые и называются старшими линейными адресами. Для каждой VM существует собственный старший адрес (рис. 2).

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

Структура клиента, адрес которой CB_Client_Pointer входит в управляющий блок, является важнейшим набором данных, обеспечивающим обмен информацией между приложением и виртуальным драйвером. Когда при вызове виртуального драйвера процессор переходит на нулевой уровень привилегий, в стеке уровня 0 (не в стеке приложения!) сохраняются значения всех регистров вызывающего приложения, соответствующие точке вызова драйвера. Совокупность этих значений и называется структурой клиента. При входе в драйвер адрес структуры клиента находится в регистре EBP, а обращение к отдельным элементам этой структуры осуществляется с помощью наглядных символических обозначений вида Client_AX, Client_FLAGS, Client_CS и т. д.:

     movzx     EAX,[EBP.Client_AX]     ;Передача AX приложения в EAX драйвера  
     mov     [EBP.Client_DX],data         ;Передача данного из драйвера в DX приложения  

Возможно также обращение как к байтовым (Client_AL, Client_DH), так и к 32-разрядным (Client_EFLAGS, Client_ESI) регистрам. Структура клиента является чрезвычайно удобным средством обмена информацией между приложением и драйвером, поскольку регистры приложения не только сохраняются в структуре клиента при переходе в драйвер, но и восстанавливаются из нее при возврате в приложение. Поэтому изменение значений в структуре клиента в процессе выполнения программы драйвера приведет к соответствующему изменению регистров процессора после возврата в приложение.

Виртуальные драйверы обычно пишутся на языке ассемблера, хотя внутренние процедуры драйвера вполне могут составляться на языке С (С++). Использование языка высокого уровня упрощает реализацию вычислительных и логических алгоритмов в процедурах драйвера, однако делает текст драйвера менее наглядным. Поскольку нашей задачей является освоение основных принципов разработки драйверов и приводимые ниже примеры относительно просты, мы ограничимся языком ассемблера. Для создания выполнимого модуля необходимо использовать 32-разрядные ассемблер и компоновщик,  входящие, в частности, в пакет DDK (Device Development Kit — пакет для разработки драйверов). В том же пакете можно найти заголовочные и другие используемые при разработке драйверов, включаемые файлы с многочисленными макросами и определениями констант.

Прежде всего рассмотрим общую структуру виртуального драйвера, а также правила его подготовки и загрузки. Ниже приведен текст простейшего виртуального драйвера для систем Windows 95/98, который не выполняет никакой работы, хотя и может быть установлен в системе как полноценный драйвер.

 

Структура виртуального драйвера

.386p          ;Будут использоваться 32-разрядные регистры  
.XLIST              ;Подавление включения в листинг текста vmm.inc  
include vmm.inc     ;Подключение файла vmm.inc  
.LIST                        ;Разрешение дальнейшего вывода в листинг  
;Блок описания драйвера  
Declare_Virtual_Device VMyD,1,0,VMyD_Control,8000h, \  
    Undefined_Init_Order,V86_API_Handler,PM_API_Handler  
;==============Сегмент инициализации реального режима===============  
VxD_REAL_INIT_SEG  
;Процедура инициализации реального режима  
BeginProc VMyD_Real_Init     ;Точка входа при инициализации  
     mov     AH,09h     ;Функция DOS вывода на экран  
     mov     DX,offset msg;Адрес выводимого сообщения  
     int     21h     ;Вызов DOS  
     mov     AX,Device_Load_Ok;Код успешного завершения  
     xor     BX,BX     ;Дополнительные  
     xor     SI,SI      ;данные  
     xor     EDX,EDX     ;отсутствуют  
     ret  
msg db 'Виртуальный драйвер VMyD загружен',13,10,'$'  
EndProc   VMyD_Real_Init  
VxD_REAL_INIT_ENDS  
;===============Сегмент данных=======================================  
VxD_DATA_SEG  
;Здесь размещаются данные, используемые драйвером  
VxD_DATA_ENDS  
;===============Сегмент защищенного режима===========================  
VxD_CODE_SEG  
;Управляющая процедура для обработки системных сообщений Windows  
BeginProc VMyD_Control  
;Сюда могут быть включены процедуры обработки различных   
;системных сообщений Windows: Device_Init инициализации драйвера,   
;Create_VM создания новой виртуальной машины, ;Begin_PM_App запуска   
;приложения защищенного режима и End_PM_App его завершения и др.  
     clc  
     ret  
EndProc VMyD_Control  
;Процедура, вызываемая из приложения реального режима (приложения DOS)  
BeginProc V86_API_Handler  
;Сюда включаются содержательные процедуры драйвера, которые   
;должны выполняться при вызове драйвера из приложений реального режима  
     ret      ;Возврат из драйвера в вызывающее приложение  
EndProc V86_API_Handler  
;Процедура, вызываемая из приложения защищенного режима (приложения Windows)  
BeginProc PM_API_Handler  
;Сюда включаются содержательные процедуры драйвера, которые   
;должны выполняться при вызове драйвера из приложений защищенного режима  
     ret      ;Возврат из драйвера в вызывающее приложение  
EndProc PM_API_Handler  
VxD_CODE_ENDS  
end VMyD_Real_Init     ;Конец текста драйвера с указанием точки входа  

Исходный текст драйвера начинается с директивы ассемблера .386, позволяющей использовать расширенный набор команд современных процессоров, в частности привилегированных команд защищенного режима. Следующая далее директива .XLIST подавляет вывод в листинг трансляции всех последующих предложений программы вплоть до отмены ее действия директивой .LIST. В нашем случае подавляется вывод в листинг весьма обширного файла VMM.INC с определениями макросов и констант, фактически обеспечивающего использование в драйвере средств менеджера виртуальной машины VMM.

Как видно из приведенного листинга, драйвер в простейшем случае состоит из блока описания драйвера и трех сегментов: сегмента команд инициализации в реальном режиме VxD_REAL_INIT, в котором размещается процедура инициализации реального режима (вместе с относящимися к ней данными); сегмента данных VxD_DATA_SEG со всеми данными, которые могут потребоваться драйверу в процессе его работы; сегмента команд защищенного режима VxD_CODE, содержащего в нашем случае процедуры VMyD_Control, V86_API_Handler и V86_API_Handler (имена процедур произвольны).

В тексте драйвера широко используются макросы, определенные в файле VMM.INC. Так, макросы VxD_REAL_INIT_SEG и VxD_REAL_INIT_ENDS задают начало и конец сегмента инициализации реального режима, макросы VxD_CODE_SEG и VxD_CODE_ENDS — начало и конец сегмента защищенного режима, а макросы BeginProc и EndProc — границы процедур, входящих в состав драйвера. Имена макросов можно набирать как строчными, так и прописными буквами. Приведенное в примере начертание взято из документации Windows.

Блок описания драйвера, с которого начинается текст драйвера, вводится с помощью макроса Declare_Virtual_Device. Этот макрос требует указания восьми  параметров, располагаемых в следующем порядке:

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

В нашем случае драйверу дано произвольное имя VMyD, управляющей процедуре (которая обязательно должна присутствовать, хотя у нас она фактически пуста) — VMyD_Control, процедурам обслуживания реального и защищенного режимов, которые в дальнейшем мы будем называть API-процедурами, — V86_API_Handler и PM_API_Handler соответственно. Для версии выбран номер 1.0, а для идентификационного кода — 8000h. При разработке коммерческого виртуального драйвера его следует зарегистрировать в корпорации Microsoft и получить для него идентификационный код. Порядок инициализации важен для драйверов, обращающихся друг к другу; для наших драйверов порядок инициализации не имеет значения, что указано с помощью константы Undefined_Init_Order.

Процедура инициализации реального режима VMyD_Real_init вызывается автоматически в процессе загрузки драйвера, когда процессор еще не перешел в защищенный режим. Поскольку процедура выполняется в реальном режиме, в ней возможен вызов функций DOS. У нас она используется для вывода на экран (в процессе загрузки Windows) сообщения о загрузке нашего драйвера.

API (Application Program Interface — интерфейс прикладных программ) процедуры являются, можно сказать, содержательной частью драйвера. Эти процедуры, выполняемые на нулевом уровне привилегий в плоской модели памяти, могут быть вызваны из любой прикладной программы (выполняемой, естественно, на третьем уровне защиты). Для обращения к драйверу из приложений DOS (реальный режим) и Windows (защищенный режим) предусматриваются две различные API-процедуры; если действия драйвера в ответ на обращение программ в обоих случаях одинаковы, в соответствующих полях блока описания драйвера можно указать имя единственной процедуры. В некоторых случаях драйвер работает «сам по себе», без обращения к нему из программы пользователя, тогда как и сами процедуры, и их имена в блоке описания драйвера отсутствуют. В дальнейшем мы будем рассматривать только драйверы, предназначенные для работы с приложениями Windows, и в блоке описания драйвера имя API-процедуры реального режима будет отсутствовать:

Declare_Virtual_Device VMyD,1,0,VMyD_Control,8000h, \  
    Undefined_Init_Order,,API_Handler  

Для ускорения процесса подготовки выполнимого модуля виртуального драйвера удобно создать пакет из трех файлов. Приводимый ниже пример их содержимого предполагает, что пакет DDK установлен на диске G: в каталоге DDK95, исходный файл с текстом драйвера имеет имя VMYD.ASM, а выполнимый модуль драйвера — VMYD.VXD.

Файл WIN95.BAT  
g:\ddk95\masm611c\ml /coff /DBLD_COFF /Zi /Fl /Sn /DMASM6 /c /Cx vmyd.asm  
g:\ddk95\msvc20\link vmyd.obj @vmyd.lnk  
del vmyd.obj  
Файл VMYD.DEF  
LIBRARY VMYD  
DESCRIPTION ‘Virtual VMyD driver’  
EXETYPE DEV386  
SEGMENTS  
_LTEXT CLASS ‘LCODE’ PRELOAD NONDISCARDABLE  
_LDATA CLASS ‘LCODE’ PRELOAD NONDISCARDABLE  
_RCODE CLASS ‘RCODE’  
EXPORTS  
VMyD_DDB @1  
Файл VMYD.LNK  
DEBUG  
DEBUGTYPE:BOTH  
MACHINE:I386  
PDB:NONE  
DEF:VMYD.DEF  
OUT:VMYD.VXD  
VXD  

Для того чтобы в операторе include в исходном тексте драйвера не описывать длинный путь к файлу VMM.INC, можно в файл AUTOEXEC.BAT включить команду

SET INCLUDE=g:\ddk95\inc32  

Загружаемый модуль драйвера подготавливается запуском в сеансе DOS приведенного выше командного файла W95.BAT.

Установка созданного драйвера в системе осуществляется путем включения в раздел [386Enh] файла SYSTEM.INI следующей строки (в предположении, что программа драйвера находится в каталоге DRIVERS диска F:):

DEVICE=f:\drivers\vmyd.vxd  

В процессе загрузки системы Windows на одном из этапов (после того как появится и исчезнет полноэкранная заставка Windows) на экран будет выведено сообщение «Виртуальный драйвер VMyD загружен», после чего система будет работать как обычно, поскольку наш драйвер и по собственной инициативе ничего не делает, и не взаимодействует ни с системой, ни с приложениями.

 

(Продолжение следует)

КомпьютерПресс 3'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
Популярные статьи
КомпьютерПресс использует