VxD Programming: Primer
VxD Initialization and TerminationThere are two types of VxD: static and dynamic. Each type has different load method. They also receive different initialization and termination control messages.
Static VxD:VMM loads a static VxD when:
- A real-mode resident programs issue int 2Fh, 1605h to load it
- The VxD is specified in the registry under the key:
- The VxD is specified in system.ini under [386enh] section:
When the VMM loads your static VxD, your VxD will receive three system control messages in the following order:
- Sys_Critical_Init VMM sends this control message after switching into protected mode but before enabling interrupts. Most VxDs don't need to handle this message except when:
- Your VxD hooks some interrupts that will be called later by other VxDs or protected mode programs. Since the interrupts are disabled when you process this control message, you can be sure that the interrupts you're hooking won't be called during the time you're hooking them.
- Your VxD provides some VxD services that will be called during initialization by other VxDs. For example, some VxD that loads after your VxD may need to call one of your VxD's services during Device_Init control message processing. Since Sys_Critical_Init message is sent before Device_Init message, you must initialize your services during Sys_Critical_Init message.
- Device_Init VMM sends this control message after the interrupts are enabled. Most VxDs perform initialization in response to this message. Because the interrupts are enabled, time-consuming operations can be done without the fear of losing hardware interrupts. You should do your initialization here (if needed).
- Init_Complete After all VxDs processed Device_Init message but before the VMM releases all initialization segments (ICODE and RCODE segment classes), the VMM sends this control message. Few VxDs need to process this message.
When it's time to terminate the static VxD, the VMM sends the following control messages:
- System_Exit2 When your VxD receives this message, Windows 95 is about to shut down. All other VMs except the system VM are already destroyed. However the CPU is still in protected mode and it's still safe to execute real-mode code in the system VM. Kernel32.dll was already unloaded by this time.
- Sys_Critical_Exit2 Your VxD receives this message when all VxDs have processed System_Exit2 and the interrupts are disabled.
You may wonder why those two exit messages have "2" appended to them.Remember that when the VMM loads the static VxDs, it loads the VxDs with the lower initialization order first so that the VxDs can rely upon services of the VxDs that load before them. For example, if VxD2 relies on the services of VxD1, it must specify its initialization order to be larger than that of VxD1. The load order would be:
..... VxD1 ===> VxD2 ===> VxD3 .....Now during unloading, it stands to reason that the VxDs that initialize later should uninitialize first so that they may still call VxD services of the VxDs that were loaded before them. In the above example, the order should be:
.... VxD3 ===> VxD2 ===> VxD1.....In the above example, if VxD2 called some VxD1's services during initialization, it may need to rely on VxD1's services again during unloading. System_Exit2 and Sys_Critical_Exit2 are sent in reverse initialization order. It means that, when VxD2 receives those messages, VxD1 hasn't done uninitialization yet and it can still call VxD1's services. System_Exit and Sys_Critical_Exit messages are not sent in reverse initialization order. It means that when you process those two messages, you can't be sure that you can still call VxD's services of the VxDs that were loaded before you. Those messages should not be used for newer VxDs.
There are two more exit messages:
- Device_Reboot_Notify2 Notifies the VxD that the VMM is going to restart the system. The interupts are still enabled during this time.
- Crit_Reboot_Notify2 Notifies the VxD that the VMM is going to restart the system. The interrupts are disabled.
Dynamic VxD:Dynamic VxDs can be dynamically loaded and unloaded during Windows 9x sessions. This feature is not available under Windows 3.x. The primary goal of dynamic VxDs is to support dynamic hardware reconfiguration such as Plug and Play devices. However, you can load/unload them from your win32 applications as well, making them ideal as your applications' ring-0 extension.
The example in the previous tutorial is a static VxD. You can convert the example into a dynamic VxD by adding the keyword DYNAMIC to the VXD statement in .DEF file.
VXD FIRSTVXD DYNAMICThat's all you have to do to convert a static VxD into a dynamic one.
Dynamic VxD can be loaded by:
- Putting it in /SYSTEM/IOSUBSYS folder in your Windows folder. The VxDs in this folder will be loaded by Input Output Supervisor (IOS). The VxDs in this folder should support layer device drivers so it may not be a good idea to load your dynamic VxD this way.
- Using VxD Loader service. VxDLDR is a static VxD that can load dynamic VxDs. You can call its services from other VxDs or from 16-bit code.
- Using CreateFile API from a Win32 application. You specify the dynamic VxD you want to load to CreateFile in the format below:
For example, if you want to load a dynamic VxD named FirstVxD which is in the current directory, you should do it as follows:
VxDName db "//./FirstVxD.VXD",0
hDevice dd ?
invoke CreateFile, addr VxDName,0,0,0,0, FILE_FLAG_DELETE_ON_CLOSE,0
If you use CreateFile to load a dynamic VxD, the VxD must handle w32_DeviceIoControl message. VWIN32 sends this control message to your dynamic VxD when it is first loaded by CreateFile method. Your VxD must return 0 in eax in response to this message. w32_DeviceIoControl messages are also sent when the application calls DeviceIoControl API to communicate with the VxD. We will examine DeviceIoControl interface in the later tutorial.
A dynamic VxD receives one message during initialization:
Other System Control MessagesDuring the time a VxD stays in memory, it will receive many control messages other than those related to initialization and termination. Some of them are related to virtual machine management and some to miscellaneous events. For example, VM-related control messages are:
Creating procedures inside VxDYou declare a procedure in VxD inside a segment. You should define a segment first and then put your procedure inside it. For example, if you want your function to be in a pageable segment, you should define a pageable segment first, like this:
VxD_PAGEABLE_CODE_SEGYou can put many procedures inside a segment. You as the VxD writer must decide in which segment you should put your procedures. If your procedures must be in memory at all time such as hardware interrupt handlers, put them in a locked segment. Otherwise you should put them in the pageable segment.
[Your procedure here]
You define your procedure with BeginProc and EndProc macros.
BeginProc namename is the name of your procedure. BeginProc macro can take several more parameters, you should consult Win95 DDK documentation for detail. But most of the time, you can get by with only the name of the procedure.
You should use BeginProc-EndProc macros instead of the normal proc-endp directives because BeginProc-EndProc macros provide more functionality than proc-endp.
VxD Coding Convention
Register UsageYour VxD can use any general register, FS and GS. But you should beware about modifying segment registers. Especially, you should definitely not alter CS and SS unless you are quite positive you know what you're doing. You can use DS and ES so long as you remember to restore their values when you return. Two flags are especially important: direction and interrupt flags. You should not disable interrupts for an extended period of time and if you modify the direction flag, don't forget to restore its previous state before you return.
Parameter-passing ConventionThere are two calling conventions for VxD services: register-based and stack-based. With register-based services, you pass parameters to the services via various registers and you can check the carry flag after calling the service to see if the operation is successful. You can not assume that the values in the general registers will be preserved after calling the services. With stack-based services, you push the parameters on the stack and you got the return value in eax. Stack-based services preserve ebx, esi, edi and ebp. Most of the register-based services originate from Windows 3.x days. Most of the time, you can differentiate between those two kinds of services by looking at the names. If the name of the service begins with an underscore like _HeapAllocate, it's a stack-based (C) service (except for a few services exported by VWIN32.VXD). If the service name doesn't begin with an underscore, it's a register-based service.
Calling VxD ServicesYou call VMM and VxD services by using VMMCall and VxDCall macros. Both macros have exactly the same syntax. You use VMMCall when you want to call VxD services exported by VMM and you use VxDCall when you call services exported by VxDs other than the VMM.
VMMCall service ; for calling register-based serviceVMMCall and VxDCall decompose to int 20h followed by a dword that I described in the previous tutorial but they are much more convenient to use. In the case of stack-based services, you must enclose the argument list with a pair of angle bracket.
VMMCall _service, <argument list> ; for calling stack-based service
VMMCall _HeapAllocate, <<size mybuffer>, HeapLockedIfDP>_HeapAllocate is a stack-based service. It accepts two parameters. We must enclose them inside an angle bracket. However, the first parameter is an expression that the macro may interpret incorrectly, so we put it inside another angle bracket.
Flat AddressesIn older tools, the assembler and linker generate incorrect addresses when you use offset operator. So VxD programmers use offset flat: instead of offset. vmm.inc contains a macro to make it easier. OFFSET32 expands to offset flat:. So if you want to use offset operator, you should use OFFSET32 instead.
Note: I experimented with offset operator when I wrote this tutorial. It generated correct addresses so I think the bug has been removed in MASM 6.14. But just to play safe, you should use OFFSET32 macro instead of the plain offset.