天天看點

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;
        }      

繼續閱讀