Wdf框架之WdfObject状态机(3)-前篇

好久没写关于Wdf框架的博客了,因为有各种琐碎事缠身,得赶在RS4 RTM前把状态机(3)系列完成。WdfObject状态机(2)系列将注意力集中在驱动程序的继承层次上只存在单薄一层WdfDriver上;而这篇文章将要分析的情形将具备一定的普遍性:在代码上参考并修改了toaster\kmdf\func\simple。下图是默认KMDF对象层次结构  
simple虽然没有包含图中所有元素,但它仍然具有WDFDRIVER/WDFDEVICE/WDFIOREQUEST/WDFQUEUE这些派生自WdfObject的对象并且在继承图上具有父子关系,因此选取它简单而又不失代表性。下面贴出部分代码:
VOID DevEvtCleanupCallback(WDFOBJECT devObj)
{
	FDO_DATA* fdoCtx = ToasterFdoGetData(devObj);
	fdoCtx->devRef = 2;
}

VOID QueEvtDestroyCallback(WDFOBJECT queObj)
{
	QUE_DATA* queCtx = ToasterQueGetData(queObj);
	queCtx->queRef = 1;
}

VOID QueEvtCleanupCallback(WDFOBJECT queObj)
{
	QUE_DATA* queCtx = ToasterQueGetData(queObj);
	queCtx->queRef = 2;
}

NTSTATUS
ToasterEvtDeviceAdd(
    IN WDFDRIVER       Driver,
    IN PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS               status = STATUS_SUCCESS;
    PFDO_DATA              fdoData;
    WDF_IO_QUEUE_CONFIG    queueConfig;
    WDF_OBJECT_ATTRIBUTES  fdoAttributes, queueAttributes;
    WDFDEVICE              hDevice;
    WDFQUEUE               queue;

    // 为了观察停用设备时WDFQUEUE和WDFDEVICE状态变化,及由状态变化时,调用EvtCleanupCallback/
    // EvtDestroyCallback的时机,分别设置WDFDEVICE/WDFQUEUE的属性
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fdoAttributes, FDO_DATA);
    fdoAttributes.EvtCleanupCallback = DevEvtCleanupCallback;
    fdoAttributes.EvtDestroyCallback = DevEvtDestroyCallback;

    status = WdfDeviceCreate(&DeviceInit, &fdoAttributes, &hDevice);
    if (!NT_SUCCESS(status)) {
        KdPrint( ("WdfDeviceCreate failed with status code 0x%x\n", status));
        return status;
    }

    fdoData = ToasterFdoGetData(hDevice);

    status = WdfDeviceCreateDeviceInterface(
                 hDevice,
                 (LPGUID) &GUID_DEVINTERFACE_TOASTER,
                 NULL // ReferenceString
             );
        ...
	WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&queueAttributes, QUE_DATA);
	queueAttributes.EvtCleanupCallback = QueEvtCleanupCallback;
	queueAttributes.EvtDestroyCallback = QueEvtDestroyCallback;
	
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig,  WdfIoQueueDispatchParallel);

    queueConfig.EvtIoRead = ToasterEvtIoRead;
    queueConfig.EvtIoWrite = ToasterEvtIoWrite;
    queueConfig.EvtIoDeviceControl = ToasterEvtIoDeviceControl;

    status = WdfIoQueueCreate(
        hDevice,
        &queueConfig,
        &queueAttributes,
        &queue
        );
    ...
    return status;
}

    为了获得queue和hDevice的句柄值,得在ToasterEvtDeviceAdd函数返回前下断点:

kd> bu wdfsimple!ToasterEvtDeviceAdd ;下延迟断点
kd> g
Breakpoint 0 hit
wdfsimple!ToasterEvtDeviceAdd:
8e5f4010 8bff            mov     edi,edi
kd> g
Breakpoint 1 hit
wdfsimple!ToasterEvtDeviceAdd+0x13e: ;在AddDevice返回到OS之前下的断点
8e5f414e 837db000        cmp     dword ptr [ebp-50h],0
kd> ?? queue
struct WDFQUEUE__ * 0x7f05e218
   +0x000 unused           : ??
kd> ?? hDevice 
struct WDFDEVICE__ * 0x4c3b8890
   +0x000 unused           : ??

queue和hDevice的句柄值分别为0x7f05e218和0x4c3b8890,我们需要借由这两个句柄值获得对应FxObject对象的地址。这些值在后面的调试过程中会反复用到:

kd> .load wdfkd ;加载wdf调试扩展
kd> !wdfhandle 0x7f05e218 ;查看WDFQUEUE句柄对应FxObject对象信息

Dumping WDFHANDLE 0x7f05e218
=============================
Handle type is WDFQUEUE
Refcount: 1
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtCleanupCallback 8e5f1180 wdfsimple!QueEvtCleanupCallback
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback

Parent: !wdfobject 0x80ef96d0, internal type is FxPkgIo ;windbg分析得出WDFQUEUE的父对象是FxpkgIo
Owning device: !wdfdevice 0x4c3b8890

!wdfobject 0x80fa1de0
kd> !wdfhandle 0x4c3b8890 ;查看WDFDEIVCE句柄对应FxObject对象信息

Dumping WDFHANDLE 0x4c3b8890
=============================
Handle type is WDFDEVICE
Refcount: 1
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtCleanupCallback 8e5f1100 wdfsimple!DevEvtCleanupCallback
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback

Parent: !wdfhandle 0x4cb03cd0, type is WDFDRIVER
Owning device: !wdfdevice 0x4c3b8890

!wdfobject 0xb3c47768

从windbg的输出可以看到,目前WDFDEVICE的父对象是WDFDRIVER,这符合开头部分的继承图;但是WDFQUEUE的父对象竟然不是继承图中的WDFDEIVCE,而是是不在五行之中的FxpkgIo,我只能认为原图作者著书时间与当前WDF框架相去甚远(大概有5年了吧),可能有所出路。但是,这并不影响对父子对象状态变化的研究,所以让我们继续探索。

kd> dt wdf01000!FxObject -y m_ObjectState ;搜索m_ObjectState字段在FxObject结构中的偏移
   +0x012 m_ObjectState    : Uint2B
kd> ba w 2 0x80fa1de0+0x12 ;为WDFQUEUE对应的FxObject!m_ObjectState设置访问断点
kd> ba w 2 0xb3c47768+0x12 ;为WDFDEVEICE对应的FxObject!m_ObjectState设置访问断点

按照MSDN的说法,在WDF框架中删除父对象时会递归的删除该对象下所有子对象,同时修改对象状态值。为了观察父子对象状态值变动的先后顺序,以及确定调用EvtCleanupCallback/EvtDestroyCallback的时机,我分别为WDFDEVICE/WDFQUEUE对象设置了访问断点。至此,所有准备工作完成,就静静的等待设备停用了~

kd> bl ;列出已设置的断点,后面触发中断时,可以到此索引断点位置
 0 e 8e5f4010 [c:\winddk\7600.16385.1\src\general\toaster\kmdf\func\simple\toaster.c @ 155]    0001 (0001) wdfsimple!ToasterEvtDeviceAdd
 1 e 8e5f414e [c:\winddk\7600.16385.1\src\general\toaster\kmdf\func\simple\toaster.c @ 256]    0001 (0001) wdfsimple!ToasterEvtDeviceAdd+0x13e
 2 e 80fa1df2 w 2 0001 (0001) 
 3 e b3c4777a w 2 0001 (0001) 
 4 e 8e5f1086 [c:\winddk\7600.16385.1\src\general\toaster\kmdf\func\simple\toaster.c @ 110]    0001 (0001) wdfsimple!DevEvtDestroyCallback+0x6
 5 e 8e5f1106 [c:\winddk\7600.16385.1\src\general\toaster\kmdf\func\simple\toaster.c @ 116]    0001 (0001) wdfsimple!DevEvtCleanupCallback+0x6
 6 e 8e5f1136 [c:\winddk\7600.16385.1\src\general\toaster\kmdf\func\simple\toaster.c @ 122]    0001 (0001) wdfsimple!QueEvtDestroyCallback+0x6
 7 e 8e5f1186 [c:\winddk\7600.16385.1\src\general\toaster\kmdf\func\simple\toaster.c @ 128]    0001 (0001) wdfsimple!QueEvtCleanupCallback+0x6

另外,根据我的观察,对于win10x86的机器,每次修改FxObject!m_ObjectState值时,OS都使用esi寄存器存放对象首地址,以[esi+0x12]间址寻址方式来读写m_ObjectState字段。如下例:

kd> g
Breakpoint 3 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
8607453f 5e              pop     esi
kd> r @esi
esi=b3c47768
kd> !wdfobject b3c47768

The type for object 0xb3c47768 is FxDevice
State: FxObjectStateDisposingEarly (0x3)
!wdfhandle 0x4c3b8890
所以,为了行文简洁,在接下去的调试过程中,我直接用寄存器esi作为!wdfobject命令的参数,打印对象状态值。

F5运行调试器,并在设备管理器中停用Toast,windbg马上会发生中断:

kd> g
Breakpoint 3 hit
kd> !wdfobject b3c47768
The type for object 0xb3c47768 is FxDevice
State: FxObjectStateDisposingEarly (0x3)
!wdfhandle 0x4c3b8890
dt FxDevice 0xb3c47768
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtCleanupCallback 8e5f1100 wdfsimple!DevEvtCleanupCallback
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x4c3b8890
Handle type is WDFDEVICE
Refcount: 1
;以上片段是WDFDEVICE第一次修改状态
kd> g
Breakpoint 3 hit
kd> !wdfobject @esi
The type for object 0xb3c47768 is FxDevice
State: FxObjectStateDisposingDisposeChildren (0x4)
!wdfhandle 0x4c3b8890
dt FxDevice 0xb3c47768
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtCleanupCallback 8e5f1100 wdfsimple!DevEvtCleanupCallback
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x4c3b8890
Handle type is WDFDEVICE
Refcount: 1
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtCleanupCallback 8e5f1100 wdfsimple!DevEvtCleanupCallback
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890

!wdfobject 0xb3c47768
;以上片段是WDFDEIVEC第二次修改状态

停用设备后,作为父对象的WDFDEVICE会先于子对象WDFQUEUE,发生两次状态改变。设备状态从FxObjectStateDisposingEarly跃迁到FxObjectStateDisposingDisposeChildren,这两个对象状态在以往单层设备驱动中从未谋面。 其次对象状态虽有改动,但其句柄的引用计数维持不变,不要错过这个信号,这是代码中是否可以使用句柄的依据。最后,EvtCleanupCallback尚未调用,更别说EvtDestroyCallback回调了。

再次运行并触发断点时,是因为OS尝试修改子对象WDFQUEUE的状态:

kd> g
Breakpoint 2 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
kd> !wdfobject @esi ;FxIoQueue的状态值由FxObjectStateCreate变为FxObjectStateDisposingEarly
The type for object 0x80fa1de0 is FxIoQueue
State: FxObjectStateDisposingEarly (0x3)
!wdfhandle 0x7f05e218
dt FxIoQueue 0x80fa1de0
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtCleanupCallback 8e5f1180 wdfsimple!QueEvtCleanupCallback
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
Parent: !wdfobject 0x80ef96d0
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x7f05e218 
Refcount: 1 ;检查当前句柄的引用计数
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtCleanupCallback 8e5f1180 wdfsimple!QueEvtCleanupCallback
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
kd> g
Breakpoint 2 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
kd> !wdfobject @esi ;状态机再次修改FxIoQueue的状态,可以说,前一个状态FxObjectStateDisposingEarly是个短暂的状态
The type for object 0x80fa1de0 is FxIoQueue
State: FxObjectStateDisposingDisposeChildren (0x4)
!wdfhandle 0x7f05e218
dt FxIoQueue 0x80fa1de0
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtCleanupCallback 8e5f1180 wdfsimple!QueEvtCleanupCallback
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
kd> !wdfhandle 0x7f05e218
Handle type is WDFQUEUE
Refcount: 1 ;句柄值依然不变
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtCleanupCallback 8e5f1180 wdfsimple!QueEvtCleanupCallback
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback

对比FxDevice和FxIoQueue的状态变化可知,为了删除整棵对象树中的各个对象,根对象在FxObjectStateDisposingDisposeChildren阶段向下遍历所有子对象,每次寻获的子对象又如法炮制的递归上述过程,直到遇到对象树中的离根对象最远的叶对象。那么,遇到叶对象后,会发生怎样的剧情?

kd> g
Breakpoint 7 hit
wdfsimple!QueEvtCleanupCallback+0x6:
kd> g
Breakpoint 2 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
kd> !wdfobject @esi
The type for object 0x80fa1de0 is FxIoQueue
State: FxObjectStateDisposed (0x2)
!wdfhandle 0x7f05e218
dt FxIoQueue 0x80fa1de0
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
kd> !wdfhandle 0x7f05e218
Refcount: 1
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
FxIoQueue遍历完子对象了后,开始自身的清理工作:调用QueEvtCleanupCallback,而后修改状态FxObjectStateDisposed。 如果,把上述过程换个轻松的说法,就是这样:首先对象树上的对象看破红尘,想脱世抛开肉身(调用EvtCleanupCallback做清理工作);无奈涉世太深---子嗣多,所以对外宣布和众子孙断绝关系(改变状态为FxObjectStateDisposingDisposeChildren);最后,成功出家(子嗣都抛弃了,disposed有抛弃的意思。状态修改为FxObjectStateDisposed)。但是,在断绝子孙的过程中,子孙们不理解啊:出家有啥好的,搞不懂,我也要体验一把,结果纷纷效仿。毕竟,青年人入世浅,所以率先于他们的祖宗出世。

在继续下去之前,来做个选择题,预测一下驱动程序的走向:

1).既然调用了QueEvtCleanupCallback,状态机将着手准备调用QueEvtDestroyCallback,清理FxIoQueue对象;

2).状态机回溯到父对象FxDevice,调用DevEvtCleanupCallback,然后修改FxObject!m_ObjectState状态值;

你选了那个选项?到后面看看有没有选对,反正当时我是没选对~

开始公布答案:

kd> g
Breakpoint 5 hit
wdfsimple!DevEvtCleanupCallback+0x6:
kd> !wdfhandle 0x4c3b8890
Handle type is WDFDEVICE
Refcount: 1
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtCleanupCallback 8e5f1100 wdfsimple!DevEvtCleanupCallback
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890

!wdfobject 0xb3c47768
d> g
Breakpoint 3 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
kd> !wdfobject @esi
The type for object 0xb3c47768 is FxDevice
State: FxObjectStateDisposed (0x2)
!wdfhandle 0x4c3b8890
dt FxDevice 0xb3c47768
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x4c3b8890
Handle type is WDFDEVICE
Refcount: 1
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback

windbg给出的标准答案是2)。不知道各位有没有选对?按照windbg给出的答案,可以得到这样的结论:删除某个FxObject对象时,状态机将遵循下列顺序:

1).先修改该对象的状态为FxObjectStateDisposingDisposeChildren;

2).接着保持该对象的状态并在该对象的对象树中递归式(由近及远)修改各个子对象的状态;

3).当递归遇到对象树中的叶子节点时,调用EvtCleanupCallback;

4).修改对象状态(不在保持对象状态),使其从FxObjectStateDisposingDisposeChildren变为FxObjectStateDisposed;

5).从子对象向发生删除操作的对象返回,沿途调用EvtCleanupCallback,顺带修改对象状态。

执行完EvtCleanupCallback后,剩下的事就毫无悬念了,肯定就是调用EvtDestroyCallback。状态机按:

1).先修改子对象的状态为FxObjectStateDeletedAndDisposed;

2).调用EvtDestroyCallback;

3).调用FxObject的析构函数,并修改子对象的状态为FxObjectStateDestroyed;

4).回溯到父对象,父对象执行步骤1)-3)。

需要注意的是,和调用EvtCleanupCallback略有不同:父对象并没有先修改状态为FxObjectStateDeletedAndDisposed,然后等待子对象清理完回来后再调用EvtDestroyCallback;而是父对象一直后知后觉的等到子对象返回,才做上述操作。

kd> g
Breakpoint 2 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
kd> !wdfobject @esi
The type for object 0x80fa1de0 is FxIoQueue
State: FxObjectStateDeletedAndDisposed (0xa)
!wdfhandle 0x7f05e218
dt FxIoQueue 0x80fa1de0
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x7f05e218
Handle type is WDFQUEUE
Refcount: 1
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    EvtDestroyCallback 8e5f1130 wdfsimple!QueEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890
!wdfobject 0x80fa1de0
kd> g
Breakpoint 6 hit
wdfsimple!QueEvtDestroyCallback+0x6:
8e5f1136 8b4508          mov     eax,dword ptr [ebp+8]
kd> g
Breakpoint 2 hit
Wdf01000!FxObject::~FxObject+0x51:
8607cc21 5e              pop     esi
kd> !wdfobject @esi
The type for object 0x80fa1de0 is FxIoQueue
State: FxObjectStateDestroyed (0xc)
!wdfhandle 0x7f05e218
dt FxIoQueue 0x80fa1de0
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    <no associated attribute callbacks>
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x7f05e218
Handle type is WDFQUEUE
Refcount: 0
Contexts:
    context:  dt 0x80fa1ff8 QUE_DATA (size is 0x4 bytes)
    <no associated attribute callbacks>
;从这往上是子对象的改变
;从这往下是父对象的改变
kd> g
Breakpoint 3 hit
Wdf01000!FxObject::SetObjectStateLocked+0x1f:
kd> !wdfobject @esi
The type for object 0xb3c47768 is FxDevice
State: FxObjectStateDeletedAndDisposed (0xa)
!wdfhandle 0x4c3b8890
dt FxDevice 0xb3c47768
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
kd> !wdfhandle 0x4c3b8890
Handle type is WDFDEVICE
Refcount: 1
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    EvtDestroyCallback 8e5f1080 wdfsimple!DevEvtDestroyCallback
Owning device: !wdfdevice 0x4c3b8890
kd> g
Breakpoint 4 hit
wdfsimple!DevEvtDestroyCallback+0x6:
kd> g
Breakpoint 3 hit
Wdf01000!FxObject::~FxObject+0x51:
8607cc21 5e              pop     esi
kd> !wdfobject @esi
The type for object 0xb3c47768 is FxDevice
State: FxObjectStateDestroyed (0xc)
!wdfhandle 0x4c3b8890
dt FxDevice 0xb3c47768
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    <no associated attribute callbacks>
Owning device: !wdfdevice 0x4c3b8890
kd> !wdfhandle 0x4c3b8890
Handle type is WDFDEVICE
Refcount: 0
Contexts:
    context:  dt 0xb3c478f8 FDO_DATA (size is 0x28 bytes)
    <no associated attribute callbacks>
Owning device: !wdfdevice 0x4c3b8890

!wdfobject 0xb3c47768

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值