| ||
This chapter concentrates on the basics of driver development for the Windows operating system. The first part of this chapter covers virtual device drivers (VxDs). I do not agree with the common opinion that obsolete materials are not worthy of being considered in contemporary books on computing. On the contrary, in practically all other branches of science, historical information is considered an important part of the knowledge accumulated in a specific area. The student must know how the discipline evolved and must understand the logic of its evolution. Therefore, I didn't exclude the chapter about 16-bit programming, and I preserved material about VxDs. The second part of this chapter concentrates on kernel-mode drivers. This material is important for programmers and has preserved its urgency.
Furthermore, it is important to mention that this chapter is oriented toward MASM32.
The "x" character in the VxD abbreviation means "any type of device." Although a new type of driver has been introduced in addition to VxD, the use of VxDs remains important because many users all over the world continue to work with Windows 98 and Windows Millennium Edition. In this chapter, in contrast to my normal practice, I extensively use macro definitions contained in the INC files supplied with Microsoft's Windows Driver Development Kit (DDK). Using this approach allows me to provide all the material in one chapter.
To develop VxDs, you'll need files such as VMM.INC, SHELL.INC, and VCOND.INC, which are supplied with the Windows DDK.
At Windows startup, the WIN.COM program loads the VMM32.VXD driver (VMM stands for Virtual Machine Manager). This driver, in turn , initializes other VxDs. Then, VMM switches the processor to the protected mode and initializes the system virtual machine. In addition, VMM provides services to other VxDs. When the user starts an MS-DOS application, a separate virtual machine is allocated to that application so that the DOS application has the illusion of running on an individual computer. When the user starts normal 32-bit applications, they operate within the framework of the system virtual machine. Applications that run on different virtual machines are unaware of the existence of other virtual machines. The most important goal of virtual drivers is to ensure shared access to the hardware without conflicts for all concurrently running virtual machines. Another task that virtual drivers must carry out is organizing communications between the system virtual machine and other virtual machines running on the same computer.
Note that in Windows, there are also so-called standard drivers that have the DRV filename extension, are characterized by the Dynamic Link Library (DLL) structure, and export API functions for working with some peripheral devices (e.g., a video adapter). These drivers get access to peripheral devices through VxDs. Because VxDs operate in ring 0, they can access any memory region and use input and output ports to directly access peripheral devices.
All virtual drivers are divided into two classesstatic and dynamic. Static drivers are loaded at system startup and remain in the memory until system shutdown. Static drivers existed even in Windows 3. x Dynamic drivers can be loaded and unloaded as needed by the system. Mainly, they are used for serving Plug-and-Play devices and are loaded by the configuration manager. A dynamic virtual driver can also be loaded from a normal application using standard functions for working with files.
There are three mechanisms that can be used by virtual drivers for intercommunication:
Control messages The VMM sends these messages to virtual drivers. Drivers also can exchange information using such messages. This mechanism is similar to the way applications use Windows messages to communicate with each other and with the operating system.
Callback functions The virtual driver can allow another driver to use the callback function.
Virtual drivers and VMM These can export specific functions for calling them from other virtual drivers. To call the function, it is necessary to know the number of the virtual driver exporting this function and the number of this function.
The format of virtual drivers is the Linear Executable (LE) format. This format supports the presence of both 16-bit and 32-bit code. This is urgent for static VxDs, which are initialized in a real ( unprivileged ) mode. In Windows NT, drivers are loaded in the protected mode. Therefore, this format is not used in this operating system.
Code and data in the LE format file are placed in segments. The following list briefly describes possible classes of segments:
LCODEThe code or data contained within that code couldn't be paged to the disk.
PCODEThe code can be temporarily paged to the disk.
PDATESimilar to the previous class, in this case the class is related to data.
ICODEThe segment stores the initialization code. After initialization, the segment is removed from the memory.
DBOCODEThis is used for starting the driver under the control of the debugger.
SCODEStatic code or data. These always remain in the memory, even if the driver is unloaded.
RCODEThis contains 16-bit code for real-mode initialization.
16ICODEThis is 16-bit code for protected-mode initialization.
MCODEThis contains message strings.
The preceding classes of segments are not specified directly in the program text. Segments and classes are declared in a DEF file. The VMM.INC file contains a vast number of macro definitions, and you can't do without them. However, this allows me to describe all of this material within one chapter.
Consider the contents of the DEF file (Listing 27.1). This listing contains segments for every possible case. Naturally, you do not need to use all segments defined here. Thus, this file can be used for creating practically any virtual driver. Segments belonging to the same class will be joined into the same segment after compiling and building. Only the first string specifying the driver name must be changed. Note that the driver name must be specified in uppercase letters . In addition, in the first line, it is possible to specify the driver type. By default, it is assumed that this is a static driver. If you specify a string such as VXD VXD1 DYNAMIC , the compiler would create a dynamic virtual driver.
VXD VXD1 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 VXD1_DDB @1
In the end of this file, the only exported variablethe Device Description Block (DDB)is specified. This block is defined in the VMM.INC file. It contains 22 fields and provides information about the virtual driver.
The VMM.INC file defines macro names for all segments listed previously. For example; for the _LTEXT segment, the VxD_LOCKED_CODE_SEG name is specified, and the _RCODE segment has the VxD_REAL_INIT_SEG name. Listings provided in this chapter actively use these macro names.
Now, consider the translation of virtual drivers. To produce a virtual driver, issue the following commands:
ml /coff /c /Cx /DMASM6 /DBLD_COFF /DIS_32 vxd1.asm link /vxd /def:vxd.def vxd1.obj
Constants such as MASM6, BLD_COF, IS_32 are used by conditional translation operators specified in the VMM.INC and VCOND.INC files. Note that if you are using the DEF file in the form specified in Listing 27.1, then warning messages informing you that specific sections are missing might appear in the course of compiling. These warnings can be ignored.
To carry out some actions, you'll often need to use macro definitions from the VMM.INC file. Therefore, it is necessary to become acquainted with the most frequently used ones.
The most important is the Declare_Virtual_Device macro. It fills the DDB structure, thus simplifying the programmer's task. The general format of this macro is as follows (the structure of the macro can be found in the VMM.INC file):
Declare_Virtual_Device Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData
Consider the macro parameters:
Name The virtual driver name. This name must match the name specified in the DEF file.
MajorVer, MinorVer Major and minor version numbers of the driver.
CtrlProc The name of the driver's control procedure. This procedure receives and processes the messages arriving at the driver. The procedure name consists of two parts : the driver name and Control suffix. For example, if the driver name is VXD1 , then the procedure will have the following name: VXD1_Control .
DeviceID The 16-bit unique identifier of the virtual driver. It must be specified if the virtual driver must provide services to other drivers. In addition, this identifier might be needed if your driver is intended to operate in real mode.
InitOrder The driver's loading order. This is an ordinal number. Drivers with lower ordinal numbers are the first to be loaded. This parameter is meaningful only for static drivers.
V86Proc, PMProc Addresses of functions that the driver will export for MS-DOS and standard Windows applications. If the driver won't export functions, these parameters should be omitted.
Ref_Data The reference to the data used by the input/output supervisor. As a rule, this parameter is omitted.
To define the control procedure, use the Begin_control_dispatch and End_control_dispatch macros. This can be done as follows:
Begin_control_dispatch VXD1 Control_Dispatch message, function End_control_dispatch VXD1
The Control_Dispatch macro defines, which messages must be processed by which functions. For example:
Begin_control_dispatch VXD1 Control_Dispatch INIT_CQMPLETE, INIT End_control_dispatch VXD1
Thus, you have all the required information to build the simplest driver. To be more precise, this will be the skeleton of the future driver. For the moment, you don't even need to clearly understand how it will work.
.586P include vmm.inc include vcond.inc DECLARE_VIRTUAL_DEVICE. VXD1, 1, 0, VXD1_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER Begin_control_dispatch VXD1 Control_Dispatch INIT_COMPLETE, INIT End_control_dispatch VXD1 VxD_LOCKED_CODE_SEG BeginProc INIT EndProc INIT VxD_LOCKED_CODE_ENDS end
Now, translate the driver provided in Listing 27.2 according to the algorithm described earlier. Do not forget to add the /MAP command-line option. As a result, the VXD1.MAP file will appear in your working directory in addition to the VXD1.VXD file. The contents of this file are shown in Listing 27.3.
VXD1 Timestamp is 3bb5ad7a (Sat Sep 29 17:16:10 2001) Preferred load address is 00400000 Start Length Name Class 0001:00000000 00000050H _LDATA CODE 0001:00000050 00000007H _LTEXT CODE Address Publics by Value Rva+Base Lib:Object 0001:00000000 VXD1_DDB 00401000 vxd1.obj 0001:00000050 VXD1_Control 00401050 f vxd1.obj 0001:00000057 INIT 00401057 f vxd1.obj entry point at 0000:00000000 Static symbols
When viewing the MAP file, note that there are two segments defined there: _LDATA and _LTEXT . Both segments relate to the same class.
To conclude this listing, I'd like to mention that instead of the standard name proc/name endp combination, the BeginProc and EndProc macros are used here. Definitions of these macros are provided in the VMM.INC file.
Virtual drivers can provide services to other drivers. In other words, virtual drivers export their functions. The call to an exported function is a 6-byte value, shown as follows:
int 2Oh DD 00110002H
Here, 11H is the virtual driver identifier, and 02H is the service number (the index in the table of services). However, I won't write the call in this form. Instead, I'll use the VMMCall and VxDCall macros. The first macro is intended for calling VMM services, and the second macro is used for calling services of other virtual drivers.
Now you have all the information required for writing a simple but usable static driver. The text of this driver is provided in Listing 27.3. After the listing, I provide comments about this driver and cover the basic principles of building static virtual drivers.
.586P include vmm.inc include shell.inc include vcond.inc ; Fill the DDB structure DECLARE_VIRTUAL_DEVICE VXD2, 1, 0, VXD2_Control, \ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER ; Declare the received messages ; and the procedures to process them Begin_control_dispatch VXD2 Control_Dispatch Create_VM, OnVMCreate Control_Dispatch VM_Terminate2, OnVMClose End_control_dispatch VXD2 ; Segment for storing messages VxD_PAGEABLE_DATA_SEG MsgTitle db "Message from a VXD driver", 0 VMCreated db "Creating a virtual machine", 0 VMDestroyed db "Destroying a virtual machine", 0 VMFocus db "Changing a virtual machine focus" VxD_PAGEABLE_DATA_ENDS ; Segment containing code VxD_PAGEABLE_CODE_SEG ; Procedure that reacts to virtual machine creation BeginProc OnVMCreate ; Your code goes here MOV ECX, OFFSET VMCreated CALL MES RET EndProc OnVMCreate ; Procedure that reacts to the closing of a virtual machine BeginProc OnVMClose ; Your code goes here MOV ECX, OFFSET VMDestroyed CALL MES RET EndProc OnVMClose ; Procedure that outputs a message MES PROC ; Get the system virtual machine handle VMMCall Get_sys_vm_handle ; The handle is returned in EBX, ; and the message flag is returned in EAX MOV EAX, MB_OK ; Message header address MOV EDI, OFFSET MsgTitle ; Address of the CallBack function, NULL in this case XOR ESI, ESI ; Reference to the CallBack function data XOR EDX, EDX ; VxD service function -- message window VxDCall SHELL_Message MES ENDP VxD_PAGEABLE_CODE_ENDS end
As already mentioned, a static driver is loaded at system startup and remains in memory until system shutdown. The most convenient way of loading such a driver is inserting a string such as device=driver_name into the [386enh] section of the SYSTEM.INI file. You can also use the system registry by including the following value entry there:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\key\ StaticVxD=pathname
However, the first approach is more convenient because, in case of error, your VxD can be easily disabled by editing the SYSTEM.INI file under MS-DOS.
When installing VxDs, VMM sends the following messages to the drivers:
SysCriticalInit This message is sent when switching to the protected mode but before enabling interrupts.
Device_Init This message is sent after enabling interrupts. This message is used most frequently by virtual drivers for startup initialization.
Init_Complete This is the last message sent to virtual drivers at system startup.
Having received the message and carried out all required tasks , the driver must reset the carry flag and return control to the operating system.
Before unloading, static virtual drivers also must receive several messages.
System_Exit2 This message is sent before the system shutdown. The microprocessor at that time is still in the protected mode.
Sys_Critical_Exit2 This is the next message sent to virtual drivers before system shutdown.
Device_Reboot_Notify2 This message informs virtual drivers that the system is going to shut down. However, interrupts are still available.
Crit_Reboot_Notify2 This is similar to the previous message, but interrupts are no longer available.
Now, consider the program provided in Listing 27.3. This driver outputs the message about activation of the virtual machine (e.g., the creation of a console or startup of an MS-DOS application) and its deactivation . The following two service functions were used in this driver: get the handle to the system virtual machine and output the message. Consider these functions in more detail:
Get_sys_vm_handle Get the handle to the system virtual machine. The handle is returned in the EBX register.
SHELL_Message Output the message. Parameters are stored in the following registers:
EBX The handle to the virtual machine
EAX The message flag (e.g., MB_OK )
ECX The 32-bit address of the message string
EDI The 32-bit address of the header string
ESI The address of the function that reacts to user input (if such a function is missing, this parameter is equal to zero)
EDX The address of the data to be sent to the function
Finally, when exiting, the driver must clear the carry flag. In this case, this operation depends on the correct execution of the SHELL_Message function.
In this section, I cover dynamic virtual drivers. There are three methods of loading such drivers:
Place a driver into the \SYSTEM\IOSUBSYS directory. Drivers residing in this directory are loaded by the input/output manager.
Use the VxDLDR service. This service function can be called only from virtual drivers.
Use the CreateFile function.
The latter method of loading dynamic drivers is the one I will explain in detail now. The sequence of steps that you need to carry out to complete this task is as follows:
Open the driver using the CreateFile function. In the case of success, the function returns the identifier that will then be used when calling the functions exported by this driver.
Use the dynamic driver function by calling the DeviceIoControl API function.
Close the driver by calling the CloseHandle function; the driver will then be automatically unloaded from the memory.
Now, consider the program that loads a dynamic driver. This program is shown in Listing 27.4. It loads the MSG.VXD driver and calls its service number 3.
; The FILES1.ASM file .586P ; Flat memory model .MODEL FLAT, stdcall ; Constants STD_INPUT_HANDLE equ -10 FILE_FLAG_DELETE_ON_CLOSE equ 4000000h ; Prototypes of external procedures EXTERN GetStdHandle@4:NEAR EXTERN ExitProcess@4:NEAR EXTERN GetCommandLineA@0:NEAR EXTERN CreateFileA@28:NEAR EXTERN CloseHandle@4:NEAR EXTERN MessageBoxA@16:NEAR EXTERN ReadConsoleA@20:NEAR EXTERN DeviceIoControl@32:NEAR ; ;--------------------------------------------- ; INCLUDELIB directives for the linker includelib c: \masm32\lib\user32.lib includelib c:\masm32\lib\kernel32.lib ;--------------------------------------------- ; ; Data segment _DATA SEGMENT HANDL DWORD ? HFILE DWORD ? BUF DB "\.\msg.vxd", 0 CAP DB "Message box", 0 MES DB "Error loading the driver", 0 BUFER DB 20 DUP(0) LENS DWORD ? ; Number of characters for output MES1 DB "Service call OK!", 0 _DATA ENDS ; Code segment _TEXT SEGMENT START: ; Get the output handle PUSH STD_INPUT_HANDLE CALL GetStdHandle@4 MOV HANDL, EAX ; Open the file PUSH 0 PUSH FILE_FLAG_DELETE_ON_CLOSE PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH OFFSET BUF CALL CreateFileA@28 CMP EAX, -1 JE _ERR MOV HFILE, EAX ; Call the VxD service PUSH 0 PUSH 0 PUSH 0 PUSH 0 PUSH 18 PUSH OFFSET MES1 PUSH 3 ; Service number PUSH HFILE CALL DeviceIoControl@32 ; Wait for the <ENTER> key PUSH 0 PUSH OFFSET LENS PUSH 200 PUSH OFFSET BUFER PUSH HANDL CALL ReadConsoleA@20 ; Close and unload the driver PUSH HFILE CALL CloseHandle@4 _EXIT: ; Program termination PUSH 0 CALL ExitProcess@4 _ERR: PUSH 0 ; MB_OK PUSH OFFSET CAP PUSH OFFSET MES PUSH 0 ; Window handle CALL MessageBoxA@16 JMP _EXIT _TEXT ENDS END START
The program presented in Listing 27.4 requires some comments. The most important role is delegated to the DeviceIoControl function. Here are the parameters of this function:
First parameterThe descriptor of the driver obtained using the CreateFile function
Second parameterThe number of the required operation
Third parameterThe address of the data for the driver
Fourth parameterThe data length
Fifth parameterThe buffer, in which the driver will store its data
Sixth parameterThe buffer length
Seventh parameterThe address of the variable that will store the number of bytes loaded into the buffer by the driver
Eighth parameterThe address of the OVERLAPPED structure
As you can see, when calling the function, you pass the pointer to the MES1 string.
I hope that you won't experience any difficulties understanding how the driver loading program operates. Now, it is time to consider the driver. This driver carries out a simple function. When its service is called, this driver displays a message on the screen. At the same time, the message text is passed by the calling program. When calling the DeviceIoControl function with the driver handle, the w32_deviceIoControl message is delivered to the driver. The EBX register contains the virtual machine handle, and ESI points to the structure, the contents of which will be covered in detail later in this section. It is necessary to bear in mind that when the driver is unloaded, the same message arrives to it, which also needs to be processed. Now, consider the structure referenced by the ESI register.
DIOCParams STRUC Internal1 DD ? VMHandle DD ? Internal2 DD ? dwIoControlCode DD ? lpvInBuffer DD ? cbInBuffer DD ? lpvOutBuffer DD ? cbOutBuffer DD ? lpcbBytesReturned DD ? lpoOverlapped DD ? hDevice DD ? tagProcess DD ? DIOCParams ENDS
The fields of this structure are as follows.
Internall The pointer to the Client_Reg_Struc structure that defines the registers of the calling application (see Listing 27.6 and the comments about it)
VMHandle The virtual machine handle
Internal2 The pointer to the DDB
dwIoControlCode The number of the required operation
lpvInBuffer The pointer to the buffer containing information about the calling program
cbInBuffer The number of bytes sent in the buffer
lpvOutBuffer The pointer to the buffer, in which the driver can store the information for the calling program
cbOutBuffer The number of bytes in the buffer
lpcbBytesReturned The number of bytes to be returned
lpoOverlapped The pointer to the Overlapped structure
hDevice The driver handle returned by the CreateFile function
tagProcess The process tag
.586P include vmm.inc include vcond.inc include vwin32.inc include shell.inc DECLARE_VIRTUAL_DEVICE MSG, 1, 0, MSG_Control, \ UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER Begin_control_dispatch MSG ; The w32_DeviceIoControl message ; will be processed by the PROC1 procedure Control_Dispatch w32_DeviceIoControl, PROC1 End_control_dispatch MSG ; Data segment VxD_PAGEABLE_DATA_SEG CAP1 DB "Message box", 0 MES1 DB 50 DUP(0) VxD_PAGEABLE_DATA_ENDS ; Code segment VxD_PAGEABLE_CODE_SEG BeginProc PROC1 CMP DWORD PTR [ESI]+12, DIOC_Open JNE L1 XOR EAX, EAX JMP _EXIT L1: CMP DWORD PTR [ESI]+12, 3 JNZ _EXIT ; String length MOV EDI, DWORD PTR [ESI]+16 VMMCall _lstrlen,<EDI> ; Copy to buffer INC EAX ; Length VMMCall _lstrcpyn, <OFFSET MES1, EDI, EAX> ; Call the SHELL_Message function MOV ECX, OFFSET MES1 ; DWORD PTR [ESI]+14 MOV EDI, OFFSET CAP1 MOV EAX, MB_OK+MB_ICONEXCLAMATION VMMCall Get_Sys_VM_Handle ; Address of the CallBack function, NULL in this case XOR ESI, ESI ; Reference to the data for the CallBack function XOR EDX, EDX VxDCall SHELL_Message XOR EAX, EAX _EXIT: RET EndProc PROC1 VxD_PAGEABLE_CODE_ENDS end
The program in Listing 27.5 requires some comments.
As I pointed out earlier, when the driver loads, it receives the w32_DeviceIoControl message, and the ESI register points to the message structure. The dwIoControlCode field will contain the DIOC_Open number, which is equal to zero. The dwIoControlCode field is located by the offset ESI+12 . After making sure that this location contains zero the driver returns control after resetting EAX to zero (this is required).
When calling the driver from the program, you use number 3. After making sure that the dwIoControlCode field contains 3, the driver must carry out the actions expected by the program.
The task of this driver is to display a message containing the string received from the calling program. The string address and its length are specified. To demonstrate some functions of a VxD service, you define the string length again and copy it into the buffer prepared in the driver body.
Finally, it is necessary to output the message and return the control after resetting the EAX register to zero.
It is time to provide a listing and some comments about the Client_Reg_Struc structure.
Client_Reg_Struc STRUC Client_EDI DD ? Client_ESI DD ? Client_EBP DD ? Client_res0 DD ? Client_EBX DD ? Client_EDX DD ? Client_ECX DD ? Client_EAX DD ? Client_Error DD ? Client_EIP DD ? Client_CS DW ? Client_res1 DW ? Client_EFlags DD ? Client_ESP DD ? Client_SS DW ? Client_res2 DW ? Client_ES DW ? Client_res 3 DW ? Client_DS DW ? Client_res4 DW ? Client_FS DW ? Client_res5 DW ? Client_GS DW ? Client_res6 DW ? Client_Alt_EIP DD ? Client_Alt_CS DW ? Client_res7 DW ? Client_Alt_EFlags DD ? Client_Alt_ESP DD ? Client_Alt_SS DW ? Client_res8 DW ? Client_Alt_ES DW ? Client_res9 DW ? Client_Alt_DS DW ? Client_res10 DW ? Client_Alt_FS DW ? Client_res11 DW ? Client_Alt_GS DW ? Client_res12 DW ? Client_Reg_Struc ENDS
The structure in Listing 27.6 contains three types of fields:
Client_resX Reserved fields
Client_XXX Registers of the program started within a virtual machine
Client_Alt_XXX Registers of a 32-bit program started in the system virtual machine
At this point, the description of virtual drivers comes to a logical end.
| ||