C++中如何从含结构体和数组的ETW数据包提取特定字段?
提取Microsoft-Windows-USB-USB3HUB ETW事件中的Fid_PortNumber和Fid_PortPath[0]字段
我刚好做过不少ETW事件解析的开发,针对你要提取的这两个字段,咱们一步步来拆解实现逻辑:
核心思路概述
ETW事件的字段是树形嵌套结构,所以必须通过递归遍历事件属性元数据,定位到目标字段后,再根据_TDH_OUT_TYPE枚举对应的类型来提取值。对于数组类型的字段(比如Fid_PortPath),还要额外处理数组长度和索引访问。
具体实现步骤
1. 先获取事件的元数据信息
首先,你需要调用TdhGetEventInformation来获取当前ETW事件的属性列表,这是解析的基础。示例代码大致如下:
PEVENT_RECORD pEventRecord; // 这是你从ETW会话中拿到的事件记录 DWORD bufferSize = 0; // 先调用一次获取所需缓冲区大小 TdhGetEventInformation(pEventRecord, 0, nullptr, nullptr, &bufferSize); // 分配缓冲区 std::vector<BYTE> metadataBuffer(bufferSize); PEVENT_EVENT_INFO pEventInfo = reinterpret_cast<PEVENT_EVENT_INFO>(metadataBuffer.data()); // 再次调用获取实际元数据 if (TdhGetEventInformation(pEventRecord, 0, nullptr, pEventInfo, &bufferSize) != ERROR_SUCCESS) { // 处理错误 return; }
2. 递归遍历字段属性,定位目标字段
写一个递归函数,遍历pEventInfo->EventPropertyInfo数组里的每个属性,匹配字段名(注意字段名是PropertyName指向的字符串)。这里要区分普通字段和数组字段:
void TraverseProperties(PEVENT_PROPERTY_INFO pProps, DWORD propCount, PBYTE eventData, DWORD eventDataSize) { for (DWORD i = 0; i < propCount; ++i) { PEVENT_PROPERTY_INFO pProp = &pProps[i]; // 获取字段名 PCWSTR propName = reinterpret_cast<PCWSTR>(reinterpret_cast<PBYTE>(pProps) + pProp->NameOffset); // 匹配目标字段:Fid_PortNumber if (_wcsicmp(propName, L"Fid_PortNumber") == 0) { // 提取该字段的值 PBYTE fieldData = eventData + pProp->DataOffset; switch (pProp->NonStructType.OutType) { case TDH_OUT_USHORT: USHORT portNumber = *reinterpret_cast<USHORT*>(fieldData); // 保存portNumber到你的变量或存储结构 break; // 根据实际类型补充其他case,比如TDH_OUT_DWORD等 default: // 处理未知类型 break; } } // 匹配目标数组字段:Fid_PortPath else if (_wcsicmp(propName, L"Fid_PortPath") == 0) { // 先获取数组长度 DWORD arrayCount = 0; if (pProp->CountPropertyIndex != TDH_NO_COUNT_PROPERTY) { // 如果长度由另一个字段指定,需要先提取那个字段的值 PEVENT_PROPERTY_INFO countProp = &pProps[pProp->CountPropertyIndex]; PBYTE countData = eventData + countProp->DataOffset; arrayCount = *reinterpret_cast<DWORD*>(countData); } else { // 如果是固定长度数组,直接用pProp->StructType.NumOfElements arrayCount = pProp->StructType.NumOfElements; } // 访问数组的第0个元素 if (arrayCount > 0) { // 计算第0个元素的偏移:基础偏移 + 单个元素大小 * 索引 DWORD elementSize = pProp->StructType.Size; PBYTE elementData = eventData + pProp->DataOffset + (elementSize * 0); // 根据元素的类型提取值,比如如果是USHORT类型: USHORT portPath0 = *reinterpret_cast<USHORT*>(elementData); // 保存portPath0 } } // 如果当前字段是嵌套结构,递归遍历子属性 if (pProp->Flags & PropertyStruct) { PEVENT_PROPERTY_INFO childProps = reinterpret_cast<PEVENT_PROPERTY_INFO>(reinterpret_cast<PBYTE>(pProps) + pProp->StructType.StructStartIndex); DWORD childPropCount = pProp->StructType.NumOfElements; TraverseProperties(childProps, childPropCount, eventData, eventDataSize); } } }
3. 调用递归函数开始解析
在拿到pEventInfo之后,调用上面的函数:
TraverseProperties(pEventInfo->EventPropertyInfo, pEventInfo->PropertyCount, pEventRecord->UserData, pEventRecord->UserDataLength);
关键注意事项
- 字段类型匹配:一定要根据
pProp->NonStructType.OutType的值来决定如何解析内存,比如TDH_OUT_USHORT对应16位无符号整数,TDH_OUT_DWORD对应32位无符号整数,不要硬转类型,否则会出现数据错误。 - 数组长度处理:ETW数组分为动态长度(由另一个字段指定长度)和固定长度(元数据中指定
NumOfElements),两种情况要分别处理。 - 内存偏移计算:所有字段的位置都是相对于
eventData(也就是pEventRecord->UserData)的偏移,不要搞错基准地址。
内容的提问来源于stack exchange,提问作者igal k
相关产品推荐
相关产品推荐

