Windows用户态应用实时收集WPP追踪日志的代码问题排查
首先明确一点:WPP追踪完全可以通过ETW API实时收集,你的思路是对的,问题应该出在代码的几个关键参数配置或者权限上,咱们一步步梳理:
一、核心问题排查点
1. 错误的日志会话模式
你在pSessionProperties->LogFileMode里设置了EVENT_TRACE_SYSTEM_LOGGER_MODE,这个标志是专门给Windows系统内置的内核会话用的,自定义用户会话绝对不能用它!去掉这个标志,只保留EVENT_TRACE_REAL_TIME_MODE即可,否则会话创建后会出现异常,无法正确捕获自定义提供者的事件。
2. Provider GUID的匹配问题
你在EnableTraceEx2里传入的&ProviderGuid必须完全和驱动里WPP初始化时用的DRIVER_PROVIDER_GUID一致,包括大小写、连字符位置,一点都不能错。可以把驱动里定义的GUID直接复制到用户态代码里,避免手动输入出错。
3. 关键字参数设置
你的EnableTraceEx2里关键字参数传了0,但如果驱动里的WPP日志宏指定了关键字(比如用WPP_LOGGER(WPP_KEYWORD_MYFEATURE)这类带关键字的宏输出日志),0表示不匹配任何关键字,自然收不到事件。可以先改成0xFFFFFFFF(匹配所有关键字)来测试,确认能收到事件后再根据需求调整。
4. 权限不足
ETW会话创建和内核提供者启用需要管理员权限,一定要确保你的用户态程序是以管理员身份运行的,否则StartTrace或EnableTraceEx2会静默失败(返回错误码比如ERROR_ACCESS_DENIED),但你代码里没对这些API的返回值做详细检查,建议在每个API调用后添加错误判断,比如:
status = StartTrace((PTRACEHANDLE)&SessionHandle, SessionName, pSessionProperties); if (status != ERROR_SUCCESS) { wprintf(L"StartTrace failed: %lu\n", status); goto cleanup; }
5. TdhLoadManifest的误用
WPP日志解析依赖的是TMF文件(Trace Message Format),而不是直接加载PDB。TdhLoadManifest是用来加载ETW清单的,对WPP来说并不适用。正确的做法是:
- 确保驱动编译时生成了TMF文件(通常在输出目录里,后缀是.tmf)
- 调用
TdhSetManifestSearchPath指定TMF文件所在的路径,这样后续解析事件时Tdh才能找到格式信息 - 如果想用PDB解析,需要调用
TdhLoadTraceManifestFromPdb来从PDB中提取追踪信息,但这个步骤是解析事件用的,不影响事件捕获——你现在连事件都收不到,先解决捕获问题,再处理解析。
二、修正后的关键代码片段
void StartETWSession() { ENABLE_TRACE_PARAMETERS traceParameters; ZeroMemory(&traceParameters, sizeof(traceParameters)); traceParameters.Version = ENABLE_TRACE_PARAMETERS_VERSION_2; traceParameters.EnableFilterDesc = NULL; traceParameters.FilterDescCount = 0; bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(SessionName); pSessionProperties = (EVENT_TRACE_PROPERTIES*)malloc(bufferSize); ZeroMemory(pSessionProperties, bufferSize); pSessionProperties->Wnode.BufferSize = bufferSize; pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; pSessionProperties->Wnode.ClientContext = 1; // 自定义会话GUID,随便生成一个即可,不用系统的 pSessionProperties->Wnode.Guid = MY_CUSTOM_SESSION_GUID; pSessionProperties->EnableFlags = EVENT_TRACE_FLAG_CSWITCH; // 这个是系统事件开关,和WPP无关,可保留或去掉 // 去掉SYSTEM_LOGGER_MODE,只保留实时模式 pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; pSessionProperties->MaximumFileSize = 100; pSessionProperties->FlushTimer = 1; pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LoggerNameOffset), sizeof(SessionName), SessionName); ULONG status = StartTrace((PTRACEHANDLE)&SessionHandle, SessionName, pSessionProperties); if (status != ERROR_SUCCESS) { wprintf(L"StartTrace failed: %lu\n", status); goto cleanup; } // 设置TMF文件路径(假设TMF在当前目录) WCHAR tmfPath[MAX_PATH] = L".\\"; status = TdhSetManifestSearchPath(tmfPath); if (status != ERROR_SUCCESS) { wprintf(L"TdhSetManifestSearchPath failed: %lu\n", status); // 这里可以继续执行,只是后续解析可能失败 } // 启用驱动的WPP提供者,用正确的DRIVER_PROVIDER_GUID,匹配所有关键字 status = EnableTraceEx2( SessionHandle, &DRIVER_PROVIDER_GUID, // 确保这个GUID和驱动里的完全一致 EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_VERBOSE, 0xFFFFFFFF, // 匹配所有关键字 0, 0, NULL ); if (status != ERROR_SUCCESS) { wprintf(L"EnableTraceEx2 failed: %lu\n", status); goto cleanup; } EVENT_TRACE_LOGFILE logFile; ZeroMemory(&logFile, sizeof(EVENT_TRACE_LOGFILE)); logFile.LoggerName = SessionName; logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; logFile.EventRecordCallback = EventRecordCallback; logFile.Context = &context; hTrace = OpenTrace(&logFile); if (INVALID_PROCESSTRACE_HANDLE == hTrace) { wprintf(L"OpenTrace() failed with status: %lu\n", GetLastError()); goto cleanup; } status = ProcessTrace(&hTrace, 1, NULL, NULL); if (status != ERROR_SUCCESS) { wprintf(L"ProcessTrace failed: %lu\n", status); } cleanup: // 别忘了关闭会话和释放资源 if (SessionHandle != INVALID_HANDLE_VALUE) { ControlTrace(SessionHandle, SessionName, pSessionProperties, EVENT_TRACE_CONTROL_STOP); } if (pSessionProperties) { free(pSessionProperties); } if (hTrace != INVALID_PROCESSTRACE_HANDLE) { CloseTrace(hTrace); } } VOID WINAPI EventRecordCallback(EVENT_RECORD* pEventRecord) { // 先打印所有收到的事件的ProviderID,确认是否有驱动的GUID GUID providerId = pEventRecord->EventHeader.ProviderId; wprintf(L"Received event from provider: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n", providerId.Data1, providerId.Data2, providerId.Data3, providerId.Data4[0], providerId.Data4[1], providerId.Data4[2], providerId.Data4[3], providerId.Data4[4], providerId.Data4[5], providerId.Data4[6], providerId.Data4[7]); if (IsEqualGUID(providerId, DRIVER_PROVIDER_GUID)) { // 现在应该能走到这里了 wprintf(L"Got WPP event from driver!\n"); // 后续可以用TdhFormatEvent等函数解析事件内容 } }
三、额外验证步骤
- 先用TraceView确认驱动的WPP日志能正常输出,排除驱动本身的问题
- 检查驱动里是否调用了
WPP_INIT_TRACING初始化追踪,并且在卸载时调用WPP_CLEANUP - 如果驱动是内核模式,确认用户态程序的位数和驱动一致(比如都是x64),避免跨位数的兼容性问题
备注:内容来源于stack exchange,提问作者TheKwiatek666

