本文是前一篇文章<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可以发现很多趣事:
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将执行下列代码片(这段代码由编译器设置,因此只能反汇编):
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。这个函数出现在前面堆栈调用中,因此,我们就跟进去看看这个编译器提供的函数的实现:
其实现大概如下。
if(WdfDriverStubDisplacedDriverUnload!=NULL)
{
if(FxStubDriverUnload!=FxStubDriverUnload)
{
(*WdfDriverStubDisplacedDriverUnload)(Driver_Object);
}
}
FxStubDriverUnloadCommon();
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)
我们借由windbg分析输出的调用堆栈和FxDriver的状态值(FxObjectStateDeletedDisposing),可以简单的分析出驱动不进入FxObject::DeleteWorkerAndUnlock的if分支的原因:
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);
既然if分支无法进入,驱动程序只能进入else分支,对于kmdfdrv驱动而言,它的年轻生命就终止在这个分支中:
else {
SetObjectStateLocked(FxObjectStateDeletedDisposing); //改变状态
//在DisposeChildrenWorker函数中调用KMDFdrv!DrvCleanupCallback函数
if (DisposeChildrenWorker(FxObjectStateDeferedDeleting, OldIrql, CanDefer)) {
//在DeletedAndDisposedWorkerLocked中减少引用计数,并调用KMDFdrv!DrvDestroyCallback函数
DeletedAndDisposedWorkerLocked(OldIrql, FALSE);
return TRUE;
}
这篇文章有点长了,下篇文章再分析这段具有送葬功能的else分支~