iczelion Vxd tut3
Virtual Device Driver Skeleton
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 FormatVxD 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.
Code and data in an LE file are stored in segments with different runtime attributes. Below are the available segment classes.
- LCODE Page-locked code and data. This segment is locked into memory. In other words, this segment will not be paged to disk, thus you should use this segment class judiciously so as not to waste precious system memory. Code and data that must be present in memory at all time should be in this segment. Especially the hardware interrupt handlers.
- PCODE Pageable code. This segment is pageable by VMM. Code in this segment needs not be present in memory all the time. VMM will page this segment to disk if it needs physical memory.
- PDATA Pageable data.
- ICODE Initialization-only code. The code in this segment is used during initialization of the VxD only. After initialization, this segment will be discarded by VMM to reclaim physical memory.
- DBOCODE debug-only code and data. The code and data in this segment is used when you run the VxD under a debugger. It contains the handler for Debug_Query control message, for example.
- SCODE Static code and data. This segment will always be present in memory even when the VxD is unloaded. This segment is especially useful for dynamic VxD when it must be loaded/unloaded many times during a Window session and wants to remember the last configuration/state.
- RCODE Real-mode initialization code and data. This segment contains the 16-bit code and data for real mode initialization.
- 16ICODE USE16 protected-mode initialization data. This segment is a 16-bit one which contains the code that the VxD will copy from protected mode to V86 mode. For example, if you want to paste some V86 code into a VM, the code you intend to paste must be in this segment. If you put the code in other segment, the assembler will generate wrong code, i.e. it will generate 32-bit code instead of the intended 16-bit one.
- MCODE Locked message strings. This segment contains message strings which are compiled with the help of VMM message macros. This helps you create international versions of your driver.
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 FIRSTVXDThe first statement is for declaration of the VxD name. 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.
_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'
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, its 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 overkills 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.
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:
<put your code here>
VxD SkeletonNow 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 read the definition of those macros in various include files such as vmm.inc.
Here is the VxD skeleton source code:
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
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.
.386pTell the assembler that we want to use 80386 instruction set including the privileged CPU instructions. You can also use .486p or .586p.
include vmm.incYou 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_ORDERAs 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 exist) 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.
- Name The name of the VxD. Maximum length is 8 characters. It MUST be uppercase. The name should be unique among the VxDs in the system. The macro also uses the name to create the name of the DDB by appending _DDB to the device name. So if you use FIRSTVXD as the name of the VxD, Declare_Virtual_Device macro will declare the name of the DDB as FIRSTVXD_DDB. Remember that, you will also have to export the DDB in .DEF file too. You have to match the label in the source file with the one in the .DEF file.
- MajorVer and MinorVer The major and minor versions of your VxD
- CtrlProc The name of the device control procedure for your VxD. A device control procedure is a function that receives and processes control messages for a VxD. You can think of a device control procedure as the window procedure equivalence. Since we will use Begin_Control_Dispatch macro to create our device control procedure, we should use the standard name which is in the form of VxDName_Control. Begin_Control_Dispatch macro appends _Control to the name passed to it ( and we usually pass the name of the VxD to it) so we should specify the name of our VxD appended by _Control in CtrlProc parameter.
- DeviceID The 16-bit unique identifier of your VxD. You need the ID if and only if your VxD must handle one of the situations below
- Your VxD exports VxD services for other VxDs to use. Since int 20h interface uses the device ID to locate/identify the VxD, it's imperative that your VxD must have a unique identifier.
- Your VxD broadcasts your existence to real-mode applications during initialization with int 2Fh function 1607h.
- Some real-mode software (TSR) will use int 2Fh, function 1605h to load your VxD.
- InitOrder Initialization order, or in short, load order. VMM loads VxDs in the order specified. Each VxD will have a load order number. For example,
DEBUG_INIT_ORDER EQU 000000000H
DEBUGCMD_INIT_ORDER EQU 000000000H
PERF_INIT_ORDER EQU 000900000H
APM_INIT_ORDER EQU 001000000H
V86Proc and PMProc Your VxD can export API for use by V86 and protected-mode programs. V86Proc and PMProc specify the addresses of those API. Remember that, VxDs exist primarily for supervising VMs and a VM other than the system VM runs a DOS or protected-mode application. It stands to reason for VxDs to provide API support for DOS and protected-mode programs. If you don't export those API, you can omit these fields.
RefData Reference data used by Input Output Supervisor (IOS). The only occasion you would use this field is when you code a layer block driver for use with IOS. You can omit this field if your VxD is not a layer driver. Next we have Begin_Control_Dispatch macro.
You can see that VMM, DEBUG and DEBUGCMD are the first VxDs that are loaded, followed by PERF and APM. The VxD with lower value of initialization order is loaded first. If your VxD requires the services of other VxD during initialization, you should specify an initialization order value that is larger than that of the VxD you want to call so that by the time your VxD is loaded, that VxD is already there in memory, ready for you. If your VxD doesn't care about initialization order, specify UNDEFINED_INIT_ORDER in this parameter.
Begin_control_dispatch FIRSTVXDThis 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_Dispatch message, functionFor example, if your VxD processes only Device_Init message, your device control procedure would look like this:
Begin_Control_Dispatch FIRSTVXDOnDeviceInit is the name of the function that will handle Device_Init message. You can name your function anything you like.
Control_Dispatch Device_Init, OnDeviceInit
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 VxDThe 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_32 firstvxd.asm
-coff Specify the COFF object format
-c Assemble only. Do not call the linker to link the object file, since we will call link.exe later with more parameters.
-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_COFFlink -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.
link -vxd -def:$(NAME).def $(NAME).obj
ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm