Assembler - язык неограниченных возможностей

         

Драйверы устройств


В Windows, так же как и в DOS, существует еще один вид исполнимых файлов — драйверы устройств. Windows 3.x и Windows 95 используют одну модель драйверов, Windows NT — другую, a Windows 98 — уже третью, хотя и во многом близкую к модели Windows NT. В Windows 3.x/Windows 95 используются два типа драйверов устройств — виртуальные драйверы (VxD), выполняющиеся с уровнем привилегий 0 (обычно имеют расширение .386 для Windows 3.x и .VXD для Windows 95), и непривилегированные драйверы, исполняющиеся, как и обычные программы, с уровнем привилегий 3 (обычно имеют расширение .DRV). Windows NT использует несовместимую модель драйверов, так называемую kernel-mode (режим ядра). На основе модели kernel-mode с добавлением поддержки технологии PNP и понятия потоков данных в 1996 году была основана модель WDM (win32 driver model), которая теперь используется в Windows 98 и NT и, по-видимому, будет играть главную роль в дальнейшем.

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

Чтобы самостоятельно создавать драйверы для любой версии Windows, необходим комплект программ, документации, включаемых файлов и библиотек, распространяемый Microsoft, который называется DDK (Drivers Development Kit). (DDK для Windows NT и Windows 98 распространяются бесплатно.)

Мы не будем рассматривать программирование драйверов для Windows в деталях, так как этой теме посвящено достаточно много литературы, не говоря уже о документации, прилагающейся к DDK. Чтобы создать драйвер, в любом случае лучше всего начать с одного из прилагающихся примеров и изменять/добавлять процедуры инициализации, обработчики сообщений, прерываний и исключений, обработчики для API, предоставляемого драйвером, и т.д. Рассмотрим только, как выглядит исходный текст драйвера, потому что он несколько отличается от привычных нам ассемблерных программ.


Любой драйвер начинается с директивы include vmm.inc, которая включает файл, содержащий определения используемых сегментов и макроопределений. Макроопределения вида VXD_LOCKED_CODE_SEG/VXD_LOCKED_CODE_ENDS соответствуют директивам начала и конца соответствующих сегментов (в данном случае сегмента _LTEXT). Другие два важных макроопределения Declare_Virtual_Device и VMMCall/WDMCall. Первое — это просто определение, получающее в качестве параметров идентификатор драйвера, название, версию, порядок загрузки и адреса основных процедур драйвера, из которых строится заголовок драйвера. Второе — это замена команды call, получающая в качестве параметра имя функции VMM или WDM, к которой надо обратиться. Например, элемент кода драйвера BIOSXLAT, перехватывающий прерывание 10h, выглядит следующим образом:

VxD_ICODE_SEG ; начало сегмента _ITEXT (сегмент кода ; инициализации, исполняющийся ; в защищенном режиме, который удаляется ; из памяти после сообщения Init_Complete) BeginProc BIOSXlat_Sys_Critical_Init ; процедура, которая вызывается ; для обработчика сообщения ; Sys_Critical_Init - первого сообщения, ; которое получает драйвер. ; Обычно обработчики сообщений должны ; сохранять регистры ЕВХ, EDI, ESI и ЕВР, ; хотя в этом случае этого можно не делать mov esi,OFFSET32 BIOSXlat_Int10 ; адрес обработчика INT 10h ; в регистр ESI. Важно использовать ; макроопределение OFFSET32 всюду ; вместо offset mov edx,10h ; любое число, которое будет ; помещаться в EDX при вызове ; регистрируемого обработчика VMMCall Allocate_PM_Call_Back ; зарегистрировать ; точку входа, обращаясь ; к которой, программы из защищенного ; режима в VM будут передавать ; управление процедуре в драйвере ;(но не win32-nporpaммa) - ; они должны использовать ; DeviceIOControl для работы ; с драйверами, jc BXLSCI_NoPM ; если CF = 1 - произошла ошибка xchg edx,eax ; точку входа - в EDX, ; число 10h - в ЕАХ ; (теперь это - номер перехватываемого прерывания для Set_PM_Int_Vector) mov ecx,edx shr ecx,10h ; селектор точки входа movzx edx,dx ; смещение точки входа VMMCall Set_PM_Int_Vector ; установить обработчик ; прерывания INT 10h. ; Если эта функция вызвана до Sys_VM_Init, установленный обработчик ; становится звеном цепочки обработчиков для всех виртуальных машин. ; После того как прерывание проходит по цепочке обработчиков ; в защищенном режиме, оно отображается в V86, точно так же, как в DPMI



; [код перехвата других прерываний]

EndProc BIOSXlat_Sys_Critical_Init ; конец процедуры VxD_ICODE_ENDS ; конец сегмента инициализации

Соответственно, чтобы эта процедура была вызвана для сообщения Sys_Critical_Init, в сегменте фиксированного кода _LTEXT должна быть следующая запись:

VxD_LQCKED_CODE_SEG ; начало сегмента _LTEXT BeginProc BIOSXlat_Control ; начало процедуры Control_Dispatch Sys_Critical_Init,\BIOSXlat_Sys_Critical_Init ; при помощи еще одного макроопределения из vmm.inc ; зарегистрировать процедуру BIOSXlat_Sys_Critical_Init ; как обработчик сообщения Sys_Critical_Init clc ; процедура-обработчик управляющих ; сообщений должна возвращать CF = 0 ret EndProc BIOSXlat_Control ; конец процедуры VxD_LOCKED_CODE_ENDS ; конец сегмента _LTEXT

И наконец, процедура BIOSXlat_Control регистрируется как процедура, получающая управляющие сообщения, в заголовке драйвера:

; первая строка после .386р и include vmm.inc: Declare_Virtual_Device BlOSXlat, 1, 0, BIOSXlat_Control,\ BIOSXiat_Device_ID, BIOSXlat_Init_Order

Это не слишком сложно и, пользуясь примерами и документацией из DDK и отладчиком SoftICE, можно справиться практически с любой задачей, для которой имеет смысл создавать драйвер.


Содержание раздела