A Better Way to Provide VxD to VxD Services

0 Likes

Problem:

A Better Way to Provide VxD to VxD Services

Resolution:

When a VxD wants to export services to other VxDs, the conventional approach is to create a VxD service table. The VxD service table contains the addresses of the services to be exported, and there is a pointer to the table in the VxD's Device Data Block (DDB). Other VxDs call the exported services using macro VxDCall. This macro issues instruction INT 20h, which is trapped by the system. The system's INT 20h handler examines the DWORD that immediately follows the INT instruction to determine which VxD service is being called. The exporter VxD is identified by a device ID constant, and the service within that VxD by an ordinal value. The INT 20h locates the DDB of the exporter VxD, finds its service table, extracts the function address, and then patches over the INT 20h in the caller's instruction stream with a CALL instruction that invokes the target VxD service.

While this mechanism is certainly tried and true, it has some disadvantages. First, the declaration of the VxD service table requires use of a few ugly macros. Then the address of the table and the service count must be inserted into the DDB. These details create opportunities to introduce bugs. Ensuring matching calling sequences and calling conventions (i.e. __stdcall vs. __cdecl) complicates it further. Another headache is obtaining a unique device ID to designate the exporter VxD. VxD ID constants are allocated only by Microsoft, so if you arbitrarily assign one, then your VxD could conflict with some other VxD.

There is an alternative approach to exporting VxD services which addresses some of these problems. You can use the system service Directed_Sys_Control to send a control message to the exporter VxD. Typically, the control message that you send is the general purpose W32_DEVICEIOCONTROL. In other words, a VxD can export a set of services, each of which corresponds to a unique identifier passed with the W32_DEVICEIOCONTROL message.

In the VxD that exports the services, using this method is as simple as adding cases to a switch statement in the handler for W32_DEVICEIOCONTROL. However, the first step is to create a shared include file that defines the set of identifiers for the services. This is done with macro CTL_CODE:

// my_ioctls.h

#define IOCTL_MYDRIVER_SERVICE_1

CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS)

#define IOCTL_MYDRIVER_SERVICE_2

CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)

#define IOCTL_MYDRIVER_SERVICE_3

CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS)

The code above defines three constants that identify the exported services. So the handler for W32_DEVICEIOCONTROL in the exporter VxD looks like this (using VtoolsD):

DWORD OnW32Deviceiocontrol(PIOCTLPARAMS p)

{

switch (p->dioc_IOCtlCode)

{

case DIOC_OPEN: // CreateFile

case DIOC_CLOSEHANDLE: // handle closed

return DEVIOCTL_NOERROR;

case IOCTL_MYDRIVER_SERVICE_1:

// implement service 1

break;

case IOCTL_MYDRIVER_SERVICE_2:

// implement service 2

break;

case IOCTL_MYDRIVER_SERVICE_3:

// implement service 3

break;

default:

return 1; // bad function

}

}

In order to use Directed_Sys_Control from the calling VxD to send a message to the exporter VxD, you must first locate the DDB of the exporter VxD. This has to be done just once, not each time the calling VxD sends a message.

To locate the DDB of the target device, use system service Get_DDB. Once you have a valid DDB pointer, use this function to call the exported service:

BOOL SendDeviceIoControl(

PDDB pDdb, // pointer to DDB of exporter VxD

ULONG IoctlCode, // dioc_IOCtlCode

PVOID InputBuffer, // dioc_InBuf

ULONG SizeOfInputBuffer, // dioc_cbInBuf

PVOID OutputBuffer, // dioc_OutBuf

ULONG SizeOfOutputBuffer,// dioc_cbOutBuf

PULONG pBytesReturned // dioc_bytesret

)

{

ALLREGS regs; // register struct passed to Directed_Sys_Ctrl

IOCTLPARAMS io; // Ioctl param struct passed to target device

if ( pDdb == NULL )

return FALSE;

// set up the ioctl params

io.dioc_IOCtlCode = IoctlCode;

io.dioc_InBuf = InputBuffer;

io.dioc_cbInBuf = SizeOfInputBuffer;

io.dioc_OutBuf = OutputBuffer;

io.dioc_cbOutBuf = SizeOfOutputBuffer;

io.dioc_bytesret = pBytesReturned;

// clear the regs structure

memset(®s, 0, sizeof(regs));

// put a pointer to the ioctl param struct in the register struct

regs.RESI = (DWORD)&io;

// send the message to the target device

Directed_Sys_Control(pDdb, W32_DEVICEIOCONTROL, ®s);

return (regs.REAX == DEVIOCTL_NOERROR);

}

The parameters to the SendDeviceIoControl become the indicated members of the IOCTLPARAMS structure that the exporter VxD receives in its handler for control message W32_DEVICEIOCONTROL. You can customize a set of services using this general mechanism.

Compared to the conventional VxD service table method, the Directed_Sys_Control method has some clear advantages. It avoids the use of complicated and error prone macros. There is a single fixed calling convention which can be safely adapted to an arbitrary set of services. You don't need to obtain a unique device ID constant from Microsoft. And if you will be porting the VxD to an NT or WDM driver in the future, the code will be much easier to port.

One note of warning. Directed_Sys_Control does not validate the DDB pointer passed to it, so the exporter VxD and its caller should establish a protocol that provides the caller of some notification when the exporter unloads.

Old KB# 11800
Comment List
Anonymous
Related Discussions
Recommended