天天看点

Wdf框架之WdfObject状态机(2) 一文补充

本文是前一篇文章<Wdf框架之WdfObject状态机(2)>的补充。MS设计如此复杂的状态机的目的,是在状态发生改变时调用适当的回调函数,如:EvtCleanupCallback/EvtDestroyCallback,可是前一篇文章舍本逐末的关注了FxObject状态的变化,而忽略了这些回调函数的调用时机,遗漏部分就在此文中补充。

源码也有所改动,先贴于此:

typedef struct _DrvCtx
{
  int ref;
  PDRIVER_OBJECT drvObj;
  WDFDRIVER drvHnd;
}DrvCtx, *PDrvCtx;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DrvCtx, GetDrvCtx);

void Unload(WDFDRIVER drvObj)
{
  DrvCtx* drvCtx = GetDrvCtx(drvObj);
  drvCtx->ref = 0;
}

VOID DrvDestroyCallback(WDFOBJECT drvObj)
{
  DrvCtx* drvCtx = GetDrvCtx(drvObj);
  drvCtx->ref = 1;
}

VOID DrvCleanupCallback(WDFOBJECT drvObj)
{
  DrvCtx* drvCtx = GetDrvCtx(drvObj);
  drvCtx->ref = 2;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT drvObj, PUNICODE_STRING regPath)
{
  WDF_DRIVER_CONFIG config;
  WDFDRIVER drvHnd;
  WDF_OBJECT_ATTRIBUTES attr;
  NTSTATUS status;
  DrvCtx* drvCtx;
  UNREFERENCED_PARAMETER(regPath);

  WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, DrvCtx);
  attr.EvtCleanupCallback = DrvCleanupCallback;
  attr.EvtDestroyCallback = DrvDestroyCallback;
  
  WDF_DRIVER_CONFIG_INIT(&config, NULL);
  config.DriverInitFlags = WdfDriverInitNonPnpDriver;

  config.EvtDriverUnload = Unload;
  status = WdfDriverCreate(drvObj,regPath,&attr,&config,&drvHnd);
  if (!NT_SUCCESS(status))
    return status;

  drvCtx = GetDrvCtx(drvHnd);
  drvCtx->drvObj = drvObj;
  drvCtx->drvHnd = drvHnd;
  drvCtx->ref = 0;
  return status;
}      

根据前文内容,大家可能已经了解到:对于非pnp驱动,停用驱动时,驱动对象会从FxObjectStateCreated状态逐步变为FxObjectStateDestroyed,并最终被释放。本篇再次从Unload开始,结合源码和windbg调试输出,跟踪状态驱动对象的变化。先把断点架好:

kd> bl
 0 e a7271104     0001 (0001) KMDFdrv!Unload+0x4
 1 e a72710c4     0001 (0001) KMDFdrv!DrvDestroyCallback+0x4
 2 e a72710a4     0001 (0001) KMDFdrv!DrvCleanupCallback+0x4      

当停用驱动时,调试器旋即会中断在#0断点并得到如下的调用堆栈:

kd> kb
ChildEBP RetAddr  Args to Child              
833a3ba0 8633c2de 57634e08 95cb3a40 95cb3a40 KMDFdrv!Unload+0x4 [g:\kmdfdrv\kmdfdrv\kmdf.c @ 15]
833a3bb8 a5f01324 967e11f0 833a3be8 81f04dfd Wdf01000!FxDriver::Unload+0x6f [fxdriver.cpp @ 167]
833a3bc4 81f04dfd 967e11f0 85846928 89e02040 KMDFdrv!FxStubDriverUnload+0x1a [stub.cpp @ 159]
833a3be8 81ce0c5f 95cb3a40 00000000 89e02040 nt!IopLoadUnloadDriver+0x87      

IopLoadUnloadDriver依次调用FxStubDriverUnload,Wdf01000!FxDriver::Unload及KMDFdrv!Unload。初次看到这样的输出我们不经会想:在停用驱动时IopLoadUnloadDriver应该调用KMDFdrv!Unload,然而堆栈回溯中多余的两个函数调用是哪来的?带着这样的疑惑,逆向KMDFdrv驱动的入口函数FxDriverEntry可以发现很多趣事:

刚进入FxDriverEntry(FxDriverEntryWorker)时,驱动对象_DRIVER_OBJECT!DriverUnload字段为空(当然这也正常,以前WMD驱动都是在DriverEntry中设置Unload函数的):

kd> kb ;在DriverEntry下断点,调试器中断后查看调用堆栈
ChildEBP RetAddr  Args to Child              
8a1a3ab4 a84e12ba a55139b0 a4f96000 a55139b0 KMDFdrv!DriverEntry [g:\kmdfdrv\kmdfdrv\kmdf.c @ 32]
8a1a3ad0 81bf523d a55139b0 a4f96000 8e0dfba4 KMDFdrv!FxDriverEntryWorker+0x8c [d:\th\minkernel\wdf\framework\kmdf\src\dynamic\stub\stub.cpp @ 325]
8a1a3bc0 81b4cdc2 00000000 8a1a3be0 87cb8530 nt!IopLoadDriver+0x455
...
kd> ?? drvObj
struct _DRIVER_OBJECT * 0xa55139b0
   +0x034 DriverUnload     : (null) ;调用WdfDriverCreate前DRIVER_OBJECT!DriverUnload字段为空      

当调用WdfDriverCreate后,_DRIVER_OBJECT!DriverUnload字段指向wdf框架代码Wdf01000!FxDriver::Unload:

kd> ?? drvObj
struct _DRIVER_OBJECT * 0xa55139b0
   +0x034 DriverUnload     : 0x8865c26f     void  Wdf01000!FxDriver::Unload+0
kd> ln . ;ln用于检测代码地址所靠近的符号名,windbg分析当前执行到kmdf.c文件第49行,即WdfDriverCreate的下一行
g:\kmdfdrv\kmdfdrv\kmdf.c(49)
(a84e1000)   KMDFdrv!DriverEntry+0x5b   |  (a84e10a0)   KMDFdrv!DrvCleanupCallback      

当退出KmdfDrv!DriverEntry后,FxDriverEntryWorker将执行下列代码片(这段代码由编译器设置,因此只能反汇编):

Wdf框架之WdfObject状态机(2) 一文补充

它对应的C源码大概如下:

if(WdfDriverGlobals->!=0)
{
    if(Driver_Object->DriverUnload!=NULL)
    {
    WdfDriverStubDisplacedDriverUnload = Driver_Object->DriverUnload;
    Driver_Object->DriverUnload = FxStubDriverUnload;
    }
}
else
…      

WdfDriverStubDisplacedDriverUnload是个函数指针,暂存Wdf01000!FxDriver::Unload函数。因为驱动对象的卸载函数被编译器设置为FxStubDriverUnload,所以当停用驱动时,OS会调用KMDFdrv! FxStubDriverUnload。这个函数出现在前面堆栈调用中,因此,我们就跟进去看看这个编译器提供的函数的实现:

Wdf框架之WdfObject状态机(2) 一文补充

其实现大概如下。

if(WdfDriverStubDisplacedDriverUnload!=NULL)
{    
  if(FxStubDriverUnload!=FxStubDriverUnload)
  {        
    (*WdfDriverStubDisplacedDriverUnload)(Driver_Object);    
  }
}
FxStubDriverUnloadCommon();      

函数指针WdfDriverStubDisplacedDriverUnload的值为Wdf01000!FxDriver::Unload。这个函数好,它在WDF框架中有现成的源码,如下图,不用煞费苦心的反汇编了:

Wdf框架之WdfObject状态机(2) 一文补充

上图中,一旦执行语句

pDriver->m_DriverUnload.Invoke(pDriver->GetHandle());      

就会调用到Kmdfdrv!Unload函数;而减少引用计数,改变FxObject状态以及释放FxObject对象的代码都在L177之后,因此进入kmdfdrv!Unload函数时,我们可以无拘无束的操作FxDriver对象。

    待到调用Unload毕,FxDriver执行语句pDriver->DeleteObject();再次改变对象状态值,发生状态迁移,并在中断在函数KMDFdrv!DrvCleanupCallback处:

virtual VOID DeleteObject(VOID)
{
    //这段断言语句挺重要的,有助于帮我们确定程序在FxObject::DeleteObject函数中的流程
    ASSERT(Mx::MxGetCurrentIrql() == PASSIVE_LEVEL);
    //FxDriver::DeleteObject被声明为虚函数,__super是VS编译器的扩展语法,最终将调用FxObject::DeleteObject
    __super::DeleteObject();
}      
//windbg中断时的调试输出:
kd> kb
ChildEBP RetAddr  Args to Child              
86fb3b70 86d4b9c5 74ac43e8 8b53bc10 8b53bc24 KMDFdrv!DrvCleanupCallback [g:\kmdfdrv\kmdfdrv\kmdf.c @ 26]
86fb3b84 86d5119a bac34ec8 8b53bc10 a24bfa40 Wdf01000!FxObject::CallCleanupCallbacks+0x35 [minkernel\wdf\framework\shared\object\fxobject.cpp @ 354]
86fb3ba8 86d7c310 a24bfa40 8ade8040 86fb3bc4 Wdf01000!FxObject::DeleteObject+0x24dba [minkernel\wdf\framework\shared\object\fxobjectstatemachine.cpp @ 124]
86fb3bb8 a6e01324 b38ffec8 86fb3be8 82160dfd Wdf01000!FxDriver::Unload+0xa1 [minkernel\wdf\framework\shared\core\fxdriver.cpp @ 179]
86fb3bc4 82160dfd b38ffec8 860c65c0 8ade8040 KMDFdrv!FxStubDriverUnload+0x1a [d:\th\minkernel\wdf\framework\kmdf\src\dynamic\stub\stub.cpp @ 159]
86fb3be8 81f3cc5f a24bfa40 00000000 8ade8040 nt!IopLoadUnloadDriver+0x87
kd> ?? drvObj
void * 0x74ac43e8
kd> !wdfhandle 0x74ac43e8
Dumping WDFHANDLE 0x74ac43e8
=============================
Handle type is WDFDRIVER
Refcount: 1 ;<----引用计数没有改变
Contexts:
    context:  dt 0x8b53bcf0 DrvCtx (size is 0xc bytes)
!wdfobject 0x8b53bc10
kd> !wdfobject 0x8b53bc10
The type for object 0x8b53bc10 is FxDriver
State: FxObjectStateDeletedDisposing (0x9) ;对象状态改变
!wdfhandle 0x74ac43e8
dt FxDriver 0x8b53bc10
Contexts:
    context:  dt 0x8b53bcf0 DrvCtx (size is 0xc bytes)      
BOOLEAN
FxObject::DeleteWorkerAndUnlock(__drv_restoresIRQL KIRQL OldIrql,BOOLEAN CanDefer)
{
    //要进入if分支,必须满足2个条件CanDefer为真;ShouldDeferDisposeLocked返回真,
    //只有当IRQL!=PASSIVE_LEVEL时,ShouldDeferDisposeLocked才会返回真。然而,前面进入FxDriver::DeleteObject时,必须确保IRQL为PASSIVE_LEVEL
    //因此,驱动不会进入if分支
    if (CanDefer && ShouldDeferDisposeLocked(&OldIrql)) {
        QueueDeferredDisposeLocked(FxObjectStateDeferedDeleting);      
else {
        SetObjectStateLocked(FxObjectStateDeletedDisposing); //改变状态
        //在DisposeChildrenWorker函数中调用KMDFdrv!DrvCleanupCallback函数
        if (DisposeChildrenWorker(FxObjectStateDeferedDeleting, OldIrql, CanDefer)) {
            //在DeletedAndDisposedWorkerLocked中减少引用计数,并调用KMDFdrv!DrvDestroyCallback函数
            DeletedAndDisposedWorkerLocked(OldIrql, FALSE);
            return TRUE;
        }      

继续阅读