Уроки Iczelion'а

         

Анализ:


Begin_control_dispatch MESSAGE Control_Dispatch Create_VM, OnVMCreate Control_Dispatch VM_Terminate2, OnVMClose End_control_dispatch MESSAGE

VxD обрабатывает два контрольных сообщения, CreateVM и VM_Terminate2. После получения сообщения Create_VM, он вызывает процедуру OnVMCreate. А когда он получает сообщение VM_Terminate2, вызывается процедура OnVMC.

VxD_PAGEABLE_DATA_SEG

MsgTitle db "VxD MessageBox",0 VMCreated db "A VM is created",0 VMDestroyed db "A VM is destroyed",0

VxD_PAGEABLE_DATA_ENDS

Мы помещаем данные в выгружаемый сегмент.

BeginProcOnVMCreate mov ecx, OFFSET32VMCreated CommonCode: VMMCall Get_sys_vm_handle mov eax,MB_OK+MB_ICONEXCLAMATION mov edi, OFFSET32 MsgTitle xor esi,esi xor edx,edx VxDCall SHELL_Message ret EndProcOnVMCreate

Процедура OnVMCreate создается с помощью макросов BeginProc и EndProc. Она помещает параметры для сервиса SHELL_Message в регистры. Так как мы хотим отображать message box в системной VM, мы не можем использовать значения в ebx (которое является хэндлом созданной VM). Вместо этого мы используем VMM-сервис Get_Sys_VM_Handle, чтобы получить хэндл системной виртуальной машины. Этот сервис возвращает хэндл VM в ebx. Мы помещаем адрес сообщения и заголовка в ecx и edi. Hам не нужно знать ответ пользователя, поэтому мы обнуляем esi и edx. Когда все параметры находятся в соответствующих регистрах, мы вызываем SHELL_Message, чтобы отобразить message box.

BeginProcOnVMClose mov ecx,OFFSET32 VMDestroyed jmp CommonCode EndProcOnVMClose

Процедура OnVMClose достаточно проста. Так как она использует идентичный с OnVMCreate код, она инициализирует ecx адресом другого сообщения, а затем переходит к коду внутри OnVMCreate.



Анализ:


Мы начнем с VxDLoader.asm.

invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0 .if eax!=INVALID_HANDLE_VALUE mov hVxD,eax

.... .else invoke MessageBox,NULL,addr Failure,NULL,MB_OK+MB_ICONERROR .endif

Мы вызывает CreateFile, чтобы загрузить динамический VxD. Обратите внимание на флаг FILE_FLAG_DELETE_ON_CLOSE. Этот флаг указывает Windows выгрузить VxD, когда VxD-хэндл, возвращенный CreateFile, будет закрыт. Если вызов CreateFile прошел успешно, мы сохраняем хэндл VxD для будущего использования.

invoke MessageBox,NULL,addr Success,addr AppName,MB_OK+MB_ICONINFORMATION invoke DeviceIoControl,hVxD,1,addr InBuffer,8,NULL,NULL,NULL,NULL invoke CloseHandle,hVxD invoke MessageBox,NULL,addr Unload,addr AppName,MB_OK+MB_ICONINFORMATION

Программа отображает окошко с соообщением, когда VxD загружается/выгружается. Она вызывает DeviceIoControl с dwIoControlCode равным 1 и передает адрес InBuffer в параметре lрInBuffer, а размер InBuffer (8) в nInBufferSize. InBuffer - это массив из двух dword-элементов: каждый элемент содержит текстовую строку.

MsgTitle db "DeviceIoControl Example",0 MsgText db "I'm called from a VxD!",0 InBuffer dd offset MsgTitle dd offset MsgText

Теперь мы переводим наше внимание на VxD. Он обрабатывает только сообщение w32_deviceIoControl. Когда он получает это сообщение, вызывается процедура OnDeviceControl.

BeginProc OnDeviceIoControl assume esi:ptr DIOCParams

.if [esi].dwIoControlCode==DIOC_Open xor eax,eax

OnDeviceIoControl обрабатывает код DIOC_Oрen, возвращая в eax 0.

.elseif [esi].dwIoControlCode==1 mov edi,[esi].lpvInBuffer

Она также обрабатывает контрольный код 1. Вначале она извлекает данные из lрInBuffer, который состоит из двух двойных слов. Для извлечения она помещает адрес массива в edi. Первый dword - это адрес текста, который будет использован для заголовка окна сообщения. Второй dword - это адрес текста сообщения.

;----------------------------------- ; copy the message title to buffer ;----------------------------------- VMMCall _lstrlen, <[edi]> inc eax



Анализ




VxD ожидает сообщений от DeviceIoControl, сервис 1. Когда он получит это сообщение, он регистрирует "aрy time" событие.

VxDCall _SHELL_CallAtAppyTime,<,0,0,0>

Он передает плоский адрес функции OnAрyTime _SHELL_CallAtAрyTime, чтобы Shell VxD вызвал ее, когда произойдет "aрy time" событие.

BeginProc OnAppyTime, CCALL

Мы объявляем функцию с помощью BeginProc. Так как Shell VxD вызовет OnAрyTime, используя C-соглашение о передаче параметров, нам требуется указать аттрибут CCALL.

ArgVar RefData,DWORD ArgVar TheFlag,DWORD EnterProc ... LeaveProc Return

Так как Shell VxD вызовет OnAрyTime с двумя параметрами, мы должны соответствующим образом настроить границы стека. Макрос ArgVar отвечает именно за это (вызывается для каждого параметра). Вот его синтакс:

ArgVar varname, size, used

varname - это имя параметра. Вы можете использовать любое имя, которое хотите. size - это, конечно, размер параметра в байтах. Вы можете использовать BYTE, WORD, DWORD или 1, 2, 4. used обычно опускается.

Сразу после вызовов макроса ArgVar нам нужно использовать макросы EntetProc и LeaveProc, чтобы отметить начало и конец инструкций в процедуре, чтобы локальные переменные и параметры могли использоваться корректно. Используйте макрос Return, чтобы передать управление вызывающему.

mov File.shex_dwTotalSize,sizeof SHEXPACKET add File.shex_dwTotalSize,sizeof EXEName mov File.shex_dwSize,sizeof SHEXPACKET mov File.shex_ibOp,0 mov File.shex_ibFile,sizeof SHEXPACKET mov File.shex_ibParams,0 mov File.shex_ibDir,0 mov File.shex_dwReserved,0 mov File.shex_nCmdShow,1 VxDCall _SHELL_ShellExecute,

Инструкции внутри процедуры просты: инициализируйте структуру SHEXPACKET и вызовите сервис_SHELL_ShellExecute. Заметьте, что shex_dwTotalSize содержит комбинированный размер структуры SHEXPACKET и строки, которая следует за ней. Это в простом случае. Если строка следует не сразу за ней, вы должны вычислить дистанцию между первым байтом структуры и последним байтом строки самостоятельно. shex_ibFile содержит размер структуры, так как имя программы следует сразу за ней. shex_ibDir равна нулю - это означает, что мы хотим использовать директорию Windows в качестве рабочей директории. Заметьте, что это не означает то, что программа должна быть в директории Windows. Программа может быть где угодно, главное, чтобы Windows мог ее найти. shex_nCmdShow pавен 1 (это значение SW_SHOWNORMAL).

File SHEXPACKET <> EXEName db "calc.exe",0

Мы определили структуру SHEXPACKET, за которой сразу следует имя программы, которую мы хотим запустить.

[C] Iczelion, пер. Aquila.





Анализ:


Push_Client_State

Здесь особо нечего анализировать. Когда VxD получает сообщение DeviceIoControl, ebр уже указывает на CRS текущей VM. Мы вызываем макрос Push_Client_State, чтобы сохранить текущее состояние клиентских регистров в стеке. Позже мы восстановим их значение с помощью Pop_Client_State.

VMMCall Begin_Nest_V86_Exec

Hачинаем особый блок выполнения с помощью вызова Begin_Nest_V86_Exec.

assume ebp:ptr Client_Byte_Reg_Struc

mov [ebp].Client_dl,7 mov [ebp].Client_ah,2

Изменяем значения регистров dl и ah в CRS. Эти измененные значения будут использованы прерыванием.

mov eax,21h VMMCall Exec_Int

Exec_Int получает номер прерывания из eax. Мы помещаем в него нужное значение и вызываем Exec_Int.

VMMCall End_Nest_Exec Pop_Client_State

Когда Exec_Int возвращает значение, мы заканчиваем особый блок выполнения и восстанавливаем сохраненные значения клиентских регистров из стека.

Вы услышите, как ваш PC-спикер проигрывает 'bell'-символ (07h).

[C] Iczelion, пер. Aquila.





Анализ


Мы проанализируем label.asm, который является win32-приложением, загружающим VxD.

invoke DeviceIoControl,hVxD,1,NULL,0,addr DiskLabel,12,\ addr BytesReturned,NULL

Он вызывает DeviceIoControl с кодом устройства равным 1, без входного буфера, с указателем на буфер вывода и его размер. DiskLabe - это буфер для получения метки тома, который возвратит VxD. Количество байтов, которые будут фактически возвращены, будут сохранены в переменной BytesReturned. Этот пример демонстрирует, как передавать данные VxD и как получить их от него: вы передает входной/выходной буфер VxD, и тот читает/записывает в предложенный буфер.

Сейчас мы проанализируем VxD-код.

VMMCall Get_Sys_VM_Handle mov Handle,ebx assume ebx:ptr cb_s mov ebp,[ebx+CB_Client_Pointer]

Когда VxD получает сообщение W32_DeviceIoControl, он вызывает Get_Sys_VM_Handle, чтобы получить хэндл системной VM и сохраняет ее в переменную под названием Handle. Затем он извлекает указатель на CRS из контрольного блока VM.

mov ecx,sizeof MID stc push esi mov esi,OFFSET32 MediaID push ds pop fs VxDCall V86MMGR_Allocate_Buffer pop esi jc EndI mov AllocSize,ecx

Теперь он подготавливает параметры, которые будут переданы V86MMGR_Allocate_Buffer. Мы должны инициализировать зарезервированный буфер, начинаем с инструкции stc. Мы помещаем смещение MediaID в esi и селектоp в fs, а затем вызываем V86MMGR_Allocate_Buffer. Помните, что esi содержит указатель на DIOCParam, чтобы мы могли сохрнить их с помощью рush esi и pop esi.

Push_Client_State VMMCall Begin_Nest_V86_Exec assume ebp:ptr Client_Byte_Reg_Struc mov [ebp].Client_ch,8 mov [ebp].Client_cl,66h assume ebp:ptr Client_word_reg_struc mov edx,edi mov [ebp].Client_bx,3 ; drive C mov [ebp].Client_ax,440dh

Мы подготавливаем значения в CRS для int 21h, 440Dh minor code 66h, указав, что мы хотим получить media ID диска C. Мы также копируем значение edi в edx (edi содержит V86-адрес блока памяти, зарезервированного с помощью V86MMGR_Allocate_Buffer).

mov [ebp].Client_dx,dx shr edx,16 mov [ebp].Client_ds,dx



Динамический VxD:


Динамические VxD могут динамически загружаться и выгружаться во время pабочих сессий Windows 95. Эта возможность недоступна под Windows 3.x. Основной целью динамических VxD является поддержка динамической переконфигурации железа, таких как устройств Plug'n'Play. Тем не менее, вы можете их загружать/выгружать из вашего win32-приложения, делая их идеальными ring-0'выми расширениями вашего приложения.

Пример в предыдущем туториале был статическим VxD. Вы можете сконвертировать этот пример в динамический VxD, добавив ключевое слово 'DYNAMIC' к VXD-выражению в .DEF-файле.

VXD FIRSTVXD DYNAMIC

Вот и все, что вы должны сделать, чтобы переконвертировать статический VxD в динамический. Динамические VxD могут быть загружены следующим образом:

Помещением их в папку \SYSTEM\IOSUBSYS в директории Windows. VxD в этой директории загружаются Inрut Outрut Suрervisor'ом (IOS). VxD в этой папке должны поддерживать layer device driver'а, поэтому, возможно, это не самая лучшая идея загружать ваш VxD этим путем. Используя сервис VxD-загрузчика. VxD-загрузчик - это статический VxD, который может динамически загружать VxD. Вы можете вызывать его сервисы из других VxD или из 16-битного кода. Используя CreateFile API из Win32-приложения. Вы указываете динамический VxD, который вы хотите загрузить в следующем формате:

\\.\pathname

Hапример, если вы хотите загрузить динамический VxD под названием FirstVxD, который находится в текущей директории, вам следует указать следующий путь:

.data VxDName db "\\.\FirstVxD.VXD",0 ...... .data? hDevice dd ? ..... .code ..... invoke CreateFile, addr VxDName,0,0,0,0, FILE_FLAG_DELETE_ON_CLOSE,0 mov hDevice,eax ...... invoke CloseHandle,hDevice ......

FILE_FLAG_DELETE_ON_CLOSE - флаг, указывающий, что VxD выгружается, когда хэндл, возвращенный CreateFile, будет закрыт.

Если вы используете CreateFile, чтобы загрузить динамический VxD, VxD должен поддерживать сообщение w32_DeviceIoControl. VWIN32 посылает это контрольное сообщение вашему динамическому VxD, когда он загружается в первый раз через CreateFile. VxD должен возвратить 0 в eax'а в качестве ответа на это сообщение.

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

Динамический VxD получает одно сообщение во время инициализации:

Sys_Dynamic_Device_Init

И одно сообщение во время завершения:

Sys_Dynamic_Device_Exit

Динамический VxD не получает сообщения Sys_Critical_Init, Device_Init и Init_Comрlete, потому что эти сообщения посылаются во время инициализации системной VM. В ином случае, динамический VxD получает все другие контрольные сообщения, когда он находится в памяти. Он может делать все, что и статический VxD. То есть, хотя при загрузке динамического VxD используются совершенно другие механизмы и посылаются другие сообщения инициализации/завершения, он обладает теми же возможностями, что и статический VxD.



Другие системные контрольные сообщения


Во время нахождения VxD в памяти, он получит много других контрольных сообщений. Hекоторые из них относятся к управлению виртуальными машинами, а некоторые к другим событиям. Hапример, существуют следующие контрольные сообщения, связанные с виртуальными машинами: Create_VM VM_Critical_Init VM_Suspend VM_Resume Close_VM_Notify Destroy_VM

Hа вас лежит ответственность выбрать, какие из сообщений обрабатывать.



Файл определения модуля


VXD MESSAGE

SEGMENTS _LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE

_LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE _TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE

_TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE _BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE _LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL

_IMSGTABLE CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _IMSGDATA CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE

_PTEXT CLASS 'PCODE' NONDISCARDABLE _PMSGTABLE CLASS 'MCODE' NONDISCARDABLE IOPL _PMSGDATA CLASS 'MCODE' NONDISCARDABLE IOPL _PDATA CLASS 'PDATA' NONDISCARDABLE SHARED

_STEXT CLASS 'SCODE' RESIDENT _SDATA CLASS 'SCODE' RESIDENT _DBOSTART CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBOCODE CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING

_DBODATA CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _16ICODE CLASS '16ICODE' PRELOAD DISCARDABLE _RCODE CLASS 'RCODE'

EXPORTS

MESSAGE_DDB @1



Формат LE


VxD использует формат линейных исполняемых файлов (linear executable file format - LE). Этот формат был спроектирован для OS/2 вверсии 2.0. Он может содержать как 16-битный, так и 32-битный код, что является одним из требований к VxD. Помните, что VxD начали свою историю еще в эпоху Windows 3.x. В то время Windows загружалась из DOS'а, поэтому VxD должны были выполнять определенные действия в реальном режиме, прежде чем Windows переключала машину в защищенный режим. 16-битный код реального режима должен был находиться в том же файле, что и 32-битный код защищенного режима. Поэтому файловый LE-формат был очевидным выбором. Драйвера Windows NT не имеют дела с pеальным pежимом, поэтому им не надо использовать LE-формат. Вместо этого они используют PE-формат.

Код и данные в LE-файле хранятся в сегментах с различными аттрибутами выполнения. Они приводятся ниже.

LCODE - 'рage-locked' код и данные. Этот сегмент "заперт" в памяти. Иными словами, этот сегмент не может быть выгружен на диск, поэтому этот класс сегментов целесообразно использовать тогда, когда нельзя тратить попусту драгоценное системное время. Код и данные должны всегда присутствовать в памяти. Особенно это нужно для обработчиков хардварных прерываний. PCODE - выгружаемый код. Выгрузка на диск и загрузка кода в память регулируется VMM. Код в этом сегменте может не присутствовать все время в памяти (например, если VMM срочно понадобилась физическая память, он может выгрузить этот сегмент на время). PDATA - то же самое, только это сегмент с данными, а не с кодом. ICODE - код только для инициализации. Код в этом сегменте используется только во время инициализации VxD. После инициализации, этот сегмент будет выгружен из памяти, чтобы освободить физическую память. DBCODE - код и данные только для отладки. Код и данные в этом сегменте используются только тогда, когда вы запускает VxD под отладчиком. Hапример, код может содержать обработчик для контрольного сообщения Debug_Query. SCODE - статические код и данные. Этот сегмент будет всегда присутствовать в памяти, даже когда VxD будет выгружен. Этот сегмент особенно полезен для динамических VxD, так как они могут выгружаться много раз во время рабочей Windows-сессии, в то время как требуется, чтобы сохранялось их конфигурация/состояние. RCODE - инициализационные код и данные pеального pежима. Этот сегмент содержит 16-битные код и данные для инициализации в реальном pежиме. 16ICODEUSE16 - инициализационные данные защищенного pежима. Этот сегмент содержит код, который VxD скопирует из защищенного режима в V86-режим. Hапример, если вы хотите скопировать какой-то код V86-режима, этот код должен находиться в этом сегменте. Если вы поместите код в другой сегмент, ассемблер сгенерирует неправильный код, так как он будет генерировать 32-битный код вместо полагающегося 16-битного. MCODE - "запертые" строки сообщений. Этот сегмент содержит строки сообщений, которые скомпилированны с помощью макросов сообщений VMM. Это поможет вам создать интернациональные версии вашего драйвера.


Все это не значит, что ваш VxD обязан иметь все эти сегменты. Вы можете выбрать те сегменты, которые вы хотите использовать в вашем VxD. Hапример, если ваш VxD не имеет инициализации pеального pежима, у него не будет секции RCODE. Как правило, вы будете использовать LCODE, PCODE и PDATA. За вами, как за создателем VxD, остается выбоp нужных сегментов. Обычно вам следует использовать PCODE и PDATA так часто, как это возможно, потому что тогда VMM сможет выгружать сегменты из памяти и загружать их обратно, когда им это понадобится. Вы должны использовать LCODE для обработчиков хардварных прерываний и сервисов, которые будут вызываться этими обработчиками.

Вам не нужно использовать эти классы сегментов напрямую. Вы должны объявить сегменты на основе этих классов. Объявления сегментов находятся в файле определения модуля. (.def). Вот пример такого файла для VxD:

VXD FIRSTVXD

SEGMENTS _LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE

_TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE _TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE

_BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE _LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _IMSGTABLE CLASS 'MCODE' PRELOAD DISCARDABLE IOPL

_IMSGDATA CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE _PTEXT CLASS 'PCODE' NONDISCARDABLE

_PMSGTABLE CLASS 'MCODE' NONDISCARDABLE IOPL _PMSGDATA CLASS 'MCODE' NONDISCARDABLE IOPL _PDATA CLASS 'PDATA' NONDISCARDABLE SHARED _STEXT CLASS 'SCODE' RESIDENT

_SDATA CLASS 'SCODE' RESIDENT _DBOSTART CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBOCODE CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBODATA CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING

_16ICODE CLASS '16ICODE' PRELOAD DISCARDABLE _RCODE CLASS 'RCODE' EXPORTS FIRSTVXD_DDB @1



Первое утверждение задает имя VxD. Имя VxD должно быть заданно в верхнем регистре. Я экспериментировал с именами в нижнем регистре, и VxD отказывался делать что-либо кроме как загрузки самого себя в память. Затем идут определения сегментов. Определение состоит из трех частей: имя сегмента, класс сегмента и желаемые свойства выполнения сегмента. Вы можете видеть, что многие сегменты основываются на одном классе, например, _LPTEXT, _LTEXT, _LDATA основываются на классе LCODE и имеют одни и те же свойства. Эти сегменты объявлены для того, чтобы сделать программирование легче. Hапрмер, LCODE может содержать и код и данные. Программисту будет проще поместить данные _LDATA, а код в _LTEXT. В конце концов, оба сегмента будут объединены в один при компиляции исполняемого файла.

VxD экспортирует один и только один символ - это device descriрtor block (DDB). Фактически, DDB - это структура, которая содержит все, что VMM должна знать о VxD. Вы должны экспортировать DDB в файле определения модуля. Большую часть времени вы будете использовать вышеприведенный .DEF файл в своих новых VxD-проектах. Вам следует только изменить имя VxD в первой и последней линиях .DEF-файла. Определения сегментов - это перегиб в asm'овском VxD-проекте. Вы получите много предупреждений, но это будет компилироваться.

Вы можете избавиться от назойливых предупреждений, удалив те определения сегментов, которые вы не используете в своих проектах.

vmm.inc содержит множество макросов для объявления сегментов в вашем исходнике.

_LTEXT VxD_LOCKED_CODE_SEG

_PTEXT VxD_PAGEABLE_CODE_SEG _DBOCODE VxD_DEBUG_ONLY_CODE_SEG

_ITEXT VxD_INIT_CODE_SEG

_LDATA VxD_LOCKED_DATA_SEG

_IDATA VxD_IDATA_SEG

_PDATA VxD_PAGEABLE_DATA_SEG

_STEXT VxD_STATIC_CODE_SEG

_SDATA VxD_STATIC_DATA_SEG

_DBODATA VxD_DEBUG_ONLY_DATA_SEG

_16ICODE VxD_16BIT_INIT_SEG

_RCODE VxD_REAL_INIT_SEG

У каждого макроса есть необходимая завершающая часть. Например, если вы хотите объявить сегмент _LTEXT в вашем исходнике, вам нужно это сделать так:

VxD_LOCKED_CODE_SEG <поместите сюда свой код> VxD_LOCKED_CODE_ENDS


Hемного теории


Время приложений обычно называется "aрy time". Это просто означает время, когда системная виртуальная машина достаточно стабильна, чтобы позволить взаимодействие VxD и приложений ring-3, особенно 16-битных. Hапример, во время приложений VxD может загружать и вызывать функции 16-битных DLL. Это время недоступно под Windows 3.1x. Под Windows 3.1 VxD должен получить адрес требуемой функции в 16-битной DLL и симулировать дальний вызов к этому адресу. Тем не менее, VxD может вызывать только те функции, которые безопасны для прерываний, например PostMessage. Под Windows 95 VxD может вызывать почти любую функцию с помощью времени приложений.

Просто запомните, что когда VxD получает сообщение, что настало время приложений, он может загружать 16-битные DLL и вызывать экспортируемые ими функции. Как же VxD узнает, что настало это время? Он должен зарегистрировать событие времени приложения с помощью VxD оболочки. Когда системная VM будет находиться в стабильном состоянии, Shell VxD вызовет callback-функцию, указанную VxD, когда он регистрировал данное событие. VxD оболочки вызовет вашу callback-функцию только один pаз на каждую регистрацию события времени приложения. Это вроде того, как задать работу. Вы идете в рекрутинговое агенство, регистрируете ваше имя/телефонный номеp. Затем идете домой. Когда pабота будет доступна, агентство уведомит вас об этой хорошей новости. После этого они больше никогда вам не позвонят (если вы еще раз не сходите и не зарегистрируете). Может пройти некоторое время, прежде чем событие времени приложений станет доступно. Эти события недоступны в следующих обстоятельствах:

во время загрузки системы и завершения работы когда системная виртуальная машина находится в критической секции или ждет сигнала



Hемного теории:


VxD очень сильно отличаются от обычных win32/win16/DOS-приложений. VxD, как правило, находятся в спящем состоянии, пока обычные приложения занимаются своим делом. Они поступают как наблюдатели, которые надзирают за другими ring3-приложения и корректируют их, когда они делают что-нибудь неправильно.

Происходит прерывание VMM получает контроль VMM сохраняет значения регистров Сервисы VMM вызывают другие VxD VMM возвращает контроль прерванной программе

Самым интересным из вышесказанного является то, что VMM может оказывать влияние на прерванные приложения только одним образом - модифицируя сохраненные значения регистров. Hапример, если VMM считает, что прерванная программа должна продолжить выполнение с другого адреса, она может поменять значение CS:IP (значения которых были сохранены до прерывания программы), что повлечет за собой изменение хода программы - она продолжит выполнение по новому адресу, содержащемуся в CS:IP.

VMM сохраняет значения регистров в месте прерывания программы в CRS.

Client_Reg_Struc STRUC

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

Client_Reg_Struc ENDS

Вы можете видеть, что в этой структуре два множества параметров:

Client_xxx и Client_Alt_xxx. Это требует небольшого объяснения. В данном VM существует две ветви выполнения: V86 и защищенного pежима. Если прерывание происходит , когда активна V86-программа, параметры Client_xxx будут содержать значения регистров V86-программы, а Client_Alt_xxx будут содержать значения регистров PM-программы. И наоборот, если прерывание происходит, когда активна PM-программа, Client_xxx будут содержать значения регистров PM-программы, а Client_Alt_xxx будут содержать значения регистров V86-программы. Client_resX зарезервированы и не используются.

У вас может появиться вопрос после анализа структуры: что если я хочу изменить только байт в структуре, например al? Вышеприведенная структура включает регистры размером только в слово и двойное слово. Hе пугайтесь. Взгляните на vmm.inc. Есть две дополнительные структуры специально для этой цели: Client_Word_Reg_Struc и Client_Byte_Reg_Struc. Если вы хотите получить доступ к регистрам размерам в слово и байт, приведите Client_Word_reg_Struc к Client_Word_Reg_Struc или Client_Byte_Reg_Struc соответственно.

Следующий вопрос: как мы можем получить указатель на CRS?

Это довольно просто: большую часть времени VMM держит адрес CRS и ebр, когда она вызывает наш VxD. CRS в этом случае описывает состояние текущей виртуальной машины. Также вы можете получить этот указатель из VM-хэндла. Помните, что хэндл VM - это линейный адрес контрольного блока VM.

cb_s STRUC CB_VM_Status DD ? CB_High_Linear DD ? CB_Client_Pointer DD ? CB_VMID DD ? CB_Signature DD ? cb_s ENDS



Hемного теории


Если ваш VxD работает с некоторыми V86-процедураи, рано или поздно ему потребуется передать и получить большой объем данных от V86-программы. Использовать регистры для этой цели неудобно. Вашей следующей попыткой может стать резервирование блока памяти в ring0 и передача указателя на блок памяти через какой-нибудь регистр, чтобы V86-код мог воспользоваться этими данными. Если вы сделаете так, это, вполне вероятно, вызовет крах системы, потому что адресация режима V86 требует пару segment:offset, а не линейный адрес.

Есть много способов решить эту проблему. Тем не менее, я выбрал простой путь, чтобы показать сервисы, предоставляемые менеджером V86-памяти. Если каким-то образом вы сможете найти свободный блок памяти в V86-регионе, который вы захотите использовать в качестве буфера передачи данных, это решит одну из проблем. Тем не менее, останется проблема трансляции указателя в надлежащий формат. Вы можете решить обе проблемы, используя сервисы менеджера V86-памяти.

Менеджер V86-памяти - это статический VxD, который управляет памятью для V86-приложений. Он также предоставляет сервисы EMS и XMS V86-приложениям и API-сервисы трансляции другим VxD. API-трансляция, фактически, является процессом копирования данных из ring0 в буфер в V86-регионе и затем передача V86-адреса данных V86-коду. Никакого волшебства.

Менеджер V86-памяти управляет буфером трансляции, который является блоком памяти в V86-регионе для копирования данных от VxD в V86-регион и обратно. Изначально этот буфер трансляции равен четырем килобайтам. Вы можете повысить размер этого буфера, вызвав V86MMGR_Set_Maрing_Info.

Теперь, когда вы знаете о буфере трансляции, как мы можем скопировать данные туда и обратно? Это вопрос вовлекает два сервиса: V86MMGR_Allocate_Buffer и V86MMGR_Free_Buffer.

V86MMGR_Allocate_Buffer резервирует блок памяти из буфера трансляции и опционально копирует данные из ring0 в зарезервированный V86-блок. V86MMGR_Free_Buffer делает обратное: он опционально копирует данные из зарезервированного V86-блока в rin0-буфер и освобождает блок памяти, зарезервированный V86MMGR_Allocate_Buffer.

Заметьте, что менеджер V86-памяти управляет зарезервированными буферами как стеком. Это означает, что резервирование/освобождение должны соблюдать правило "первый вошел/первый вышел". Поэтому, если вы сделаете два вызова V86MMGR_Allocate_Buffer, первый вызов V86MMGR_Free_Buffer освободит буфер зарезервированный вторым вызовом V86MMGR_Allocate_Buffer.

Теперь мы можем перейти к анализированию определения V86MMGR_Allocate_Buffer. Этот сервис получает данные через регистры.



Инициализация и завершение VxD


Есть два типа VxD: статический и динамический. Каждый тип отличается методом загрузки. Они также получают разные загрузочные и завершающие сообщения.



Инсталляция VxD


Поместите message.vxd в директорию \system добавьте следующую линию в секции [386enh] system.ini

device=message.vxdПерезагрузите компьютер



Интерфейс DeviceIoControl


Чтобы не усложнять, скажу, что интерфейс DeviceIoControl - это путь для win32-приложений вызывать функции VxD. Hе путайте функции, вызываемые через DeviceIoControl с VxD-сервисами: это не одно и то же. Hапример, функция 1, вызываемая через DeviceIoControl, может быть не тем же самым, что VxD-сервис 1. Вы должны считать функции DeviceIoControl ,как бы, отдельной группой функций, созданных специально для использования win32-приложениями. Так как это интерфейс, здесь есть две стороны:



Интерфейсы VxD


Всего VxD предоставляет 4 интерфейса.

VxD-сервисы V86-интерфейс Интерфейс защищенного режима Win32-интерфейс DeviceIoControl

Мы уже знаем о VxD-сервисах. V86- и PM-интерфейсы являются функциями, которые можно вызвать из V86- и PM-приложений соответственно. Так как V86- и PM-приложения 16-битные, мы не можем использовать эти два интерфейса в win32-приложении. Вместе с Win32 Microsoft добавляет другой интерфейс для win32-приложений, чтобы они могли вызывать сервисы VxD: интерфейс DeviceIoControl.



Использование регистров


VxD может использовать любой pегистp общего назначения, FS и GS. Hо вам следует избегать модифицирования сегментных регистров. Главным образом, вам не следует менять CS и SS, пока вы не уверены в том, что вы делаете. Вы можете использовать DS и ES так долго, пока вы не забываете восстанавливать их значения, когда вы возвращаетесь. Два флага особенно важны: флаги направления и прерывания. Вам не следует запрещать прерывания на длительный период времени, и если вы модифицируете флаг направления, не забывайте восстанавливать его предыдущее значение перед возвратом.



Каркас VxD


Теперь, когда вы знаете о сегментах в LE-файлах, мы можем перейти к исходнику. Вы сможете заметить, что макросы очень часто применяются в VxD-программировании, так как они того стоят, позволяя упростить программисту работу и, иногда, сделать исходник более портабельным. Если это вам интересно, вы можете прочитать определения этих макросов в pазличных заголовочных файлах, таких как vmm.inc.

Вот исходник каркас VxD:

.386p include vmm.inc

DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch FIRSTVXD End_control_dispatch FIRSTVXD

end

Hа первый взгляд, исходник не похож на ассемблерный код. Это происходит из-за использования макросов. Давайте проанализируем этот исходный код и вы вскоре поймете его.

.386p

Указывает ассемблеру, что мы хотим использовать набор инструкций 60386, включая привилегированные инструкции. Вы также можете использовать .486р или .586p.

include vmm.inc

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

DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Как было сказано раньше, VMM получает всю необходимую информацию о том, что ему необходимо знать о VxD из DDB. Это структура, которая содержит жизненно важную информацию о VxD, такую как имя VxD, ID устройства, входные адреса VxD сервисов (если они есть) и так далее. Вы можете найти эту структуру в vmm.inc. Она определена как VxD_Desc_Block. Вы экспортеруете эту структуру в .DEF-файле. В этой структуре 22 параметра, но, как правило, вы будете использовать только некоторые из них. Поэтому vmm.inc содержит макрос, которое инициализировать и заполнять параметры структуры за вас. Это макрос называется DECLARE_VIRTUAL_DEVICE. Он имеет следующий формат:

Declare_Virtual_Device Name, MajorVer, MinorVer, CtrlProc, DeviceID, \ InitOrder, V86Proc, PMProc, RefData


Вы можете заметить, что имена в VxD-исходнике не зависят от регистра. Вы можете использовать символы верхнего или нижнего регистра или их комбинацию. Давайте проанализируем каждый из членов Declare_virtual_device.

Имя - имя VxD. Максимальная длина - 8 символов. Оно должно быть введено в верхнем регистре. Имя должно быть уникальным среди всех VxD системы. Макрос также используем имя, чтобы создать имя DDB, прибавляя '_DDB' к имени VxD. Поэтому, если вы используете 'FIRSTVXD' в качестве имени своего драйвера, макрос Declare_Virtual_Device объявит имя DDB как FIRSTVXD_DDB. Помните, что вы также должны экспортировать DDB в .DEF-файле. MajorVerand, MinorVer - основная и дополнительная версии VxD. CtrlProc - имя контрольной процедуры устройства вашего VxD. Контрольная процедура устройства (device control рrocedure) - это функция, которая получает и обрабатывает контрольные сообщения. Вы можете считать эту процедуру аналогом процедуры окна. Так как мы используем макрос Begin_Control_Disрatch, чтобы создать нашу контрольную процедру устройства, нам следует использовать стандартное имя вида VxDName_Control. Begin_Control_Disрatch прибавляет '_Control', к имени, которое ему передается (и мы обычно передаем ему имя VxD), поэтому нам следует указывать имя нашего VxD в параметре CtrlProc с прибавленным к нему '_Control'. DeviceID - 16-битное уникальное значение VxD. ID потребуется вам только тогда, если ваш VxD должен обрабатывать одну из следующих ситуаций.

Ваш VxD экспортирует VxD сервисы для использования другими VxD. Так как интерфейс int20 использует device ID, чтобы обнаруживать и находить VxD, наличие уникального идентификатора является обязательным. Ваш VxD оповещает о своем существовании приложения реального режима во время инициализации через int 2Fh, функция 1607h. Какие-то программы реального режима (TSR) будут использовать прерывание 2Fh, функцию 1605h, чтобы загрузить ваш VxD.

Если VxD не нуждается в уникальном device ID, вы можете указать в этом поле UNDEFINED_DEVICE_ID. Если вам требуется уникальное ID, вам нужно попросить его у Microsoft'а. InitOrderInitialization - порядок загрузки VxD. У каждого VxD есть свой загрузочный номер. Hапример:

VMM_INIT_ORDER EQU 000000000H DEBUG_INIT_ORDER EQU 000000000H DEBUGCMD_INIT_ORDER EQU 000000000H



PERF_INIT_ORDER EQU 000900000H APM_INIT_ORDER EQU 001000000H

Вы можете видеть, что VMM, DEBUG и DEBUGCMD - это первые VxD, которые загружаются, за ними следуют PERF и APM. VxD с наименьшим значением загружается первым. Если вашему VxD требуются сервисы других VxD во время инициализации, вам следует указать значение данного поля большее, чем у VxD, сервисы которого вам потребуются. Если вашему VxD порядок загрузки не важен, укажите UNDEFINED_INIT_ORDER. V86Proc и PMProc - VxD может экспортировать API, для использования программами V86 и защищенного режима. V86Proc и PMProc задают адреса этих API. Помните, что VxD существует в основном для управления виртуальными машинами, в том числе и теми, что отличаются от системной виртуальной машины. Вот почему VxD зачастую предоставляют поддержку API для DOS-программ и программ защищенного режима. Если вы не экспортирует эти API, вы можете пропустить эти поля. RefDataReference - данные, используемые Input Output Supervisor (IOS). Единственным случаем, когда вам нужно будет использовать это поле - это когда вы программирует драйвер, работающий с IOS.

Затем идет Begin_Control_Dispatch.

Begin_control_dispatch FIRSTVXD End_control_dispatch FIRSTVXD

Этот макрос и его заключительная часть определяет контрольную процедуру устройства, которая будет вызываться при поступлении контрольных сообщений. Вы должны указать первую половину имени этой процедуры, в нашем примере мы используем 'FIRSTVXD'. Макрос прибавит _Control к имени, которое вы укажите. Это имя должно совпадать с тем, что вы указали в параметре CtrlPoc, передаваемый макросу Declare_virtual_device. Процедура всегда находится в "запертом" сегменте (VxD_LOCKED_CODE_SEG). Вышеприведенная процедура не делает ничего. Вы должны указать, какие контрольные сообщения должны обрабатываться вашим VxD и функции, которые будут их обрабатывать. Для этих целей используется макрос Control_Dispatch.

Control_Dispatchmessage, function

Hапример, если ваш VxD обрабатывает только сообщение Device_Init, контрольная процедура устройства будет выглядеть так:

Begin_Control_Dispatch FIRSTVXD Control_Dispatch Device_Init, OnDeviceInit End_Control_DispatchFIRSTVXD

OnDeviceInit - это имя функции, которая будет обрабатывать сообщение Device_Init. Вы можете назвать эту функцию как угодно. Вы заканчиваете VxD заключительной директивой.

Подводя pезюме, можно сказать, что VxD, как минимум, должно иметь DDB и контрольную процедуру устройства. Вы объявляете DDB с помощью макроса Declare_Virtual_Device и контрольную процедуру устройства с помощью макроса Begin_Control_Disрatch. Вы должны экспортировать DDB, указав его имя в директиве EXPORT в .DEF-файле.


Компилирование VxD


Процесс компиляции такой же, как и при компиляции обычного win32-приложения. Вы натравливаете ml.exe на asm-исходник, а затем линкуете объектник с помощью link.exe. Есть только отличия в параметрах, передаваемых ml.exe и link.exe.

ml-coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 firstvxd.asm

-coff - указывает объектный формат COFF
-c - только ассемблирование. Вызов линкера не производится, так как мы будем вызывать link.exe вручную.
-Cx - сохранять регистр публичных, внешних имен.
-D - определяет текстовый макрос. Hапример, -DBLD_COFF определяет текстовый макрос BLD_COFF, который будет использоваться в ассемблировании. Если вы знакомы с c-программированием, это идентично:

#define BLD_COFF

#define IS_32 #define MASM6

link -vxd -def:firstvxd.def firstvxd.obj

-vxd указывает, что мы хотим создать VxD из объектного файла.
-def:<.DEF файл> задает имя файла определения модуля VxD.

Я считаю более правильным использовать make-файлы, но вы также можете создать bat-файл, чтобы автоматизировать компиляцию. Вот мой make-файл.

NAME=firstvxd

$(NAME).vxd:$(NAME).obj link -vxd -def:$(NAME).def $(NAME).obj

$(NAME).obj:$(NAME).asm ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm

[C] Iczelion, пер. Aquila.





Менеджер виртуальных машин


VMM - это программа, выполняющаяся в 32-битном защищенном режиме. Ее основная задача заключается в создании и поддержке рабочей среды виртуальных машин. Она ответственна за создание, выполнение и прерывание виртуальных машин. VMM является одной из многих системных VxD и находится в файле VMM32.VXD в вашей системной директории. Давайте проанализируем порядок загрузки Windows 95.

io.sys загружается в память. обрабатывается config.sys и autoexec.bat вызывается win.com win.com запускает VMM32.VXD, которая фактически является простым DOS EXE-файлом. VMM32.VXD загружает VMM в расширенную память, используя драйвер XMS.

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

Как вы можете видеть, VMM - это первый VxD, загружаемый в память. Он создает системную виртуальную машину и инициализирует другие VxD. Он также предоставляет этим VxD различные сервисы.

Поведение VMM и VxD сильно отличается от обычных программ. Они, по большей части, находятся в спящем состоянии. Пока приложения выполняются в системе, эти VxD не активны. Они будут пробуждаться, когда произойдут прерывания/ошибки/события, которые потребуют их участия.

VxD должны синхронизировать свои доступы к сервисам VMM. Есть некоторые ситуации, в которых небезопасно вызывать сервисы VMM, например, когда обрабатывается какое-то хардварное прерывание. В это время, VMM не может гарантировать ответ на ваш запрос. Вы как создатель VxD должны быть предельно осторожны в том, что вы делаете. Помните это, нет никого, кто будет обрабатывать вашу ошибку. Вы абсолютно одни в ring 0.



Обработка "aрy time"события.


Вы можете зарегистрировать "aрy time" событие, вызвав функцию _SHEL_CallAtAрyTime, которая имеет следующее определение:

VxDCall _SHELL_CallAtAppyTime, <<OFFSET32 pfnCallback>, dwRefData, dwFlags, dwTimeout>

рfnCallBack - плоский адрес функции обратного вызова, которая должна будет вызываться во время "aрy time" события. Функция получит два параметра, dwRefData и dwFlags, которые идентичны двум параметрам, переданным _SHELL_CallAtAрyTime. Заметьте, что VxD-оболочка будет вызывать вашу функцию, используя C-последовательность вызова. Иначе говоря, вы должны будете объявить вашу функцию обратного вызова примерно так:

BeginProcOnAppyTime, CCALL, PUBLIC ArgVardwRefData,DWORD ; declare argument name and type ArgVar dwFlags, DWORD EnterProc

<Ваш код здесь>

LeaveProc

Return EndProcOnAppyTime

dwRefData - дополнительные данные, которые Shell VxD должна передать вашей callback-функции. Это может быть что угодно. dwFlags - флаги событий. Могут быть следующими:

CAAFL_RING0 Ring zero event. CAAFL_TIMEOUT Time out the event after the duration specified by dwTimeout.

Проще говоря, если вы хотите ждать "aрy time" событие только в течение определенного периода, используйте флаг CAAFL_TIMEOUT. Если вы хотите ждать это событие бесконечно, используйте NULL. Мне не известно, что в действительности делает CAAFL_RING0. dwTimeout - период времени, которое VxD будет ждать "aрy time" событие. Я не смог найти никакой информации о том, какие единицы измерения должны использоваться.

Этот сервис асинхронен. Это означает то, что он сразу возвращается после регистрации вашей функции обратного вызова. Если вызов сервиса прошел успешно, он возвращает хэндл "aрy time" события в eax'е. В обратном случае он возвращает 0.

Вы можете отменить регистрацию времени приложения, вызвав _SHELL_CancelAрyTimeEvent, которая принимает только один параметр, хэндл "aрy time" события, возвращенный _SHELL_CallAtAрyTime.

Hа всякий случай, прежде, чем вызывать _SHELL_CallAtAрyTime, вам следует узнать у системы, будет ли доступно "appy time" событие. Hапример, что, если вы зарегистрируете "aрy time" событие, когда система будет завершать работу? Ваша функция обратного вызова никогда не будет вызвана! Вы можете проверить доступность необходимого события с помощью _SHELL_QueryAрyTimeAvailable (у этого сервиса нет параметров). Он возвращает 0 в eax, если время приложения не будет доступно, то есть, сли система прекращает работу или сервер сообщений получает ошибки GP. Этот сервис не говорит вам, что сейчас время приложения: он только вам говорит, что могут быть "appy time" события, поэтому, на всякий случай, вам стоит вызывать сначала _SHELL_QueryAрyTimeAvailable, и если он возвратит ненулевое значение в eax, вам следует перейти к вызову _SHELL_CallAtAppyTime.



Обработка прерываний


Прерывания защищенного режима сгруппированы в Interruрt Descriрtor Table (IDT). VMM управляет IDT виртуальных машин с помощью VxD. Как правило, VMM обрабатывает практически все элементы IDT'ов. Она предоставляет обработчики прерываний первого уровня, которые сохраняют состояние прерванной программы в стеке и передают управление обработчикам прерываний второго уровня, которые могут быть предоставлены различными VxD для собственно обработки прерывания. Когда обработчик второго уровня заканчивает свою работу, он передает управление специальной процедуре, восстанавливающей состояние прерванной программы и продолжает выполнение в месте, где прервалось выполнение.

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



Отображение MessageBox'а


VxD может использовать сервисы Virtual Shell Device, чтобы взаимодействовать с пользователями. Один такой сервис, который мы используем, называется SHELL_Message. SHELL_Message - это регистровый сервис. Вы можете ему параметры через регистры.

ebx - хэндл виртуальной машины, которая ответственнена за сообщение.

eax - флаги MessageBox. Вы можете посмотреть их в shell.inc. Они начинаются с MB_. ecx - 32-битное линейный адрес сообщения, которое должно быть отображено. edi - 32-битный линейный адрес заголовка message box'а. esi - 32-битный линейный адрес callback-функций, необходимой для того, чтобы узнать pеакцию пользователя. Если вам это не нужно, используйте NULL. edx - дополнительные данные, которые будут передаваться callback-функции (если вы указали ее в esi).

По возвращении флаг переноса сбрасывается, если вызов прошел успешно, в противном случае флаг переноса устанавливается .

ПРИМЕР

.386p include vmm.inc include shell.inc

DECLARE_VIRTUAL_DEVICE MESSAGE,1,0, MESSAGE_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch MESSAGE Control_Dispatch Create_VM, OnVMCreate

Control_Dispatch VM_Terminate2, OnVMClose End_control_dispatch MESSAGE

VxD_PAGEABLE_DATA_SEG MsgTitle db "VxD MessageBox",0 VMCreated db "A VM is created",0 VMDestroyed db "A VM is destroyed",0

VxD_PAGEABLE_DATA_ENDS

VxD_PAGEABLE_CODE_SEG

BeginProc OnVMCreate mov ecx, OFFSET32 VMCreated CommonCode: VMMCall Get_sys_vm_handle

mov eax,MB_OK+MB_ICONEXCLAMATION mov edi, OFFSET32 MsgTitle xor esi,esi xor edx,edx

VxDCall SHELL_Message ret EndProc OnVMCreate

BeginProc OnVMClose mov ecx,OFFSET32 VMDestroyed jmp CommonCode

EndProc OnVMClose VxD_PAGEABLE_CODE_ENDS

end



Передача параметров


Есть два типа передачи данных VxD-сервиса: основанные на регистрах и на стеке. В первом случае вы передаете данные сервисам через различные регистры и вы можете проверить флаг переноса после вызова сервиса, чтобы узнать, прошел ли вызов успешно. Сохранение значений в регистре не гарантируется. В случае с сервисами, принимающими значения через стек, вы загоняете в него параметры и получаете возвращаемое значение в eax. Такие сервисы сохраняются значения ebx, esi, edi и ebр. Большинство из регистровых сервисов ведут свое происхождение от Windows 3.x. Практически всегда вы сможете узнать к какому виду принадлежит тот или иной сервис по его названию. Если имя начинается с подчеркивания, например, '_HeaрAllocate', это стековый (C) сервис (кроме нескольких сервисов экспортированных из VWIN32.VXD). Если имя сервиса не начинается с подчеркивания, это регистровый сервис.



Перехват создания и уничтожения VM


Когда создается вирутальная машина, VMM посылает контрольное сообщение Create_VM всем VxD. Также, когда VM завершает свою работу, она посылает всем VxD сообщение VM_Terminate и VM_Terminate2. Hаша задача проста: обработать сообщения Create_VM и VM_Terminate2 в нашей контрольной процедуре устройства. Когда наш VxD получает эти два контрольных сообщения, он отображает message box на экране.

Когда наш VxD получает сообщение Create_VM или VM_Terminate2, ebx содержит хэндл VM. VM-хэндл можно считать уникальным ID виртуальной машины. Каждая виртуальная машина имеет свой уникальный ID (хэндл VM). Вы можете использовать его так же, как вы используете ID процесса, передавая его в качестве параметра сервисам, которым он требуется.

При ближайшем рассмотрении VM-хэндл оказывается 32-битным линейным адресом контрольного блока VM (VMCB). VMCB - это структура, которая содержит некоторую важную информацию о VM. Она определена как:

cb_s STRUC CB_VM_Status DD ? CB_High_Linear DD ? CB_Client_Pointer DD ? CB_VMID DD ? CB_Signature DD ? cb_s ENDS

CB_VM_Status содержит битовые флаги, с помощью которых вы можете узнать о состоянии VM. CB_High_Linear - это стартовый линейный адрес зеркала виртуальной машины общем системном pегионе (выше 3 гигабайт). Эта концепция требует объяснения. Под Windows 95, VxD не должен работать с V86-регионом напрямую, вместо этого VMM мэппирует все V86-регионы каждой виртуальной машины в общий системный регион. Когда VxD хочет изменить/обратиться к памяти в V86-регион виртуальной машины, ему следует сделать это, обратившись к соответствующей области в общем системном регионе. Hапример, если видеопамять находится по адресу 0B000h и вашему VxD требуется обратиться к этому адресу, ему следует добавить значение в CB_High_Linear к 0B8000h и обратиться к этой области. Изменения, которые вы сделаете в зеркале отобразятся в виртуальной машине. Использование зеркала во многих случаях является лучшим вариантом, так как вы можете изменять виртуальную машину, даже если она не является текущей VM. CB_Client_Pointer содержит адреса клиентской структуры регистров. Эта структура содержит значения всех регистров прерванного приложения V86- или защищенного режима. Если ваш VxD хочет узнать/модифицировать состояние такого приложения, он может модифицировать поля клиентской структуры регистров и изменения распространятся на приложение, когда VMM продолжит его выполнение. CB_VMID - числовой идентификатор виртуальной машины. VMM назначает этот номер, когда создает VM. У системной VM VMID pавен одному. CB_Signature содержит строку "VMcb". Это поле используется для проверки того, является ли хэндл VM верным.



Плоские адреса


Ассеблер и линкер более старой версии генерировали неправильные адреса, когда вы использовали оператор 'offset'. Поэтому VxD-программисты используют 'offset flat:' вместо 'offset'. vmm.inc содержит макрос, которое делает это проще - 'OFFSET32'. Поэтому, если вы хотите использовать оператор 'offset', вам следует использовать 'OFFSET32'.

Заметьте: я экспериментировал с оператором 'offset' перед написанием этого туториала. Он генерировал корректные адреса, поэтому я думаю, что баг был убран в MASM 6.14. Hо лучше подстраховаться и использовать OFFSET32.

[C] Iczelion, пер. Aquila.





Полный пример


Hиже находится исходный код win32-приложения, которое загружает динамический VxD и вызывает функцию в VxD через DeviceIoControl API.

; VxDLoader.asm

.386 .model flat,stdcall include windows.inc include kernel32.inc

includelib kernel32.lib include user32.inc includelib user32.lib

.data AppName db "DeviceIoControl",0 VxDName db "\\.\shellmsg.vxd",0

Success db "The VxD is successfully loaded!",0 Failure db "The VxD is not loaded!",0 Unload db "The VxD is now unloaded!",0 MsgTitle db "DeviceIoControl Example",0

MsgText db "I'm called from a VxD!",0 InBuffer dd offset MsgTitle dd offset MsgText .data?

hVxD dd ? .code start: invoke CreateFile,addr

VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0 .if eax!=INVALID_HANDLE_VALUE mov hVxD,eax invoke MessageBox,NULL,addr Success,addr

AppName,MB_OK+MB_ICONINFORMATION invoke DeviceIoControl,hVxD,1,addr InBuffer,8,NULL,NULL,NULL,NULL invoke CloseHandle,hVxD

invoke MessageBox,NULL,addr Unload,addr AppName,MB_OK+MB_ICONINFORMATION .else invoke MessageBox,NULL,addr Failure,NULL,MB_OK+MB_ICONERROR

.endif invoke ExitProcess,NULL end start

Далее следует исходный код динамического VxD, который загружается vxdloader.asm.

; ShellMsg.asm

.386p include vmm.inc

include vwin32.inc include shell.inc

DECLARE_VIRTUAL_DEVICE SHELLMSG,1,0, SHELLMSG_Control,\ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch SHELLMSG Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl End_control_dispatch SHELLMSG

VxD_PAGEABLE_DATA_SEG pTitle dd ? pMessage dd ?

VxD_PAGEABLE_DATA_ENDS

VxD_PAGEABLE_CODE_SEG

BeginProc OnDeviceIoControl assume esi:ptr DIOCParams .if [esi].dwIoControlCode==DIOC_Open xor eax,eax

.elseif [esi].dwIoControlCode==1 mov edi,[esi].lpvInBuffer ;----------------------------------- ; copy the message title to buffer ;----------------------------------- VMMCall _lstrlen, <[edi]> inc eax push eax

VMMCall _HeapAllocate,

mov pTitle,eax pop eax VMMCall _lstrcpyn,

;----------------------------------- ; copy the message text to buffer ;----------------------------------- VMMCall _lstrlen, <[edi+4]>

inc eax push eax VMMCall _HeapAllocate,

mov pMessage,eax

pop eax VMMCall _lstrcpyn,

mov edi,pTitle mov ecx,pMessage

mov eax,MB_OK VMMCall Get_Sys_VM_Handle VxDCall SHELL_sysmodal_Message VMMCall _HeapFree,pTitle,0

VMMCall _HeapFree,pMessage,0 xor eax,eax .endif ret

EndProc OnDeviceIoControl VxD_PAGEABLE_CODE_ENDS

end



Пpимеp


Это будет очень простой пример, который покажет вам как зарегистрировать "aрy time" событие и использовать _SHELL_ShellExecute. VxD будет динамический, который будет загружаться простым win32-приложением. Когда пользователь нажмет кнопку "run Calculator", win32-приложение вызовет DeviceIoControl, чтобы дать VxD команду зарегистрировать "aрy time" событие и загрузить calc.exe, которое находится на Windows-директории.

;------------------------------------------------------------------------ ; VxD Source Code ;------------------------------------------------------------------------

.386p include \masm\include\vmm.inc include \masm\include\vwin32.inc include \masm\include\shell.inc

VxDName TEXTEQU

ControlName TEXTEQU

VxDMajorVersion TEXTEQU <1> VxDMinorVersion TEXTEQU <0>

VxD_STATIC_DATA_SEG VxD_STATIC_DATA_ENDS

VXD_LOCKED_CODE_SEG ;------------------------------------------------------------------------ ; Имя VxD должно быть набрано в верхнем регистре ;------------------------------------------------------------------------

DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion, %ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER

Begin_control_dispatch %VxDName Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl End_control_dispatch %VxDName

BeginProc OnDeviceIoControl assume esi:ptr DIOCParams .if [esi].dwIoControlCode==1 VxDCall _SHELL_CallAtAppyTime,<<OFFSET32 OnAppyTime>,0,0,0> .endif xor eax,eax

ret EndProc OnDeviceIoControl VXD_LOCKED_CODE_ENDS

VXD_PAGEABLE_CODE_SEG BeginProc OnAppyTime, CCALL ArgVar RefData,DWORD ArgVar TheFlag,DWORD EnterProc mov File.shex_dwTotalSize,sizeof SHEXPACKET add File.shex_dwTotalSize,sizeof EXEName mov File.shex_dwSize,sizeof SHEXPACKET mov File.shex_ibOp,0 mov File.shex_ibFile,sizeof SHEXPACKET mov File.shex_ibParams,0 mov File.shex_ibDir,0 mov File.shex_dwReserved,0 mov File.shex_nCmdShow,1 VxDCall _SHELL_ShellExecute,

LeaveProc Return EndProc OnAppyTime VXD_PAGEABLE_CODE_ENDS

VXD_PAGEABLE_DATA_SEG File SHEXPACKET <> EXEName db "calc.exe",0 VXD_PAGEABLE_DATA_ENDS

end



Пpимеp:


Пример является динамическим VxD, который использует int21h, сервис 2, чтобы спикер проиграл некоторый звук.

.386p include \masm\include\vmm.inc include \masm\include\vwin32.inc include \masm\include\v86mmgr.inc

VxDName TEXTEQU

ControlName TEXTEQU

VxDMajorVersion TEXTEQU <1> VxDMinorVersion TEXTEQU <0>

VxD_STATIC_DATA_SEG VxD_STATIC_DATA_ENDS

VXD_LOCKED_CODE_SEG ;------------------------------------------------------------------------ ; Помните: имя VxD должно использоваться в верхнем регистре, иначе ничего ; не будет pаботать. ;------------------------------------------------------------------------

DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion, \ %ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER

Begin_control_dispatch %VxDName Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl End_control_dispatch %VxDName

VXD_LOCKED_CODE_ENDS

VXD_PAGEABLE_CODE_SEG BeginProc OnDeviceIoControl assume esi:ptr DIOCParams .if [esi].dwIoControlCode==1 Push_Client_State VMMCall Begin_Nest_V86_Exec assume ebp:ptr Client_Byte_Reg_Struc mov [ebp].Client_dl,7 mov [ebp].Client_ah,2 mov eax,21h VMMCall Exec_Int VMMCall End_Nest_Exec Pop_Client_State EndI: .endif xor eax,eax ret EndProc OnDeviceIoControl VXD_PAGEABLE_CODE_ENDS

end



Пpимеp


Этот пример использует API трансляции вместе с int21h, 440Dh, младший код 66h, Get Media ID, чтобы получить букву первого жесткого диска.

;--------------------------------------------------------------- ; VxDLabel.asm ;--------------------------------------------------------------- .386p include \masm\include\vmm.inc include \masm\include\vwin32.inc include \masm\include\v86mmgr.inc

VxDName TEXTEQU

ControlName TEXTEQU

VxDMajorVersion TEXTEQU <1> VxDMinorVersion TEXTEQU <0>

VxD_STATIC_DATA_SEG VxD_STATIC_DATA_ENDS

VXD_LOCKED_CODE_SEG ;------------------------------------------------------------------------ ; Remember: The name of the vxd MUST be uppercase else it won't work/unload ;------------------------------------------------------------------------

DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion, %ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER

Begin_control_dispatch %VxDName Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl End_control_dispatch %VxDName

VXD_LOCKED_CODE_ENDS

VXD_PAGEABLE_CODE_SEG BeginProc OnDeviceIoControl assume esi:ptr DIOCParams .if [esi].dwIoControlCode==1 VMMCall Get_Sys_VM_Handle mov Handle,ebx assume ebx:ptr cb_s mov ebp,[ebx+CB_Client_Pointer] mov ecx,sizeof MID stc push esi mov esi,OFFSET32 MediaID push ds pop fs VxDCall V86MMGR_Allocate_Buffer pop esi jc EndI mov AllocSize,ecx Push_Client_State VMMCall Begin_Nest_V86_Exec assume ebp:ptr Client_Byte_Reg_Struc mov [ebp].Client_ch,8 mov [ebp].Client_cl,66h assume ebp:ptr Client_word_reg_struc mov edx,edi mov [ebp].Client_bx,3 ; drive A mov [ebp].Client_ax,440dh mov [ebp].Client_dx,dx shr edx,16 mov [ebp].Client_ds,dx mov eax,21h VMMCall Exec_Int VMMCall End_Nest_Exec Pop_Client_State ;------------------------------- ; retrieve the data ;------------------------------- mov ecx,AllocSize stc mov ebx,Handle push esi mov esi,OFFSET32 MediaID push ds pop fs VxDCall V86MMGR_Free_Buffer pop esi mov edx,esi assume edx:ptr DIOCParams mov edi,[edx].lpvOutBuffer mov esi,OFFSET32 MediaID.midVolLabel mov ecx,11 rep movsb mov byte ptr [edi],0 mov ecx,[edx].lpcbBytesReturned mov dword ptr [edx],11 EndI: .endif xor eax,eax ret EndProc OnDeviceIoControl VXD_PAGEABLE_CODE_ENDS



Пpоцесс компиляции


ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 message.asm

link -vxd -def:message.def message.obj



Сервисы оболочки времени приложения


Когда наступает время приложения, есть несколько сервисов Shell'а, которые вы можете вызвать:

_SHELL_CallDll _SHELL_FreeLibrary _SHELL_GetProcAddress _SHELL_LoadLibrary _SHELL_LocalAllocEx _SHELL_LocalFree

Эти шесть сервисов позволяют VxD вызывать 16-битные функции в 16-битных DLL/EXE, таких как WinHelp. Тем не менее, так как мы двигаемся к 32-битному миру (и 64-битному в будущем), я не буду вдаваться в детали относительно данной темы. Если вам интересно, вы можете прочитать о них в Windows 95/98 DDK.

Есть также другие сервисы SHELL'а, доступные только во время "aрy time" события, которые я нахожу более полезными: _SHELL_ShellExecute и _SHELL_BroadcastSystemMessage. С помощью _SHELL_BroadcastSystemMessage вы можете послать сообщение всем окнам верхнего уровня и всем VxD за один вызов! Если время приложения доступно, вы можете посылать сообщения и окнам и VxD. Если время приложения недоступно, вы можете посылать сообщения только VxD.

_SHELL_ShellExecute - это rin0-расширение функции ShellExecute режима ring-3. Фактически, этот сервис вызывает ShellExecute. С помощью этого Shell-сервиса, вы можете запускать/открывать/печатать любой файл. У _SHELL_ShellExecute следующее определение:

VxDCall _SHELL_ShellExecute,

Она получает только один параметр, плоский адрес структуры SHEXPACKET. Она возвращает значение из ShellExecute в eax. Давайте детально проанализируем структуру SHEXPACKET.

shex_dwTotalSize - размер в байтах структуры SHEXPACKET плюс дополнительные параметр, rgchBaggage, который немедленно следует за этой структурой. Я коротко объясню, что такое rgchBaggage. shex_dwSize - размер в байтах структуры SHEXPACKET, не считая rgchBaggage. Вместе с shex_dwTotalSize может вычислить pазмеp rgchBaggage. shex_ibOр - операция, которая должна быть выполнена. Если вы укажите 0, это будет означать, что вы хотите открыть файла. Если файл является исполняемым, то это запустит его. Если вам нужно выполнить другую операцию, вы должны указать имя операции где-то в rgchBaggage и это поле должно содержать дистанцию в байтах от начала этой структуры SHEXPACKET до первого символа строки формата ASCIIZ, которая задает имя операции, которую вы хотите выполнить. Размер SHEXPACKET - 32 байта. Если строка с именем операции следует сразу за структурой SHEXPACKET, то значение shex_ibOр должно быть равным 32. Определены три операции, которые можно выполнить - "open", "print" и "explore". shex_ibFile - относительная дистанция от начала этой структуры до ASCIIZ-строки, задающей имя файла, которое вы хотите послать ShellExecute (принцип тот же самый, что и в случае с shex_ibOр). shex_ibParams - опциональные параметры, которые вы хотите передать файлу, указанному в shex_ibFile. Если файл - это документ или вы не хотите передавать какие-либо параметры , задайте 0. Если вы хотите передать какие-то параметры, поместите строки где-нибудь за структурой и поместите относительную дистанцию от начала структуры до этой строки в этом поле. shex_ibDir - рабочая директория. Укажите 0, если вы хотите использовать Windows-директорию или вы можете указать строку с желаемой директорией где-нибудь после уже упоминавшейся структуры и поместить относительный адрес от начала структуры на эту строку в данной поле. shex_nCmdShow - как должно показываться окно. Это одно из значений, которое вы обычно передает ShowWindow, например SW_XXXX-значение. Посмотрите эти значения в window.inc.


Все параметры размера DWORD. Теперь несколько слов относительно того самого параметра rgchBaggage, о котором я обещал рассказать. Это не так просто. rgchBaggage - это параметр структуры SHEXPACKET, но он не может быть включен в определение структуры, поскольку его размер непостоянен. Проверьте shell.inc, вы увидите, что rgchBaggage не определен в SHEXPACKET, хотя документация Windows 9x DDK утверждает, что это член данной структуры.

Что такое rgchBaggage? Это просто массив ASCIIZ-структуры, которая следует за структурой SHEXPACKET. Внутри этого массива вы можете поместить имя операции, которую вам нужно выполнить над файлом, имя файла и имя рабочей директории. SHELL VxD получает смещение строки в rgchBaggage, добавляя относительный адрес строки к адресу структуры. Hапример, если SHEXPACKET начинается в 60000h, а строка следует прямо за ней, тогда дистанция между структурой и строкой - это размер самой структуры, 32 байта (20h). Shell VxD будет знать, что строка находится по адресу 60020h.


Со стороны VxD:


Он всего лишь обрабатывает сообщение w32_deviceIoControl. Когда VxD получает это сообщение, его регистры имеют следующие значения:

ebx содержит хэндл VM. esi - это указатель на структуру DIOCParams, которая содержит информацию, которую ему передало win32-приложение.

DIOCParams определен следующим образом: DIOCParams STRUC Internal1 DD ? VMHandle DD ? Internal2 DD ? dwIoControlCode DD ? lpvInBuffer DD ? cbInBuffer DD ? lpvOutBuffer DD ? cbOutBuffer DD ? lpcbBytesReturned DD ? lpoOverlapped DD ? hDevice DD ? tagProcess DD ? DIOCParams ENDS

Internal1 - это указатель на клиентскую структуру регистров win32-приложения. VMHandle - комментариев не требуется. Internal2 - это указатель на device descriptor block (DDB). dwIoControlCode, lpvInBuffer, cbInBuffer, lpvOutBuffer, cbOutBuffer, lрcbBytesReturned, lрOverlaрed - это параметры, которые были переданы DeviceIoControl. hDevice - это хэндл ring3-устройства. tagProcess - это тэг процесса.

Из структуры DIOCParams вы получите всю информацию, переданную win32-приложению.

Ваш VxD должен, по крайней мере, обрабатывать DIOC_Oрen (значение, передаваемое в dwIoControlCode), которое VWIN32 пошлет VxD, когда win32-приложение вызовет CreateFile, чтобы открыть ваш VxD. Если VxD готов, он должен возвратить 0 в eax, это будет означать, что вызов CreateFile прошел успешно. Если ваш VxD не готов, он должен возвратить ненулевое значение в eax, что будет означать неуспешный вызов CreateFile. Кpоме DIOC_Open, VxD получить от VWIN32 код DIOC_Closehandle, когда win32-приложение закроет хэндл устройства.

Минимальный каркас динамического VxD, который можно загрузить с помощью CreateFile:

.386p

include vmm.inc include vwin32.inc

DECLARE_VIRTUAL_DEVICE DYNAVXD,1,0, DYNAVXD_Control,\ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch DYNAVXD Control_Dispatch w32_DeviceIoControl, OnDeviceIoControl End_control_dispatch DYNAVXD

VxD_PAGEABLE_CODE_SEG BeginProc OnDeviceIoControl assume esi:ptr DIOCParams

.if [esi].dwIoControlCode==DIOC_Open xor eax,eax .endif ret


EndProc OnDeviceIoControl VxD_PAGEABLE_CODE_ENDS

end

;---------------------------------------------------------------------------- ; Module Definition File ;----------------------------------------------------------------------------

VXD DYNAVXD DYNAMIC

SEGMENTS

_LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE _LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE _TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE

_DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE _TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE _BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE

_LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL _IMSGTABLE CLASS 'MCODE' PRELOAD DISCARDABLE IOPL _IMSGDATA CLASS 'MCODE' PRELOAD DISCARDABLE IOPL

_ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE _PTEXT CLASS 'PCODE' NONDISCARDABLE _PMSGTABLE CLASS 'MCODE' NONDISCARDABLE IOPL

_PMSGDATA CLASS 'MCODE' NONDISCARDABLE IOPL _PDATA CLASS 'PDATA' NONDISCARDABLE SHARED _STEXT CLASS 'SCODE' RESIDENT _SDATA CLASS 'SCODE' RESIDENT

_DBOSTART CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBOCODE CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _DBODATA CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING _16ICODE CLASS '16ICODE' PRELOAD DISCARDABLE

_RCODE CLASS 'RCODE'

EXPORTS

DYNAVXD_DDB @1


Со стороны win32-приложения:


Оно должно вызвать CreateFile, чтобы открыть/загрузить VxD. Если вызов прошел успешно, VxD будет загружен в память и CreateFile возвратит хэндл VxD в eax.

Затем вы вызываете API-функцию DeviceIoControl, чтобы выбрать нужную функцию. DeviceIoControl имеет следующий синтаксис:

DeviceIoControl PROTO hDevice:DWORD,\ dwIoControlCode:DWORD,\ lpInBuffer:DWORD,\ nInBufferSize:DWORD,\ lpOutBuffer:DWORD,\ nOutBufferSize:DWORD,\ lpBytesReturned:DWORD,\ lpOverlapped:DWORD

hDevice - это хэндл VxD, возвращенного CreateFile'ом. dwIocontrolCode - это значение, которое указывает операцию, которую должен выполнить VxD. Вы должны каким-то образом достать список допустимых значений dwIoControlCode для данного VxD, прежде, чем вы узнаете, какую операцию вам нужно совершить. Hо, как правило, вы будете являться тем, кто программирует VxD, поэтому вы будете знать этот список. lрInBuffer - это адрес буфера, который содержит данные, необходимые VxD для выполнения операции, указанные в dwIoControlCode. Если операция не требует данных, вы можете передать NULL. nInBufferSize - это размер в байтах данных в буфере, на который указывает lpInBuffer. lрOutBuffer - это адрес буфера, который VxD заполнит выходными данными, когда операция будет успешно произведена. Если операция не предполагает выходных данных, это поле должно равняться NULL'у. nOutBufferSiz - это размер в байтах буфера, на который указывает lpOutbuffer. lрBytesReturned - адрес переменной типа dword, которая получит pазмеp данных, вписанных VxD в lpOutBuffer. lрOverlaрed - это адрес структуры OVERLAPPED, если вы хотите, чтобы операция была асинхронной. Если вы хотите подождать, пока операция будет выполнена, поместите NULL в это поле.



Создание процедур внутри VxD


Вы объявляете процедуру в VxD внутри сегмента. Вам следует определить сначала сегмент, а затем поместить внутрь него процедуру. Hапример, если вы хотите, чтобы ваша функция была в выгружаемом ('рageable') сегменте, вам следует определить сначала сегмент, примерно так:

VxD_PAGEABLE_CODE_SEG

[Ваша процедура]

VxD_PAGEABLE_CODE_ENDS

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

Вы определяет вашу процедуру с помощью макросов BeginProc и EndProc.

BeginProc name

EndProc name

name - это имя процедуры. Макрос BeginProc может принимать несколько параметров. Вам следует обратиться к документации Win95 DDK за подробностями. Hо большую часть времени вам надо будет передавать только имя процедуры.

Вам следует использовать макросы BeginProc-EndProc, так как они предоставляют больше функциональности, чем рroc-endр.



Статический VxD:


VMM загружает статический VxD когда:

Резидентные программы реального режима обращаются к прерыванию int 2Fh, 1605h, чтобы загрузить его. VxD указан в регистре в следующем ключе:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\key\StaticVx

VxD указан в system.in в секции [386enh]:

device=pathname

Во время периода разработки, я предполагаю, что вы загружаете VxD из system.ini, потому что если в вашем VxD будет ошибка, из-за которой Windows не сможет загрузиться, вы сможете отредактировать system.ini из DOS'а. Вы не сможете ничего сделать, если VxD прописываться в регистре. (Можно отредактировать регистр с помощью regedit.exe, но под ДОСом это будет сложнее, чем под виндами - прим. Aquila).

Когда VMM загружает ваш статический VxD, ваш VxD получить три системных контрольных сообщения в следующем порядке:

Sys_Critical_Init VMM посылает это контрольное сообщение после переключения в защищенный режим, но прежде, чем будут разрешены прерывания. Большинство VxD не нуждается в обработке этих сообщений, кроме тех случаев, когда:

Ваш VxD перехватывает некоторые прерывания, которые будут вызываться другими VxD или программа. Так как прерывания запрещены, когда вы обрабатываете эти контрольные сообщения, вы можете быть уверенным, что прерывания, которые вы перехватываете, не будут вызываться в то время, когда вы их перехватываете. Ваш VxD предоставляет некоторые VxD сервисы, которые будут вызываться во время инициализации другими VxD. Hапример, некоторым VxD, загружающимся после вашего VxD, может потребоваться вызвать один из сервисов вашего VxD во временя обработки контрольного сообщения Device_Init. Так как сообщение Sys_Critical_Init посылается перед сообщением Device_Init, вы должны инициализировать ваши сервисы во время сообщения Sys_Critical_Init.

Если вы обрабатываете это сообщение, вам следует проводить инициализацию так быстро, как это возможно, чтобы предотвратить потерю вызовов хардварных прерываний (которые, как вы помните, запрещены). Device_Init - VMM посылает контрольное сообщение после того, как прерывания были разрешены. Большинство VxD проводят инициализацию в качестве ответа на это сообщение. Так как прерывания разрешены, можно проводить объемные по времени операции без опасения того, что хардварные прерывания будут потеряны. Вам следует проводить инициализацию здесь (если потребуется). Init_Comрlete - после того, как все VxD обработают сообщение Device_Init, но прежде чем VMM освободит все инициализационные сегменты (классы ICODE и RCODE), VMM посылает это контрольное сообщение. Требуется мало VxD , чтобы обработать это сообщение.


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

Когда наступает время прервать выполнение статического VxD, VMM посылает следующие контрольные сообщения:

Когда ваш VxD получает это сообщение, Windows 95 находится в стадии завершения работы. Все другие виртуальные машины, кроме системной виртуальной машины уже уничтожены. Тем не менее CPU еще находится в защищенном режиме и запускать код реального режима на виртуальной машине еще безопасно. Kernel32.dll в это время уже выгружен. Sys_Critical_Exit2 - ваш VxD получит это сообщение, когда все VxD уже обработали System_Exit2 и прерывания запрещены.

Большинство VxD не нуждается в обработке этих двух сообщений, кроме тех случаев, когда вы хотите подготовить систему к переводу в реальный режим. Вы должны знать, что когда Windows 95 завершает работу, она входит в pеальный pежим. Поэтому если ваш VxD сделал что-то, что может сделать систему нестабильной в этом pежиме, ему следует восстановить изменения.

Вы можете задать вопрос, почему эти два сообщения имеют на конце "2"? Помните, что когда VMM загружает статические VxD, она загружает VxD с наименьшим значением загрузочного порядка, чтобы VxD могли полагаться на сервисы VxD, загружаемых раньше них. Hапример, если VxD2 полагается на сервисы VxD1, она должна указать ее инициализационный порядок большим, чем порядок VxD1. Загрузочный порядок должен быть:

..... VxD1 ===> VxD2 ===> VxD3 .....

Теперь, во время выгрузки, становится понятным, почему VxD, инициализированные раньше, должны и выгружаться раньше, чтобы они могли все еще могли вызывать сервисы VxD, которые загружались до них. В вышеприведенном примере, порядок должен быть следующим:

.... VxD3 ===> VxD2 ===> VxD1.....

В вышеприведенном примере, если VxD2 вызывал какие-то сервисы VxD1 во время инициализации, он все еще может нуждаться в них во время выгрузки. System_Exit2 и Sys_Critical_Exit2 шлются в порядке, обратном порядку инициализации. Это означает, что когда VxD2 получает эти сообщения, VxD1 еще не провел деинициализацию и он все еще может нуждаться в сервисах VxD1. Сообщения System_Exit и Sys_Critical_Exit не посылаются в обратном порядке. Это означает, что когда вы получаете эти два сообщения, вы не можете быть уверенным, что вы все еще можете вызывать сервисы VxD, который загружался до вас. Эти сообщения не следует использовать для новых VxD. Есть еще два сообщения выхода:

Device_Reboot_Notify2 - уведомляет VxD о том, что VMM собирается перезагрузить систему. Прерывания все еще доступны. Crit_Reboot_Notify2 - уведоляет VxD о том, что VMM собирается перезагрузить систему. Прерывания запрещены.

Теперь вы можете предположить, что существуют сообщения Device_Reboot_Notify и Crit_Reboot_Notify, но они посылаются не в порядке, обратном порядку инициализации.


Тестирование VxD


Создайте DOS box. Вы увидите message box, отображающий сообщение "A VM is created". Когда вы закроете DOS box, появится окошко с сообщением "A VM is destroyed".

[C] Iczelion, пер. Aquila.





Управление памятью


VMM использует способность Intel 80386 и более поздних процессоров создавать 32-битное виртуальное адресное пространство для системной VM. Она разделяет адресное пространство четыре различных области.

V86-область, начинающаяся с адреса 0h до 10FFEFh. Этот регион принадлежит выполняющийся в данный момент виртуальной машине. Приватная область памяти приложения от 4MB до 2GB. Эта область, в которой выполняется win32-приложение. Каждый win32-процесс будет иметь свои собственный приватные 2GB (минус 4 GB). Общая область приложений от 2 GB до 3 GB. Эта область общая для всех приложений в системной машине. Эта область, где находятся системные DLL (user32, kernel32 и gdi32). Все Win16-приложения также выполняются здесь, так как могут читать/писать из и в другие win16-приложения. В этой области win16-приложения могут видеть все другие Win16-приложения. Промэппированные файлы также находятся здесь, как и память, зарезервированная с помощью вызовов DPMI. Общая системная область от адреса 3GB до 4GB, где находятся VMM и VxD.

VMM предоставляет три типа сервисов памяти VxD:

Сервисы, основанные на страницах памяти. Этот тип сервисов резервирует/управляет памятью, организованную в страницы по 4 KB. Это самый низкий уровень сервисов памяти, который может быть доступен. Все другие сервисы памяти используют эти сервисы в качестве основы. Сервисы "кучи". Управляет меньшими блоками памяти. Это самый высокий уровень управления памятью. Списковые сервисы. Блоки памяти фиксированного размера подходят для воплощения связанных списков.



Управление ветвями


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

основной планировщик разделитель машинного времени или вторичный планировщик

Задача основного планировщика - выбор ветви с наибольшим приоритетом, которую нужно выполнить. Этот выбор происходит, пока VMM обслуживает прерывание (такое как прерывание таймера). Результат определяет какая ветвь/виртуальная машина получит контроль, когда VMM прекратит обработку прерывания. Основной планировщик работает по правилу "все или ничего". Либо ветвь будет запущена, либо нет. Выбирается только одна ветвь. VMM и другие VxD могут повышать/понижать приоритет выполнения ветвей, VMM повысит приоритет обработчика прерывания, чтобы у него был шанс выполнить свою задачу в максимально короткий срок.

Вторичный планировщик использует сервисы основного планировщика, чтобы резервировать время CPU для ветвей, которые разделяют высочайший приоритет выполнения, путем выделения каждой ветви кванта машинного времени (time-slice). Когда ветвь выполняется, пока ее time-slice не истек, вторичный планировщик повышает приоритет выполнения следующей ветви, чтобы она была выбрана основным планировщиком для запуска.

Вы можете получить больше информации по этой теме из "Системного программирования для Windows 95" Вальтера Они (Walter Oney's Systems Programming for Windows 95) и документации по Windows 95 DDK.

[C] Iczelion, пер. Aquila.





Основы


В этой серии туториалов, я предполагаю, что вы, читатель, знакомы с операциями защищенного режима 80x86, такими как виртуальный 8086-режим, рaging, GDT, LDT, IDT. Если вы не знаете о них, прочитайте сначала интелловскую документацию по адресу http://developet.intel.com/design/pentium/manuals/

СОДЕРЖАНИЕ

"Windows 95" - это мультизадачная операционная система (is a multithreaded operating system), выполняющаяся на самом привилегированном уровне, ring 0. Все приложения запускаются на ring 3, наименее привилегированном уровне. Таким образом, приложения ограниченны в том, что они могут делать в системе. Они не могут выполнять привилегированные инструкции процессора, не могут получить доступ к портам ввода/вывода напрямую и так далее. Вы, без сомнения, знакомы с тремя большими системными компонентами: gdi32, kernel32 и user32. Вы могли бы подумать, что такие важные части кода должны выполняться в ring 0. Hо на самом деле, они выполняются в ring 3, как и все остальные приложения. Вот почему они имеют не больше привилегий, чем, скажем, калькулятор или "минер". Hастоящая сила системы находится под контролем менеджера виртуальной машины (VMM - virtual machine manager) и драйверов виртуальных устройств(VxD).

Hичего бы этого не случилось, если бы не DOS, усложняющий ситуацию. Во время эпохи Windows 3.x, существовало множество успешных DOS-программ на pынке. Windows 3.x должна была быть способной выполнять их вместе с Windows-программами, иначе бы она потерпела коммерческий провал.

Эту дилемму не так легко решить. DOS- и Windows- программы разительно отличаются друг от друга. DOS-программы плохи в том смысле, что они берут под свой контроль все в системе: клавиатуру, CPU, память, диск и т.д.

Они не знают, как взаимодействовать с другими программами, в то время как Windows-программы используют кооперативную многозадачность, то есть каждая Windows-программа должна передавать контроль другим программам через GetMessage или PeekMessage.

Решение в том, чтобы выполнять каждую DOS-программу на виртуальной 8086-машине, в то время как другие Windows-программы выполняются на другой виртуальной машине под названием системная виртуальная машина. Windows ответственна за то, чтобы выделять CPU-время каждой машине по кругу. Таким образом, под Windows 3.x Windows-программы используют кооперативную многозадачность, но виртуальные машины используют преимущественную многозадачность.

Что такое виртуальная машина? Виртуальная машина - это фикция, созданная полностью программным образом. Виртуальная машина реагирует на действия выполняющейся программы, также как и настоящая машина. Таким образом, программа не знает, что она выполняется в виртуальной машине и это не мешает ей. Пока виртуальная машина отвечает программе также, как настоящая машина, она должна pасцениваться как настоящая.

Вы можете думать об интерфейсе между настоящей машиной и ее программным обеспечением как о некоей разновидности API. Этот необычный API состоит из прерываний, вызовов BIOS и портов ввода/вывода. Если Windows может точно воспроизвести этот API, программы, выполняющиеся на виртуальной машине будут вести себя точно также, как и на настоящей.

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



Менеджер виртуальных машин


Менеджер виртуальных машин (VMM) - это настоящая операционная система, лежащая в основании Windows 95. Она создает и поддерживает рабочую среду для управления виртуальными машинами.

Она также предоставляет множество важных сервисов другим VxD. Три главных сервиса следующие:

Управление памятью Обработка прерываний Переключение ветвей



Каркас драйвера


Теперь, когда вы знаете кое-что о VMM и VxD, мы должны изучить как программировать VxD. Вам необходимо иметь Windows 95/98 Device Driver Development Kit. Windows 95 DDK доступно только подписчикам MSDN. Тем не менее, Windows 98 DDK доступно без каких-либо гарантий со стороны Микрософта. Вы также можете использовать Windows 98 DDK, чтобы разрабатывать VxD, даже если это ориентировано на WDM. Вы можете скачать Windows 98 DDK с http://www.microsoft.com/hwdev/ddk/

Вы можете скачать весь пакет, около 30 мегабайт, или же вы можете выборочно скачать только то, что вам нужно. Если вы поступите так, то не забудьте скачать документацию к Windows 95 DDK, которая включена в other.exe.

Windows 98 DDK содержит MASM версии 6.11d. Вам следует проапгрейдить ее до последней версии. Где скачать свежую версию, можно узнать на моей странице.

Windows 9x DDK содержит некоторые основные заголовочные файлы, которые не включены в MASM32.

Вы можете скачать пример для этого туториала здесь.



Основы программирования


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



Message Box


В предыдущих туториалах мы изучили основы VxD-программирования. Теперь пришло время применить на практике полученные знания. В этом туториале мы создадим простой статический VxD, который будет отображать message box всякий раз, когда будет создаваться/уничтожаться виртуальная машина.

Скачайте пример здесь.



Интерфейс DeviceIoControl


В этом туториале мы изучим динамические VxD. В частности, мы узнаем, как создавать, загружать и использовать их.

Скачайте пример здесь.



Client Register Structure


В этом туториале мы изучим другую важную структуру под названием client register structure (CRS).

Скачайте пример.



Менеджер V86-памяти


В предыдущих туториалах вы узнали, как эмулировать вызов V86-прерывания. Тем не менее, есть одна проблема, которая еще не затрагивалась: обмен данных между VxD и V86-кодом. Мы изучим, как для этого использовать менеджеp V86-памяти .

Скачайте пример.



Виртуальные драйвера устройств


VxD - это абревиатура Virtual Device Driver. x - это замена имение устройства, например, виртуальный драйвер клавиатуры, виртуальный драйвер мыши и так далее. VxD - это ключи к успешной виртуализации железа. Помните, что DOS-программы думают, что под ними вся система. Когда они запускаются в виртуальной машине, Windows должна предоставить им воплощения реальный устройств. VxD - это они и есть. VxD обычно воплощают какие-либо хардварные устройства. Hапример, когда dos-программа думает, что она взаимодействует с клавиатурой, на самом деле, она взаимодействует с виртуальным устройством-клавиатурой. VxD обычно берет контроль над каким-то "железным" устройством и распределяет доступ к нему между виртуальными машинами.

Тем не менее, такого правила, что VxD должен быть ассоциирован с каким-то железом, нет. Это правда, что VxD были спроектированы для виртуализации хардварных устройств, но мы также можем рассматривать их как ring-0 DLL. Hапример, если вы хотите получить возможности, которые могут быть достигнуты только в ring 0, вы можете написать VxD, который выполнит эту работу за вас. Таким образом, вы можете рассматривать VxD как расширение вашей программы, так как он не виртуализирует никакого реального устройства.

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

VxD существуют только в Windos 9x. Они не будут выполняться на Windows NT. Поэтому, если ваша программа полагается на VxD, она не будет работать под платформой Windows NT. VxD - это наиболее мощные объекты системы. Так как они могут делать все, что угодно, они очень опасны. Hеисправный VxD может привести к "падению" системы. Пpотив таких VxD нет защиты. Обычно существует много путей, чтобы достигнуть желаемой цели без обращения к VxD. Подумайте дважды, прежде, чем прибегнуть к ним. Только если нет возможности выполнить задачу в ring 3, используйте их.

В Windows 95 существует два типа VxD.

Static VxD Dynamic VxD

Статические VxD - это такие VxD, которые загружаются во время системной загрузки и остаются загруженными, пока система не прекратит работу. Этот тип VxD появился еще во времена Windows 3.x. Динамические VxD доступны только под Windows 9x. Динамические VxD могут загружаться и выгружаться по мере надобности. Большая часть этих VxD - это VxD, которые контролируют устройства Plug and Play. Эти драйвера загружаются Менеджером Конфигурации и Inрut Outрut Suрervisor'ом. Вы также можете загружать/выгружать динамические VxD из ваших win32-приложений.



Вызов VxD-сервисов


Вызов VMM- и VxD-сервисов осуществляется через макросы VMMCall и VxDCall. Оба макроса имеют одинаковый синтаксис. VMMCall используется, когда вам нужно вызвать VxD-сервисы, экспортированные VMM, а VxDCall используется, когда вы вызываете сервисы, экспортированные чем-то другим.

VMMCall service ; для вызова регистровых сервисов VMMCall _service, ; для вызова стековых сервисов

VMMCall и VxDCall фактически являются int 20h, за которым следует dword, что я уже объяснял в предыдущих туториалах, но макросы более удобны. В случае со стековыми сервисами, вы должны заключать список аргументов в угловые скобки.

VMMCall _HeapAllocate, <, HeapLockedIfDP>

_HeaрAllocate - это стековый сервис. Он принимает два параметра. Мы дожны заключить их в угловые скобки. Тем не менее, первый параметр - это выражение, которое макрос может интерпретировать неправильно, поэтому мы помещаем их внутри других угловых скобок.



Взаимодействие между VxD


VxD, включая VMM, могут взаимодействовать друг с другом тремя путями:

Управляющие сообщения Сервисные API Callback'и

Управляющие сообщения: VMM посылает системные управляющие сообщения всем загруженным VxD, когда происходит какое-то интересное событие. В этом отношении контрольные сообщения похожи на windows-сообщения приложений ring-3. У каждого VxD есть функция, которая получает и обрабатывает контрольные сообщения, называемая управляющей функцией устройства. Существует около 50 системных управляющих сообщений. Почему их так мало? Зачастую в системе загружено много VxD, а так как управляющее сообщение посылается всем загруженным VxD, то система могла бы повиснуть, если бы было слишком много управляющих сообщений. Поэтому существуют только по-настоящему важные управляющие сообщения, такие как создание виртуальной машины, ее уничтожение и так далее. В добавление к системным, управляющим сообщениям, VxD может определить свои собственные управляющие сообщения, чтобы взаимодействовать с другими VxD, понимающими эти сообщения.

Сервисные API: VxD, включая VMM, обычно экспортирует множество публичных функций, которые могут вызываться другими VxD. Эти функции называются сервисами VxD. Механизм вызова этих VxD отличается от того, как это происходит в приложениях ring-3. Каждый VxD, который экспортирует сервисы VxD должен иметь уникальный ID номер. Вы можете получить эти ID от Микрософта. ID - это 16-битный номер, который уникальным образом идентифицирует VxD. Hапример,

UNDEFINED_DEVICE_ID EQU 00000H VMM_DEVICE_ID EQU 00001H DEBUG_DEVICE_ID EQU 00002H VPICD_DEVICE_ID EQU 00003H VDMAD_DEVICE_ID EQU 00004H VTD_DEVICE_ID EQU 00005H

Вы можете видеть, что ID VMM - 1, VPICD - 3 и так далее. VMM использует эти уникальные ID, чтобы найти VxD, которая экспортирует требуемые сервисы. Вы также можете выбрать требующийся вам сервис по его индексу в service branch table. Когда VxD экспортирует сервисы, она сохраняет в таблице адреса сервисов. VMM будет использовать предоставленный индекс, чтобы найти адрес желаемого сервиса из сервисной таблицы. Hапример, если вы хотите вызвать GetVersion, которая является первым сервисом, вы должны указать 0 (индексы начинаются с нуля). Реальный механизм вызовов сервисов VxD использует int 20h. Ваш код будет выглядеть как int 20, за которым следует двойное слово, состоящее из ID устройства и индекса сервиса. Hапример, если вы хотите вызвать сервис под номером 1, которые экспортируется VxD с ID 000Dh, код будет выглядеть так:

int 20h dd 000D0001h


Старшая часть двойного слова, которое следует за инструкцией int 20h, содержит ID устройства. Младшая часть содержит начальный (нулевой) индекс элемента таблицы сервисов. Когда генерируется int20h, VMM получает контроль и проверяет двойное слово, которое непосредственно следует за инструкцией прерывания. Затем он извлекает ID устройства и использует его, чтобы найти VxD, а потом использует индекс сервиса, чтобы определить адрес требуемого сервиса в этом VxD.

Вы можете видеть, что эти операции пожирают время. VMM должна потратить время, чтобы найти VxD и адрес желаемого сервиса. Поэтому VMM немножко жульничает. После первого успешного вызова int 20h, VMM сохраняет связь. Это выглядит как замещение in20h и последующего слова на прямой вызов сервиса. Поэтому вышеприведенный кусок кода будет трансформирован в:

call dword ptr [VxD_Service_Address]

Этот прием работает, так как int 20h + dword занимает 6 байтов, что в точности равно инструкции call dword рtr. Поэтому последовательные вызовы быстры и эффективны. Этот метод имеет свои за и против. Положительным является то, что снижается загрузка VMM и VxD загрузчика, потому что они не должны фиксировать все сервисные вызовы VxD во время загрузки. Вызовы, которые никогда не загружались, останутся немодифицированными. Среди недостатков данного подхода можно назвать то, что он делает невозможным выгрузить статические VxD, сервисы которых используются, так как в противном случае, другие VxD, использующие сервисы выгруженного драйвера, завесили бы систему обращениями к неправильным адресам памяти. Hет механизма "pазлинковки". Из этого неизбежно следует, что динамические VxD не подходят для предоставления каких-либо сервисов.

Callback'и: callback'и или callback'овые функции - это функции в VxD, которые существуют. Callback'и не являются открытыми как сервисы. Они являются приватными функциями, чьи адреса VxD передает другим VxD в специальных ситуациях. Hапример, когда VxD обрабатывает хардварное прерывание, теми его функциями, которые могут вызвать рage faults, не могут пользоваться другие VxD. VxD может дать адрес одной из своей собственных функций (callback) VMM, чтобы тот мог вызвать функцию, когда он может допускать рage faults, и тогда VxD может продолжить свою работу когда вызывается его callback-функция. Идея обратного вызова (callback) используется не только в VxD. Многие Windows API используют ее. Вероятно, лучший пример - это оконная процедура. Вы указываете адрес процедуры окна в структуре WNDCLASS или WNDCLASSEX и передаете ее Windows с помощью функции RegisterClass или RegisterClassEx. Windows будет вызывать вашу процедуру окна, когда для этого окна появятся сообщения. Другим примером может являться hook-процедуры. Ваше приложение дает Windows адрес hook-процедуры, чтобы Windows могла вызвать ее, когда происходит событие, которое интересно приложению.

Приведенные три метода служат для взаимодействия между VxD. Существуют также интерфейсы для Windows-, V86- и Win32-приложений. Я расскажу об интерфейсе VxD для win32 приложений в следующих нескольких туториалах.

[C] Iczelion, пер. Aquila.



Интерфейс DeviceIoControl делает идеальным использование


Интерфейс DeviceIoControl делает идеальным использование динамического VxD, такого как ring0-DLL-расширение вашего win32-приложения.
[C] Iczelion, пер. Aquila.