Thursday, September 27, 2012

A Helpful Debugging Technique: Debug Stream Contexts

In this post I'd like to discuss a debugging technique that I found pretty useful. I'm a big fan of keeping lots of information around in debug builds and ASSERTing all over the place. I try to be very careful about what information I keep so that I don't change the actual flow of execution for the code. For example, I tend to try to not add references when I copy some pointers because I don't want to alter when freeing the memory happens for the objects I'm tracking (but this approach requires extra care when asserting since I don't want to use pointers to free memory).
One particularly useful thing I like is to keep track of all the files I've processed in my filter and remember various decisions I made about them and keep back-pointers to things that would otherwise be impossible to find (for example when I base my decision on some parameters in the FLT_CALLBACK_DATA I might take a snapshot of those parameters; or I might keep a stack trace for certain operations and so on). The problem that arises with this approach is, of course, where to store this information. In some cases it's feasible to use some of the structures I'm already using (StreamContext or StreamHandleContext) and just add extra members in debug builds. However, there are other times where this becomes a significant pain and it would alter the filter quite a bit (for example, if the logic of the filter is to check for presence of a context to decide on what to do with a file then adding a context in which I only populate the debugging information for files I don't intend to process would change that logic). Also, certain contexts might store data that isn't fixed in size (like a file name or path) which I typically set to be the last thing in the context so adding more debugging information (which might itself be dynamic in size) would make things very complicated.
The ideal approach would be to set more than one context on an object, but FltMgr's functions don’t really allow that. Well, here is where the some FsRtl functions really come in handy. There is support built in the file system library (FsRtl code) for contexts (which I've discussed in more detail in my post on Contexts in legacy filters and FSRTL_ADVANCED_FCB_HEADER) and the library supports setting multiple contexts for the same stream, with different keys. Moreover, the contexts have two keys and there are pretty cool searching semantics that allow searching on only one key. The functions that deal with this are FsRtlInitPerStreamContext, FsRtlInsertPerStreamContext and FsRtlLookupPerStreamContext. They’re pretty easy to use and there is even a page on MSDN on Creating and Using Per-Stream Context Structures.
Here is some code that shows how to use these functions:
typedef struct _MY_DEBUG_CONTEXT {

    //
    // this is the header for the perStream context structure. 
    //

    FSRTL_PER_STREAM_CONTEXT PerStreamContext;

    // 
    // this is the information we want to track.
    //
    
    BOOLEAN Flag;

} MY_DEBUG_CONTEXT, *PMY_DEBUG_CONTEXT;



VOID
FreeDebugContext(
    __in PVOID ContextToFree
    )
{

    //
    // here we would release and uninitialize any other data.. 
    //

    //
    // and then we free the actual context.
    //
    
    ExFreePoolWithTag( ContextToFree, 'CgbD' );
}


NTSTATUS
SetDebugContext(
    __in PFILE_OBJECT FileObject,
    __in PVOID Key1,
    __in_opt PVOID Key2,
    __in BOOLEAN Flag
    )
{
    PMY_DEBUG_CONTEXT context = NULL;
    NTSTATUS status = STATUS_SUCCESS;

    __try {

        //
        // allocate and initialize the context.
        //

        context = ExAllocatePoolWithTag( PagedPool, 
                                         sizeof(MY_DEBUG_CONTEXT),
                                         'CgbD' );

        if (context == NULL) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            __leave;    
        }

        FsRtlInitPerStreamContext( &context->PerStreamContext,
                                   Key1,
                                   Key2,
                                   FreeDebugContext );

        //
        // initialize all the members that we want to track.
        //

        context->Flag = Flag;

        //
        // try to set the context.
        //

        status = FsRtlInsertPerStreamContext( FsRtlGetPerStreamContextPointer(FileObject),
                                              &context->PerStreamContext );

        NT_ASSERT(status == STATUS_SUCCESS);

    } __finally {

        if (!NT_SUCCESS(status)) {

            if (context != NULL) {
            
                ExFreePoolWithTag( context, 'CgbD' );
            }
        }
    }

    return status;
}


BOOLEAN
GetDebugContextFlag(
    __in PFILE_OBJECT FileObject,
    __in PVOID Key1,
    __in_opt PVOID Key2
    )
{
    PMY_DEBUG_CONTEXT context = NULL;
    
    context = (PMY_DEBUG_CONTEXT)FsRtlLookupPerStreamContext( FsRtlGetPerStreamContextPointer(FileObject),
                                                              Key1,
                                                              Key2 );

    if ((context != NULL) &&
        (context->Flag)) {

        return TRUE;
    }

    return FALSE;
}
Finally, one more thing to show is how easy it is to find this structure in the debugger:
0: kd> dt @@(tempFileObject) nt!_FILE_OBJECT FsContext  <- get the FsContext
   +0x00c FsContext : 0xa24d05f8 Void
0: kd> dt 0xa24d05f8 nt!_FSRTL_ADVANCED_FCB_HEADER FilterContexts. <- look at the FilterContexts  in the FsContext
   +0x02c FilterContexts  :  [ 0xa25a1108 - 0x936e93e4 ]
      +0x000 Flink           : 0xa25a1108 _LIST_ENTRY [ 0x936e93e4 - 0xa24d0624 ]
      +0x004 Blink           : 0x936e93e4 _LIST_ENTRY [ 0xa24d0624 - 0xa25a1108 ]
0: kd> dl 0xa25a1108 <- look at all the entries in the list
a25a1108  936e93e4 a24d0624 9441eb40 92e3cb40
936e93e4  a24d0624 a25a1108 93437438 a24d05f8
a24d0624  a25a1108 936e93e4 00000000 a24d05f4
0: kd> !pool a25a1108 2  <- check the pool type for the first one
Pool page a25a1108 region is Paged pool
*a25a1100 size:   20 previous size:  100  (Allocated) *DbgC  <- this is our structure
  Owning component : Unknown (update pooltag.txt)
0: kd> !pool 936e93e4  2   <- for fun, let's see what the other context is 
Pool page 936e93e4 region is Nonpaged pool
*936e93d8 size:   68 previous size:    8  (Allocated) *FMsl   
  Pooltag FMsl : STREAM_LIST_CTRL structure, Binary : fltmgr.sys  <- obviously FltMgr's context
0: kd> dt a25a1108 _MY_DEBUG_CONTEXT  <- display the debugging context
PassThrough!_MY_DEBUG_CONTEXT
   +0x000 PerStreamContext : _FSRTL_PER_STREAM_CONTEXT
   +0x014 Flag             : 0x1 ''

No comments:

Post a Comment