Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы

       

Файл vxdsrv\vxdsrv.asm


; --------------------------------------------------- ; Виртуальный драйвер VXDSRV ; Version 1.1 ; --------------------------------------------------- ; Copyright (C) Александр Фролов 1995 ; --------------------------------------------------- ; Работает совместно с приложением dos2win.exe ; и DLL-библиотекой d2w.dll ; ; Позволяет запускать приложения Windows из виртуальной ; машины MS-DOS, из командной строки Norton Commander или ; аналогичной оболочки, работающей на виртуальной ; машине MS-DOS ; ; Выполняет перехват функции 4B00h прерывания int 21h ; и сохраняет полученные этой функцией командную строку ; и строку параметров, а также определенные отдельно ; текущий диск и текущий каталог в области памяти, ; зарезервированной DLL-библиотекой d2w.dll. ; Затем драйвер вызывает функцию, определенную ; в этой библиотеке и посылающую сообщение приложению ; dos2win.exe. Приняв сообщение, приложение dos2win.exe ; запускает программу, пользуясь данными, полученными ; от виртуального драйвера ; ---------------------------------------------------

.386p include vmm.inc

; Идентификатор драйвера VXDSRV. VXDSRV_Id equ 8000h

HiVers equ 1 ; верхний номер версии драйвера LoVers equ 1 ; нижний номер версии драйвера Vers equ ((HiVers shl 8) or LoVers) CFlag equ 1

; =================================================== ; Заголовок виртуального драйвера ; =================================================== Declare_Virtual_Device VXDSRV, HiVers, LoVers, \ VXDSRV_Control, VXDSRV_Id, Undefined_Init_Order, \ VXDSRV_V86API_Handler, VXDSRV_PMAPI_Handler,

; =================================================== ; Инициализация в реальном режиме ; =================================================== VXD_REAL_INIT_SEG

RealInit proc near

; Вывод "рекламной" текстовой строки mov ah, 9 mov dx, offset VxD_Hello int 21h

; Признак успешной инициализации mov ax, Device_Load_Ok

; Страницы физической памяти не резервируются xor bx, bx

; Данные для каждого экземпляра виртуальной машины ; не резервируются xor si, si






; Значение, передаваемое процедуре Sys_Critical_Init xor edx, edx ret RealInit endp

VxD_Hello db '*VXDSRV* Copyright (C) Alexandr Frolov 1995' db 0dh, 0ah, '$' VXD_REAL_INIT_ENDS

; =================================================== ; Системная критическая инициализация ; =================================================== VXD_ICODE_SEG BeginProc VXDSRV_Sys_Crit_Init

; Устанавливаем фильтр для прерывания int 21h mov eax, 21h mov esi, offset32 V86_Int21_Handler VMMcall Hook_V86_Int_Chain

clc ; признак успешного завершения ret EndProc VXDSRV_Sys_Crit_Init VXD_ICODE_ENDS

; =================================================== ; Зафиксированный сегмент кода ; =================================================== VxD_LOCKED_CODE_SEG

; --------------------------------------------------- ; Определение управляющих процедур ; --------------------------------------------------- BeginProc VXDSRV_Control

; Процедура системной критической инициализации Control_Dispatch Sys_Critical_Init, VXDSRV_Sys_Crit_Init

clc ret EndProc VXDSRV_Control

VxD_LOCKED_CODE_ENDS

; =================================================== ; Сегмент данных ; =================================================== VxD_DATA_SEG

; --------------------------------------------------- ; Таблица адресов функций API драйвера ; --------------------------------------------------- VXDSRV_API_Call label dword

dd offset32 vxdapiGetVersion ; AX=0 dd offset32 vxdapiRegisterWnd ; AX=1 dd offset32 vxdapiUnregisterWnd ; AX=2

VXDSRV_API_MaxCall EQU ($ - VXDSRV_API_Call) / 4

CallbackSel dw 0h ; селектор функции обратного вызова CallbackOff dd 0h ; смещение функции обратного вызова V86CallFlag dd 0 ; вызов из виртуальной машины V86 CallbackBuf dd 0 ; адрес буфера для передачи строки

flatpCmdLine dd 0 ; адрес командной строки flatpParmLine dd 0 ; адрес строки параметров hSem dd 0 ; идентификатор семафора

nCurDisk db 0 ; текущий диск szCurPath db 64 dup(0) ; текущий каталог

; Сегмент буфера для получения текущего пути wPathSeg dw 0



; Соответствующий этому буферу FLAT-адрес flatpPathBuf dd 0

VxD_DATA_ENDS

; =================================================== ; Перемещаемый сегмент кода ; =================================================== VxD_CODE_SEG

; --------------------------------------------------- ; Вход API для виртуальных машин V86 ; --------------------------------------------------- BeginProc VXDSRV_V86API_Handler mov eax, 1 ; признак машины V86 mov V86CallFlag, eax

; Вызываем универсальное API драйвера call VXDSRV_API_Handler ret EndProc VXDSRV_V86API_Handler

; --------------------------------------------------- ; Вход API для виртуальных машин защищенного режима ; --------------------------------------------------- BeginProc VXDSRV_PMAPI_Handler mov eax, 0 ; признак защищенного режима mov V86CallFlag, eax

call VXDSRV_API_Handler ret EndProc VXDSRV_PMAPI_Handler

; --------------------------------------------------- ; Универсальное API драйвера, вызывается как для ; режима VM86, так и для защищенного режима ; --------------------------------------------------- BeginProc VXDSRV_API_Handler

; Загружаем номер функции API movzx eax, [ebp.Client_AX]

; Проверяем этот номер на допустимость cmp eax, VXDSRV_API_MaxCall jae short InvalidNumber

; В случае успеха сбрасываем флаг переноса и ; вызываем функцию по таблице адресов and [ebp.Client_EFlags], NOT CFlag call VXDSRV_API_Call[eax * 4] ret InvalidNumber: or [ebp.Client_EFlags], CFlag ret EndProc VXDSRV_API_Handler

; --------------------------------------------------- ; vxdapiGetVersion, номер = 0 ; Возвращает в AX номер версии: ; AH - старший номер, AL - младший номер ; --------------------------------------------------- BeginProc vxdapiGetVersion mov [ebp.Client_AX], Vers clc ; успешное завершение ret EndProc vxdapiGetVersion

; --------------------------------------------------- ; vxdapiRegisterWnd, номер = 1 ; --------------------------------------------------- BeginProc vxdapiRegisterWnd

; Можно вызывать только из защищенного режима mov eax, V86CallFlag cmp eax, 0 jnz short RW_CallFromRealMode



; Сохраняем смещение и селектор процедуры обратного ; вызова, расположенной в фиксированном сегменте ; кода DLL-библиотеки d2w.dll

movzx eax, [ebp.Client_CX] ; смещение mov [CallbackOff], eax mov ax, [ebp.Client_DX] ; селектор mov [CallbackSel], ax

; Преобразуем адрес буфера во flat-адрес

mov ax, (Client_SI shl 8) + Client_DI VMMcall Map_Flat mov [CallbackBuf], eax

; Создаем семафор с начальным значением 1 mov ecx, 1 VMMcall Create_Semaphore mov hSem, eax ; сохраняем идентификатор семафора

clc ret

RW_CallFromRealMode: stc ret EndProc vxdapiRegisterWnd

; --------------------------------------------------- ; vxdapiUnregister, номер = 2 ; --------------------------------------------------- BeginProc vxdapiUnregisterWnd

; Можно вызывать только из защищенного режима mov eax, V86CallFlag cmp eax, 0 jnz short UW_CallFromRealMode

mov eax, 0 mov [CallbackOff], eax mov [CallbackSel], ax mov [CallbackBuf], eax

; Уничтожаем семафор mov eax, hSem VMMcall Destroy_Semaphore

clc ret

UW_CallFromRealMode: stc ret EndProc vxdapiUnregisterWnd

; --------------------------------------------------- ; StrCpy, копирование строки ASCIIZ ; [ecx] - исходный адрес ; [edx] - адрес буфера ; --------------------------------------------------- BeginProc StrCpy push eax push ecx push edx

StrCpyLoop: mov al, [ecx] mov [edx], al cmp al, 0 jz short StrCpyEnd inc ecx inc edx jmp short StrCpyLoop StrCpyEnd:

pop edx pop ecx pop eax ret EndProc StrCpy

; --------------------------------------------------- ; CopyParm, копирование строки параметров ; --------------------------------------------------- BeginProc CopyParm push eax push ebx push ecx push edx

; Вычисляем адрес строки параметров в буфере mov edx, CallbackBuf add edx, 65 + 128

; Определяем размер строки параметров mov ecx, flatpParmLine movzx ebx, byte ptr [ecx] inc ecx

; Если параметров нет, закрываем строку нулем cmp ebx, 0 jz short ParmCopyEnd

; Цикл копирования строки параметров ParmCopyLoop: cmp ebx, 0 jz short ParmCopyEnd



mov al, [ecx] mov [edx], al

dec ebx inc ecx inc edx jmp short ParmCopyLoop

ParmCopyEnd:

; Закрываем строку нулем mov al, 0 mov [edx], al

pop edx pop ecx pop ebx pop eax ret EndProc CopyParm

; --------------------------------------------------- ; V86_Int21_Handler ; Фильтр для функции 4B00h прерывания INT 21h, ; вызываемого из виртуальной машины MS-DOS ; (эта функция выполняет запуск программы MS-DOS) ; --------------------------------------------------- BeginProc V86_Int21_Handler

pushad

; Проверяем номер функции. Нас интересует только ; запуск программ mov ax, word ptr [ebp.Client_AX] cmp ax, 4B00h jnz HandlerExit

; Если окно приложения dos2win не зарегистрировано, ; ничего не делаем mov eax, CallbackBuf cmp eax, 0 jz HandlerExit

; Если запускается программа MS-DOS, ничего ; не делаем call short IsWindowsApp jz HandlerExit

; Для исключения реентерабельных вызовов выполняем ; ожидание семафора mov eax, hSem mov ecx,(Block_Enable_Ints OR Block_Svc_If_Ints_Locked) VMMcall Wait_Semaphore

; Получаем текущий диск и каталог call short GetCurDir

; Сохраняем номер текущего диска mov edx, CallbackBuf mov al, nCurDisk mov byte ptr [edx], al

; Определяем FLAT-адрес командной строки mov ax, (Client_DS shl 8) + Client_DX VMMcall Map_Flat mov flatpCmdLine, eax

; Определяем FLAT-адрес блока EPB mov ax, (Client_ES shl 8) + Client_BX VMMcall Map_Flat

; Загружаем в DX:BX адрес строки параметров mov bx, [eax + 2] mov dx, [eax + 4]

; Определяем FLAT-адрес строки параметров

push dword ptr [ebp.Client_ES] push [ebp.Client_EBX]

mov [ebp.Client_ES], dx mov [ebp.Client_BX], bx mov ax, (Client_ES shl 8) + Client_BX VMMcall Map_Flat mov flatpParmLine, eax

pop [ebp.Client_EBX] pop dword ptr [ebp.Client_ES]

; Копируем командную строку в буфер, который ; находится в DLL-библиотеке d2w.dll mov ecx, flatpCmdLine mov edx, CallbackBuf add edx, 65 call short StrCpy

; Выполняем копирование строки параметров call short CopyParm

; Определяем идентификатор текущей VM VMMCall Get_Cur_VM_Handle mov edx, ebx



; Определяем идентификатор системной VM VMMcall Get_Sys_VM_Handle

; Планируем вызов функции обратного вызова mov esi, offset32 CallbackProc VMMcall Schedule_VM_Event

popad

; Если запускается приложение Windows, блокируем ; выполнение прерывания INT 21h в виртуальной ; машине MS-DOS clc

ret

HandlerExit: popad

; Если запускается программа MS-DOS, наш драйвер ; не мешает этому процессу stc

ret EndProc V86_Int21_Handler

; --------------------------------------------------- ; CallbackProc ; Функция обратного вызова ; Вызывается в системной VM по запросу фильтра ; прерывания INT 21h, установленного нашим драйвером ; --------------------------------------------------- BeginProc CallbackProc

; Сохраняем состояние системной VM Push_Client_State

; Начинаем вложенное выполнение VMMcall Begin_Nest_Exec

; Записываем в стек системной VM параметр - ; версию нашего VxD-драйвера mov ax, Vers VMMcall Simulate_Push

; Вызов функции обратного вызова, определенной ; в DLL-библиотеке d2w.dll mov edx, [CallbackOff] mov cx, [CallbackSel] VMMcall Simulate_Far_Call

; Выполняем вызов и восстановление состояния ; системной VM VMMcall Resume_Exec VMMcall End_Nest_Exec Pop_Client_State

; Сбрасываем семафор, разрешая обработку ; следующего прерывания INT 21h mov eax, hSem VMMcall Signal_Semaphore

ret EndProc CallbackProc

; --------------------------------------------------- ; GetCurDir ; Определение текущего диска и текущего каталога ; в виртуальной машине MS-DOS, выполняющей запуск ; программы с помощью прерывания INT 21h ; --------------------------------------------------- BeginProc GetCurDir

pushad

; Сохраняем состояние VM MS-DOS Push_Client_State

VMMcall Begin_Nest_Exec

; Определяем номер текущего диска в VM MS-DOS ; Используем для этого функцию 1900h прерывания ; INT 21h. Номер диска возвращается в регистре AL mov ax, 1900h mov word ptr [ebp.Client_AX], ax mov eax, 21h VMMcall Exec_Int

; Сохраняем номер текущего диска в VM MS-DOS mov ax, word ptr [ebp.Client_AX] mov nCurDisk, al



; Для определения текущего пути VM MS-DOS ; заказываем буфер размером 64 байта в адресном ; пространстве VM MS-DOS, пользуясь функцией 4800h ; прерывания INT 21h mov ax, 4800h mov word ptr [ebp.Client_AX], ax

; Размер буфера задается в параграфах (по 16 байт) mov ax, 0004h mov word ptr [ebp.Client_BX], ax mov eax, 21h VMMcall Exec_Int

; Сохраняем сегментную компоненту адреса ; (смещение полученного буфера всегда равно 0) mov ax, word ptr [ebp.Client_AX] mov wPathSeg, ax

; В маловероятном случае, когда в VM MS-DOS ; совсем нет свободной памяти (даже 64 байт), ; мы не заполняем строку параметров: ; если памяти нет, все равно нельзя запустить ; ни программу MS-DOS, ни приложение Windows mov ax, wPathSeg cmp ax, 0 jz DoNotGetPath

; Определяем FLAT-адрес полученного буфера mov [ebp.Client_ES], ax mov [ebp.Client_BX], 0 mov ax, (Client_ES shl 8) + Client_BX VMMcall Map_Flat mov flatpPathBuf, eax

; Получаем строку текущего каталога VM MS-DOS, ; для чего вызываем функцию 4700h прерывания ; INT 21h. Перед вызовом этой функции нужно ; предоставить в DS:SI адрес буфера размером ; 64 байта, в который и будет записана строка mov ax, wPathSeg mov word ptr [ebp.Client_DS], ax mov ax, 0 mov word ptr [ebp.Client_SI], ax mov ax, 0 mov word ptr [ebp.Client_DX], ax mov ax, 4700h mov word ptr [ebp.Client_AX], ax mov eax, 21h VMMcall Exec_Int

; Копируем строку текущего каталога в буфер ; CallbackBuf со смещением 1 байт mov ecx, flatpPathBuf mov edx, CallbackBuf add edx, 1 call short StrCpy

; Освобождаем буфер, полученный в адресном ; пространстве VM MS-DOS mov ax, wPathSeg mov word ptr [ebp.Client_ES], ax mov ax, 4900h mov word ptr [ebp.Client_AX], ax mov eax, 21h VMMcall Exec_Int

VMMcall End_Nest_Exec

DoNotGetPath:

Pop_Client_State popad ret EndProc GetCurDir

; --------------------------------------------------- ; IsWindowsApp ; Проверка файла запускаемой программы ; ; Функция возвращает флаг Z = 0, если запускается ; приложение Windows в формате NE или PE, и Z != 0, ; если запускается программа MS-DOS ; ; Входные параметры: ; [ebp.Client_DS] - сегмент буфера, в котором находится ; путь к запускаемой программе ; [ebp.Client_DX] - смещение буфера, в котором находится ; путь к запускаемой программе ; --------------------------------------------------- ; Copyright (C) Сергей Ноженко, 1995 ; --------------------------------------------------- BeginProc IsWindowsApp



Push_Client_State VMMcall Begin_Nest_Exec

; Индикатор, который будет равен нулю на выходе ; из функции, если запускаемый файл не содержит ; приложение Windows sub esi, esi

; Открываем файл с программой mov word ptr [ebp.Client_AX], 3D00h mov eax, 21h VMMcall Exec_Int

; Проверка ошибки test word ptr [ebp.Client_Flags], 1 jnz EndNest

; Сохраняем идентификатор файла mov ax, word ptr [ebp.Client_AX] push eax

; Заказываем 4 параграфа памяти mov word ptr [ebp.Client_AX], 4800h mov word ptr [ebp.Client_BX], 4 mov eax, 21h VMMcall Exec_Int

; Проверяем, выделена ли нам память mov ax, word ptr [ebp.Client_AX] or ax, ax jz CloseFile

; Читаем старый заголовок исполнимого файла ; (заголовок программы MS-DOS) ; Готовим ES для вызова функции освобождения ; памяти mov word ptr [ebp.Client_DS], ax mov word ptr [ebp.Client_ES], ax mov word ptr [ebp.Client_AX], 3F00h pop eax mov word ptr [ebp.Client_BX], ax mov word ptr [ebp.Client_CX], 40h ; читаем 40h байт mov word ptr [ebp.Client_DX], 0 mov eax, 21h VMMcall Exec_Int

; Проверка ошибки test word ptr [ebp.Client_Flags], 1 jnz FreeMem

; Если размер файла меньше 40h байт, это ; не приложение Windows cmp word ptr [ebp.Client_AX], 40h jl short FreeMem

; Получаем FLAT-адрес mov ax, (Client_DS shl 8) + Client_DX VMMcall Map_Flat

; Проверяем сигнатуру EXE-файла. Если это не ; EXE-файл, то мы имеем дело не с приложением ; Windows cmp word ptr [eax], "ZM" jne short FreeMem

; Проверяем смещение таблицы relocation table. ; Если оно меньше чем 40h, это программа MS-DOS cmp word ptr [eax + 18h], 40h jl short FreeMem

mov edi, eax

; Ищем заголовок NewEXE mov word ptr [ebp.Client_AX], 4200h mov bx, word ptr [eax + 3Ch] mov word ptr [ebp.Client_DX], bx mov bx, word ptr [eax + 3Eh] mov word ptr [ebp.Client_CX], bx mov eax, 21h VMMcall Exec_Int

test word ptr [ebp.Client_Flags], 1 jnz short FreeMem

; Читаем первое слово заголовка NewEXE mov word ptr [ebp.Client_AX], 3F00h mov word ptr [ebp.Client_CX], 2 mov word ptr [ebp.Client_DX], 0 mov eax, 21h VMMcall Exec_Int

test word ptr [ebp.Client_Flags], 1 jnz short FreeMem cmp word ptr [ebp.Client_AX], 2 jne short FreeMem

; Проверяем сигнатуру исполнимого сегмента. ; Допустимы сигнатуры "NE" (segmented executable) ; и "PE" (portable executable) cmp word ptr [edi], "EN" je short WinApp

cmp word ptr [edi], "EP" jne short FreeMem

WinApp:

; Устанавливаем индикатор - запускается ; приложение Windows inc esi

; Освобождаем память FreeMem: mov word ptr [ebp.Client_AX], 4900h mov eax, 21h VMMcall Exec_Int

; Закрываем файл CloseFile: mov word ptr [ebp.Client_AX], 3E00h mov eax, 21h VMMcall Exec_Int

EndNest: VMMcall End_Nest_Exec Pop_Client_State

or esi, esi ret EndProc IsWindowsApp

VxD_CODE_ENDS END

Заголовок виртуального драйвера VXDSRV мы описали раньше, поэтому не будем на нем останавливаться.


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