使用 USB ETW 跟踪中的活动 ID GUID

本主题提供有关活动 ID GUID、如何在事件跟踪提供程序中添加这些 GUID 以及如何在 Netmon 中查看它们的信息。

USB 驱动程序堆栈 (2.0 和 3.0) 是 ETW 事件跟踪提供程序。 在 Windows 7 中,从 USB 驱动程序堆栈捕获事件跟踪时,可以从其他提供程序(如其他驱动程序和应用程序)捕获跟踪。 然后,可以读取合并的日志 (假设已为提供程序的事件跟踪) 创建了 Netmon 分析程序。

从 Windows 8 开始,可以使用活动 ID GUID 跨提供程序关联事件, (应用程序、客户端驱动程序和 USB 驱动程序堆栈) 。 当事件具有相同的活动 ID GUID 时,可以在 Netmon 中关联来自多个提供程序的事件。 基于这些 GUID,Netmon 可以显示上层的检测活动产生的 USB 事件集。

在 Netmon 中查看来自其他提供程序的组合事件跟踪时,右键单击应用程序中的事件,然后选择“ 查找对话 -> NetEvent ”以查看关联的驱动程序事件。

此图显示了来自应用程序、UMDF 驱动程序以及 Ucx01000.sys (USB 驱动程序堆栈中的其中一个驱动程序) 的相关事件。这些事件具有相同的活动 ID GUID。

microsoft 网络监视器。

如何在应用程序中添加活动 ID GUID

应用程序可以通过调用 EventActivityIdControl 来包含活动 ID GUID。 有关详细信息,请参阅事件跟踪函数

此示例代码演示应用程序如何设置活动 ID GUID 并将其发送到 ETW 提供程序(UMDF 驱动程序)。

EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &activityIdStruct.ActivityId); 
EventActivityIdControl(EVENT_ACTIVITY_CTRL_SET_ID,    &activityIdStruct.ActivityId); 

if (!DeviceIoControl(hRead,
                     IOCTL_OSRUSBFX2_SET_ACTIVITY_ID,
                     &activityIdStruct,         // Ptr to InBuffer
                     sizeof(activityIdStruct),  // Length of InBuffer
                     NULL,                      // Ptr to OutBuffer
                     0,                         // Length of OutBuffer
                     NULL,                      // BytesReturned
                     0))                        // Ptr to Overlapped structure
{         

          wprintf(L"Failed to set activity ID - error %d\n", GetLastError());
}

...

success = ReadFile(hRead, pinBuf, G_ReadLen, (PULONG) &nBytesRead, NULL);

if(success == 0) 
{
          wprintf(L"ReadFile failed - error %d\n", GetLastError());

          EventWriteReadFail(0, GetLastError());

          ...

}

在前面的示例中,应用程序调用 EventActivityIdControl (EVENT_ACTIVITY_CTRL_CREATE_ID) 创建活动 ID,然后将其设置为当前线程 (EVENT_ACTIVITY_CTRL_SET_ID) 。 应用程序通过发送驱动程序定义的 IOCTL ((如下一部分) 中所述)来指定活动 GUID 到 ETW 事件提供程序,例如用户模式驱动程序。

事件提供程序必须 ( 发布检测清单文件。MAN 文件) 。 通过运行 消息编译器 (Mc.exe) ,将生成一个头文件,其中包含事件提供程序、事件属性、通道和事件的定义。 在此示例中,应用程序调用在生成的头文件中定义的 EventWriteReadFail,以在发生故障时写入跟踪事件消息。

如何在 UMDF 驱动程序中设置活动 ID GUID

用户模式驱动程序通过调用 EventActivityIdControl 创建和设置活动 ID GUID,这些调用类似于应用程序调用它们的方式,如上一部分所述。 这些调用将活动 ID GUID 添加到当前线程,每当线程记录事件时,将使用活动 ID GUID。 有关详细信息,请参阅 使用活动标识符

此示例代码演示 UMDF 驱动程序如何设置应用程序通过 IOCTL 创建和指定的活动 ID GUID。

VOID
STDMETHODCALLTYPE
CMyControlQueue::OnDeviceIoControl(
    _In_ IWDFIoQueue *FxQueue,
    _In_ IWDFIoRequest *FxRequest,
    _In_ ULONG ControlCode,
    _In_ SIZE_T InputBufferSizeInBytes,
    _In_ SIZE_T OutputBufferSizeInBytes
    )
/*++

Routine Description:

    DeviceIoControl dispatch routine

Aruments:

    FxQueue - Framework Queue instance
    FxRequest - Framework Request  instance
    ControlCode - IO Control Code
    InputBufferSizeInBytes - Lenth of input buffer
    OutputBufferSizeInBytes - Lenth of output buffer

    Always succeeds DeviceIoIoctl
Return Value:

    VOID

--*/
{
    ...

    switch (ControlCode)
    {

        ....

        case IOCTL_OSRUSBFX2_SET_ACTIVITY_ID:
        {
            if (InputBufferSizeInBytes < sizeof(UMDF_ACTIVITY_ID))
            {
                hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
            }
            else
            {
                FxRequest->GetInputMemory(&memory );
            }

            if (SUCCEEDED(hr)) 
            {
                buffer = memory->GetDataBuffer(&bigBufferCb);
                memory->Release();

                m_Device->SetActivityId(&((PUMDF_ACTIVITY_ID)buffer)->ActivityId);
                hr = S_OK;
            }

            break;
        }
    } 
}

VOID
 SetActivityId(
        LPCGUID ActivityId
        )
    {
        CopyMemory(&m_ActivityId, ActivityId, sizeof(m_ActivityId));
    }

void
CMyReadWriteQueue::ForwardFormattedRequest(
    _In_ IWDFIoRequest*                         pRequest,
    _In_ IWDFIoTarget*                          pIoTarget
    )
{
...
    pRequest->SetCompletionCallback(
        pCompletionCallback,
        NULL
        );

...
    hrSend = pRequest->Send(pIoTarget,
                            0,  //flags
                            0); //timeout

...
    if (FAILED(hrSend))
    {
        contextHr = pRequest->RetrieveContext((void**)&pRequestContext);

        if (SUCCEEDED(contextHr)) {

            EventActivityIdControl(EVENT_ACTIVITY_CTRL_SET_ID, &pRequestContext->ActivityId);

            if (pRequestContext->RequestType == RequestTypeRead)
            {
                EventWriteReadFail(m_Device, hrSend);
            }

            delete pRequestContext;
        }

        pRequest->CompleteWithInformation(hrSend, 0);
    }

    return;
}

让我们看看应用程序创建的活动 ID GUID 如何与 用户模式驱动程序框架 (UMDF) 客户端驱动程序相关联。 当驱动程序从应用程序收到 IOCTL 请求时,它会复制专用成员中的 GUID。 在某些时候,应用程序会调用 ReadFile 来执行读取操作。 框架创建请求并调用驱动程序的处理程序 ForwardFormattedRequest。 在处理程序中,驱动程序通过调用 EventActivityIdControl 和 EventWriteReadFail 来跟踪事件消息,在线程上设置以前存储的活动 ID GUID。

注意 UMDF 驱动程序还必须包含通过检测清单文件生成的头文件。 头文件定义写入跟踪消息的宏,例如 EventWriteReadFail。

如何在内核模式驱动程序中添加活动 ID GUID

在内核模式下,驱动程序可以在源自用户模式的线程或驱动程序创建的线程上跟踪消息。 在这两种情况下,驱动程序都需要线程的活动 ID GUID。

若要跟踪消息,驱动程序必须获取注册句柄作为事件提供程序 (请参阅 EtwRegister) 然后通过指定 GUID 和事件消息调用 EtwWrite 。 有关详细信息,请参阅将事件跟踪添加到内核模式驱动程序

如果内核模式驱动程序处理由应用程序或用户模式驱动程序创建的请求,则内核模式驱动程序不会创建和设置活动 ID GUID。 相反,I/O 管理器会处理大部分活动 ID 传播。 当用户模式线程启动请求时,I/O 管理器会为请求创建 IRP,并自动将当前线程的活动 ID GUID 复制到新的 IRP 中。 如果内核模式驱动程序想要跟踪该线程上的事件,则必须通过调用 IoGetActivityIdIrp 获取 GUID,然后调用 EtwWrite

如果内核模式驱动程序使用活动 ID GUID 创建 IRP,则驱动程序可以使用 EVENT_ACTIVITY_CTRL_CREATE_SET_ID 调用 EtwActivityIdControl 来生成新的 GUID。 然后,驱动程序可以通过调用 IoSetActivityIdIrp 将新的 GUID 与 IRP 相关联,然后调用 EtwWrite

活动 ID GUID 与 IRP 一起传递给下一个较低的驱动程序。 较低的驱动程序可以将其跟踪消息添加到线程。