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.
-
LCODE
Page-locked code and data. This segment is locked into memory. In other
words, this segment will not be paged to disk. Code and data that must
be present in memory at all time should be in this segment. Especially
the hardware interrupt handler.
-
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.
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.
-
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 equivalent. 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 must 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.
If your
VxD doesn't need a unique device ID, you can specify UNDEFINED_DEVICE_ID
in
this field. If you do need a unique ID, you have to ask for one from Microsoft.
-
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,
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
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.
-
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 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.
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]