The NT Insider

Managing Dynamic Function Loading
(By: The NT Insider, Vol 14, Issue 1, January - February 2007 | Published: 07-Feb-07| Modified: 07-Feb-07)

One of the issues we've been wrestling with for some time is the problem of supporting a driver that must work on multiple versions of Windows. Complicating this problem is that  functions often appear in service packs or hot fixes, which makes it nearly impossible to handle this issue as a compile time decision.

Compounding this issue further is that functions we use internally have been retroactively deprecated in the Windows Driver Kit. The technique we use for handling this problem relies upon dynamic function detection and resolution. The purpose of this article is to demonstrate the mechanism we use to support a driver on multiple versions of Windows, which we believe will be useful to the community. Our goals with this mechanism are as follows:

  • Allow dynamic resolution of functions that may not be available in all platform versions.
  • Use functions that can no longer be directly called, such as those that are deprecated.
  • Provide down-level equivalents for functions that in some cases may be intrinsically provided by the OS, such as the correct hot fix.
  • Ensure that the resolution is low overhead since we want to avoid performing an evaluation each time we fix the function.
  • Ensure that our initialization is done in an MP-safe fashion.

We'll use the function MmIsNonPagedSystemAddressValid to demonstrate this technique. This function is now  marked deprecated in the Windows Driver Kit. While we don't use it as a general means of probing addresses, we do use it for validation of information being passed to us. For file systems and file system filter drivers, we exist in an environment in which we are sometimes passed the wrong information. Gracefully recovering from being passed wrong information minimizes support calls, while failing to recover means the OS crashes in our driver. Guess who is automatically blamed?

So, when we validate a data structure, such as the FsContext pointer in a FILE_OBJECT, we generally rely upon a magic number that we place in the data structure. However, we also verify that the address passed in for that data structure is valid. There have been times when the address isn't valid.

The first step in creating our dynamic function is to create a function prototype for our dynamic function (See Figure 1).

typedef BOOLEAN (*OSR_IS_NP_SYSADDR_VALID)(PVOID Address);

Figure 1 - Function Prototype


 

 

As you can see in Figure 2, we combine this with an external reference in our shared header file.

extern OSR_IS_NP_SYSADDR_VALID pOsrIsNonPagedSystemAddressValid;

Figure 2 - Add External Reference


 

 

Our last step, (Figure 3) which is also in our header file, is to create an inline function to invoke that function pointer.

__inline BOOLEAN OsrIsNonPagedSystemAddressValid(PVOID Address) {
    return (*pOsrIsNonPagedSystemAddressValid)(Address);
}

Figure 3 - Create Inline Function to Invoke Pointer

 

 

 


Note that in our code we use the function OsrIsNonPagedSystemAddressValid.

At this point we still need to implement our initialization function and create the pre-initialized global variable. This is necessary because the first time we call this function we actually invoke the initialization function. For example (Figure 4), the initialization function is not complicated.

static BOOLEAN IsNonPagedSystemAddressValid(PVOID Address)
{
    OSR_IS_NP_SYSADDR_VALID mmRoutine;
    UNICODE_STRING mmRoutineName;

    RtlInitUnicodeString(&mmRoutineName, L"MmIsNonPagedSystemAddressValid");

    mmRoutine = (OSR_IS_NP_SYSADDR_VALID) MmGetSystemRoutineAddress(&mmRoutineName);

    ASSERT(mmRoutine);

    InterlockedExchangePointer((volatile PVOID *)&pOsrIsNonPagedSystemAddressValid, mmRoutine);

    return (*pOsrIsNonPagedSystemAddressValid)(Address);
}

Figure 4 - Initialize


 

 

 

 

 

 

 

 

In this case, we assert that we always get a function pointer back because we believe this function will always be available. However, if we were running on a system where the function might not be defined, we could initialize to an alternative implementation. We take this approach  when calling functions such as IoCreateFileSpecifyDevice ObjectHint on Windows 2000 platforms. Sometimes it is available, and sometimes it is not. We'd rather use the OS implementation when it is available, but we have a backup solution when it isn't. However, this backup solution is rather complex and outside the scope of this article.

Note that we are performing the interlocked exchange on the pointer so that even if multiple threads call this initializer at the same time, they both put the same function address into the global pointer. An exception to this can occur if there is something seriously wrong with MmGetSystemRoutine Address!

The last thing we need is our declaration of that function pointer in shown in Figure 5.

OSR_IS_NP_SYSADDR_VALID pDmkIsNonPagedSystemAddressValid = IsNonPagedSystemAddressValid

Figure 5 - Declare the Function Pointer

Thus, the function pointer is initialized as part of the driver load and can be called at any point. Once the initialization function has been invoked, it doesn't ever need to be invoked again. The inline function should become nothing more than a function address load operation.

This approach meets our original goals of simplicity, support for dynamic determination of functions we support, being safe in the face of multiple callers, and having a very low run-time overhead. This technique has served us well, we hope it does the same for you.

This article was printed from OSR Online http://www.osronline.com

Copyright 2017 OSR Open Systems Resources, Inc.