Virtual Device Driver Skeleton

Now that you know something about VMM and VxD, we can learn how to code a VxD. You need Windows 95/98 Device Driver Development Kit. It's essential that you have it. Windows 95 DDK is available only to MSDN subscribers. However, Windows 98 DDK is available free of charge from Microsoft. You can also use Windows 98 DDK to develop VxDs even if it is oriented toward WDM. You can download Windows 98 DDK from  http://www.microsoft.com/hwdev/ddk/install98ddk.htm?
You can download the whole package, about 30 MBs or you can selectively download only the parts you're interested in. If you choose not to download the whole package, don't forget to download the Windows 95 DDK documentation included in other.exe
Windows 98 DDK contains MASM version 6.11d. You should upgrade it to the latest version. For the information about where to download the latest version upgrade, check my main page.
Windows 9x DDK contains several essential include files which are not included in MASM32 package.
You can download the example in this tutorial here.

LE File Format

VxD uses linear executable (LE) file format. This is the file format designed for OS/2 version 2.0. It can contain both 16- and 32-bit code which is one of the requirement of VxDs. Remember that VxDs date back from the days of Windows 3.x. During that time, Windows boots from DOS so VxDs may need to do some initialization in real mode before Windows switches the machine into protected mode. Real-mode 16-bit code must be in the same executable file as the 32-bit protected mode code. So LE file format is the logical choice. Windows NT drivers blissfully don't have to deal with real mode initialization so they don't have to use LE file format. Instead they use PE file format.
LE file format
Code and data in an LE file are stored in segments with different runtime attributes. Below are the available segment classes. It doesn't mean your VxD must have ALL those segments. You can choose the segments you want to use in your VxD. For example, if your VxD doesn't have real-mode initialization, it doesn't have to have RCODE segment.
Most of the time, you would use LCODE, PCODE and PDATA. It's your judgement as a VxD writer to choose the appropriate segments for your code/data. In general, you should use PCODE and PDATA as much as possible because VMM can page the segments in and out of memory if necessary. You should use LCODE to store hardware interrupt handlers and services that will be called by hardware interrupt handlers.
You don't use those segment classes directly. You must declare segments based on those classes. Those segment declarations are stored in the module definition file (.def). The full-scale module definition file for a VxD is below:
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
The first statement is the declaration of the name of the VxD. The name of a VxD MUST be all uppercase. I experimented with the lowercase name, and the VxD refused to perform anything except loading itself into memory.
Next are the segment declarations. The declaration consists of three parts: the name of the segment, the segment class and the desired runtime property of the segment. You can see that there are many segments based on the same class, for example, _LPTEXT, _LTEXT, _LDATA are all based on LCODE segment class with exactly the same properties. These segments are declared to make coding easier to understand. For example, LCODE can contain both code and data. It'll be easier on the programmer if he can store the data in _LDATA segment and the code in _LTEXT segment. Eventually, both segments will be combined into only one segment in the final executable file.
A VxD exports one and only one symbol, the device descriptor block (DDB). The DDB is actually a structure which contains everything VMM needs to know about a VxD. You MUST export the DDB in the module definition file.
Most of the time, you can use the above .DEF file in new VxD projects. You would have to change only the name of the VxD in the first and the last lines of the .DEF file. The segment declarations are overkill for an asm VxD project. They are for use by a C VxD project but using them in an asm project is ok. You'll get a lot of warning messages but it will assemble. You can get rid of the annoying warning messages by deleting the segment declarations you don't use in your project.
vmm.inc contains many macros for declaring segments in your source file.
 
_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

Each macro has its ending counterpart. For example, if you want to declare an _LTEXT segment in your source file, you would do it like this:

VxD_LOCKED_CODE_SEG

<put your code here>

VxD_LOCKED_CODE_ENDS

VxD Skeleton

Now that you know about segments in LE file, we can go on to the source file. One thing you can observe about VxD programming is the heavy use of macros. You will find macros everywhere in VxD programming. It takes some getting used to. Those macros are provided to hide some gory details from  programmers and in some ways, make the source code more portable. If you are curious, you can see the definition of those macros in various include files such as vmm.inc.
Here is the VxD skeleton source code:
 
.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


At first glance, the source code doesn't look like an asm source code. That's because of the use of macros. Let's analyze this source code and you'll soon understand it.

.386p
Tell the assembler that we want to use 80386 instruction set including the privileged CPU instructions. You can also use .486p or .586p.
include vmm.inc
You must include vmm.inc in every VxD source code because it contains the definitions of the macros you use in the source file. You can include other include files as needed.
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
As stated previously, the VMM learns everything it needs to know about a VxD from the VxD's device descriptor block (DDB). A device descriptor block is a structure that contains vital information about the VxD such as the VxD's name, its device ID, the entrypoints of its VxD services (if exists) and so on. You can look up this structure in vmm.inc. It's declared as VxD_Desc_Block. You'll export this structure in .DEF file. There are 22 members in this structure, but you'll usually need to fill only some of them. So vmm.inc contains a macro that will initialize and fill the structure members for you. That macro is DECLARE_VIRTUAL_DEVICE. It has the following format:

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

One thing you can observe is that, the labels in VxD source code is case-insensitive. You can use upper- or lowercase characters or combination of them. Let's examine each parameter of Declare_virtual_device.

Next we have Begin_Control_Dispatch macro.
Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD
This macro and its counterpart define the device control procedure which is the function that VMM calls when there are control messages for your VxD. You must specify the first half of the name of the device control procedure, in our example we use FIRSTVXD. The macro will append _Control to the name you supplied.This name must match the one you specify in CtrlProc parameter of Declare_virtual_device macro. The device control procedure is always in a locked segment (VxD_LOCKED_CODE_SEG). The above device control procedure does nothing. You have to specify what control messages your VxD is interested in handling and the functions that will handle them. You use Control_Dispatch macro for this purpose.
Control_Dispatchmessage, function
For example, if your VxD processes only Device_Init message, your device control procedure would look like this:
Begin_Control_Dispatch  FIRSTVXD
  Control_Dispatch  Device_Init, OnDeviceInit
End_Control_DispatchFIRSTVXD
OnDeviceInit is the name of the function that will handle Device_Init message. You can name your function anything you like.
You end the VxD source code with end directive.
To recapitulate, at a minimum, a VxD must have a device control block and a device control procedure. You declare a device control block with Declare_Virtual_Device macro and a device control procedure with Begin_Control_Dispatch macro. You must export the device control block by specifying its name under EXPORTS directive in .DEF file.

Assembling the VxD

The assembling process is the same as the one used in assembling normal win32 applications. You invoke ml.exe on the asm source code and then link the object file with link.exe. The differences are in the command line switches used by ml.exe and link.exe

 ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32firstvxd.asm

-coff  Specify the COFF object format
-c   Assemble only. Do not call the linker to link the object file.
-Cx  Preserve the case of public, extern labels.
-D<text> defines a text macro. For example, -DBLD_COFF defines a text macro BLD_COFF which will be used in conditional assembly. If you're interested, you can search for BLD_COFF in the include files and see for yourself what effect it has on the assembly process. So in the command line switches above, three text macros are defined: BLD_COFF, IS_32, and MASM6. If you're familiar with C, this process is identical to:

#define BLD_COFF
#define IS_32
#define MASM6
link -vxd -def:firstvxd.def  firstvxd.obj

-vxd specifies that we want to build a VxD from the object file
-def:<.DEF file> specifies the name of the module definition file of the VxD

I find it more convenient to use makefile but you can create a batch file to automate the assembling process if you don't like makefile approach. Here's my makefile.

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


[Iczelion's Win32 Assembly Homepage]