Defining Win98/Me Stubs for Kernel-Mode Routines
The WDMSTUB sample in the companion content is a lower-filter driver that defines a number of kernel routines that Windows 98/Me omits. It relies on the same basic trick that Microsoft crafted to port several hundred kernel-mode support functions from Microsoft Windows NT to Windows 98/Me that is, extending the symbol tables that the run-time loader uses when it resolves import references. To extend the symbol tables, you first define three data tables that will persist in memory:
A name table that gives the names of the functions you re defining
An address table that gives the addresses of the functions
An ordinal table that correlates the name and address tables
Here are some of the table entries from WDMSTUB:
static char* names[] = { "PoRegisterSystemState", "ExSystemTimeToLocalTime", }; static WORD ordinals[] = { 0, , 6, }; static PFN addresses[] = { (PFN) PoRegisterSystemState, (PFN) ExSystemTimeToLocalTime, };
The purpose of the ordinal table is to provide the index within addresses of the entry for a given names entry. That is, the function named by names[i] is address[ordinals[i]].
If it weren t for a version compatibility problem I ll describe in a moment, you could call _PELDR_AddExportTable as follows:
HPEEXPORTTABLE hExportTable = 0; extern "C" BOOL OnDeviceInit(DWORD dwRefData) { _PELDR_AddExportTable(&hExportTable, "ntoskrnl.exe", arraysize(addresses), // <== don't do it this way! arraysize(names), 0, (PVOID*) names, ordinals, addresses, NULL); return TRUE; }
The call to _PELDR_AddExportTable extends the table of symbols that the loader uses when it tries to resolve import references from NTOSKRNL.EXE, which is of course the Windows XP kernel. NTKERN.VXD, the main support module for WDM drivers in Windows 98/Me, initializes this table with the addresses of the several hundred functions it supports. In effect, then, WDMSTUB is an extension of NTKERN.
Version Compatibility
The version compatibility problem to which I just alluded is this: Windows 98 supported a particular subset of the Windows 2000 functions used by WDM drivers. Windows 98 Second Edition supported a larger subset. The last version of Windows, Windows Me, supports a still larger subset. You wouldn t want your stub driver to duplicate one of the functions that the operating system supports. What WDMSTUB actually does during initialization, therefore, is dynamically construct the tables that it passes to _PELDR_AddExportTable:
HPEEXPORTTABLE hExportTable = 0; extern "C" BOOL OnDeviceInit(DWORD dwRefData) { char** stubnames = (char**) _HeapAllocate(sizeof(names), HEAPZEROINIT); PFN* stubaddresses = (PFN*) _HeapAllocate(sizeof(addresses), HEAPZEROINIT); WORD* ordinals = (WORD*) _HeapAllocate(arraysize(names) * sizeof(WORD), HEAPZEROINIT); int i, istub; for (i = 0, istub = 0; i < arraysize(names); ++i) { if (_PELDR_GetProcAddress((HPEMODULE) "ntoskrnl.exe", names[i], NULL) == 0) { stubnames[istub] = names[i]; ordinals[istub] = istub; stubaddresses[istub] = addresses[i]; ++istub; } } _PELDR_AddExportTable(&hExportTable, "ntoskrnl.exe", istub, istub, 0, (PVOID*) stubnames, ordinals, stubaddresses, NULL); return TRUE; }
The line appearing in boldface is the crucial step here it makes sure that we don t inadvertently replace a function that already exists in NTKERN or another system VxD.
There s one annoying glitch in the version compatibility solution I just outlined. Windows 98 Second Edition and Windows Me export just three of the four support functions for managing the IO_REMOVE_LOCK object. The missing function is IoRemoveLockAndWaitEx, if you care. My WDMSTUB.SYS driver compensates for this omission by stubbing either all or none of the remove lock functions based on whether or not this function is missing.
Stub Functions
The main purpose of WDMSTUB.SYS is to resolve symbols that your driver might reference but not actually call. For some functions, such as PoRegister SystemState, WDMSTUB.SYS simply contains a stub that will return an error indication if it is ever called:
PVOID PoRegisterSystemState(PVOID hstate, ULONG flags) { ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); return NULL; }
Sometimes, though, you don t need to write a stub that fails the function call you can actually implement the function, as in this example:
VOID ExLocalTimeToSystemTime(PLARGE_INTEGER localtime, PLARGE_INTEGER systime) { systime->QuadPart = localtime->QuadPart + GetZoneBias(); }
where GetZoneBias is a helper routine that determines the time zone bias that is, the number of units by which local time differs from Greenwich mean time by interrogating the ActiveTimeBias value in the TimeZoneInformation registry key.
Table A-2 lists the kernel-mode support functions that WDMSTUB.SYS exports.
Support Function | Remarks |
ExFreePoolWithTag | Stubbed |
ExLocalTimeToSystemTime | Implemented |
ExSystemTimeToLocalTime | Implemented |
HalTranslateBusAddress | Subset implemented |
IoAcquireRemoveLockEx | Implemented |
IoAllocateWorkItem | Implemented |
IoCreateNotificationEvent | Stub always fails |
IoCreateSynchronizationEvent | Stub always fails |
IoFreeWorkItem | Implemented |
IoInitializeRemoveLockEx | Implemented |
IoQueueWorkItem | Implemented |
IoRaiseInformationalHardError | Implemented |
IoReleaseRemoveLockEx | Implemented |
IoReleaseRemoveLockAndWaitEx | Implemented |
IoReuseIrp | Implemented |
IoReportTargetDeviceChangeAsynchronous | Stub always fails |
IoSetCompletionRoutineEx | Implemented |
KdDebuggerEnabled | Implemented |
KeEnterCriticalRegion | Implemented |
KeLeaveCriticalRegion | Implemented |
KeNumberProcessors | Always returns 1 |
KeSetTargetProcessorDpc | Implemented |
PoCancelDeviceNotify | Stub always fails |
PoRegisterDeviceNotify | Stub always fails |
PoRegisterSystemState | Stub always fails |
PoSetSystemState | Stub always fails |
PoUnregisterSystemState | Stub always fails |
PsGetVersion | Implemented |
RtlInt64ToUnicodeString | Stub always fails |
RtlUlongByteSwap | Implemented |
RtlUlonglongByteSwap | Implemented |
RtlUshortByteSwap | Implemented |
SeSinglePrivilegeCheck | Always returns TRUE |
ExIsProcessorFeaturePresent | Implemented |
MmGetSystemRoutineAddress | Implemented |
ZwLoadDriver | Implemented |
ZwQueryDefaultLocale | Implemented |
ZwQueryInformationFile | Implemented |
ZwSetInformationFile | Implemented |
ZwUnloadDriver | Implemented |
Using WDMSTUB
To use WDMSTUB, include WDMSTUB.SYS in your driver package and specify it as a lower filter for your function driver. Here s an example from the INF file for one of the sample drivers in the companion content:
[DriverInstall] AddReg=DriverAddReg CopyFiles=DriverCopyFiles,StubCopyFiles [StubCopyFiles] wdmstub.sys,,,2 [DriverAddReg] HKR,,DevLoader,,*ntkern HKR,,NTMPDriver,,"wdmstub.sys,workitem.sys"
The indicated syntax for the NTMPDriver value in the driver key causes the system to load WDMSTUB.SYS before attempting to load the function driver WORKITEM.SYS. By the time the system gets around to loading WORK ITEM.SYS, the DriverEntry routine in WDMSTUB has already executed and defined the functions listed in Table A-2.
Note that using WDMSTUB as a lower-filter driver means that the install package won t require a reboot.
Interaction Between WDMSTUB and WDMCHECK
WDMCHECK will tell you when certain symbols are defined only because you happen to have WDMSTUB loaded. See Figure A-4 for an example.
Figure A-4. WDMCHECK results involving WDMSTUB.
Special Licensing Note
WDMSTUB.SYS is an exception to the licensing requirements for the samples in the companion content. To avoid problems on end user machines caused by inconsistent versions of WDMSTUB, I ask that you not redistribute WDMSTUB.SYS except under license from me. I will grant a royalty-free license to distribute WDMSTUB to anyone who asks. Just send an e-mail to walt oney@oneysoft.com, explain that you want to redistribute WDMSTUB, and provide contact details so I can fax a license agreement to you.