[Previous] [Next]

Defining Stubs for Kernel-Mode Routines

The stub technique used in WDMSTUB.VXD relies on the same basic trick that Microsoft crafted to port several hundred kernel-mode support functions from Microsoft Windows NT to Windows 98—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:

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 2000 kernel. NTKERN.VXD, the main support module for WDM drivers in Windows 98, initializes this table with the addresses of the several hundred functions it supports. WDMSTUB.VXD is a static VxD with an initialization order later than NTKERN and earlier than the Windows 98 Configuration Manager. Consequently, WDMSTUB's export definitions will be in place by the time the system loads any WDM drivers. In effect, then, WDMSTUB is an extension to NTKERN.

Version Compatibility

The version compatibility problem to which I alluded earlier is this: Windows 98 supports a particular subset of the Windows 2000 functions used by WDM drivers. Windows 98, Second Edition, supports a larger subset. The next version of Windows, code-named Millennium, will support a still larger subset (maybe even a superset, given that it will be released after Windows 2000). You would not want your stub VxD to duplicate one of the functions that the OS 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 bold face 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, exports just three of the four support functions for managing the IO_REMOVE_LOCK object. The missing function is IoRemoveLockAndWaitEx, if you care. My WDMSTUB.VXD 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.VXD is to resolve symbols that your driver might reference but not actually call. For some functions, such as PoRegisterSystemState , WDMSTUB.VXD 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-1 lists the kernel-mode support functions that WDMSTUB.VXD exports.

Table A-1. Functions exported by WDMSTUB.VXD.

Support Function Remarks
ExLocalTimeToSystemTime Implemented
ExSystemTimeToLocalTime Implemented
IoAcquireRemoveLockEx Implemented
IoAllocateWorkItem Implemented
IoFreeWorkItem Implemented
IoInitializeRemoveLockEx Implemented
IoQueueWorkItem Implemented
IoReleaseRemoveLockEx Implemented
IoReleaseRemoveLockAndWaitEx Implemented
IoCreateNotificationEvent Stub—always fails
IoCreateSynchronizationEvent Stub—always fails
IoReportTargetDeviceChangeAsynchronous Stub—always fails
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