Windows Event Trace 编程(TDH.lib)
来源:互联网 发布:离散傅里叶矩阵 编辑:程序博客网 时间:2024/05/01 11:53
简单做个摘录,持续更新。
一. 配置和启动Event tracing Session
要配置一个事件跟踪会话,使用EVENT_TRACE_PROPERTIES结构来指定会话的属性。
//待注释typedef struct _EVENT_TRACE_PROPERTIES { WNODE_HEADER Wnode; ULONG BufferSize; ULONG MinimumBuffers; ULONG MaximumBuffers; ULONG MaximumFileSize; ULONG LogFileMode; ULONG FlushTimer; ULONG EnableFlags; LONG AgeLimit; ULONG NumberOfBuffers; ULONG FreeBuffers; ULONG EventsLost; ULONG BuffersWritten; ULONG LogBuffersLost; ULONG RealTimeBuffersLost; HANDLE LoggerThreadId; ULONG LogFileNameOffset; ULONG LoggerNameOffset;} EVENT_TRACE_PROPERTIES, *PEVENT_TRACE_PROPERTIES;
所以所分配的内存必须足够大,包含会话日志的名称(etl文件名称),会话日志的路径,和结构体大小。
BufferSize = sizeof(EVENT_TRACE_PROPERTIES)+sizeof(LOGFILE_PATH)+sizeof(LOGSESSION_NAME);
设置完结构体内容后,调用StartTrace函数启动会话。函数成功,则通过参数返回SessionHandle,并将会话名称的偏移给LoggerNameOffset成员(有一些疑问待解决)。
//待注释ULONG StartTrace( _Out_ PTRACEHANDLE SessionHandle, _In_ LPCTSTR SessionName, _Inout_ PEVENT_TRACE_PROPERTIES Properties);
使用EnableTrace/EX/EX2函数,打开你想要记录事件到Session的providers(事件提供者)。启用或禁用指定的经典事件跟踪提供程序。
在Windows Vista和更高版本,调用EnableTraceEx功能启用或禁用提供商。
//待注释ULONG EnableTrace( _In_ ULONG Enable, _In_ ULONG EnableFlag, _In_ ULONG EnableLevel, _In_ LPCGUID ControlGuid, _In_ TRACEHANDLE SessionHandle);
使用EnableTraceEx函数,启用或禁用指定的事件跟踪提供程序。
//待注释ULONG EnableTraceEx( _In_ LPCGUID ProviderId, _In_opt_ LPCGUID SourceId, _In_ TRACEHANDLE TraceHandle, _In_ ULONG IsEnabled, _In_ UCHAR Level, _In_ ULONGLONG MatchAnyKeyword, _In_ ULONGLONG MatchAllKeyword, _In_ ULONG EnableProperty, _In_opt_ PEVENT_FILTER_DESCRIPTOR EnableFilterDesc);
EnableTraceEx2功能取代了此函数。
//待注释ULONG EnableTraceEx2( _In_ TRACEHANDLE TraceHandle, _In_ LPCGUID ProviderId, _In_ ULONG ControlCode, _In_ UCHAR Level, _In_ ULONGLONG MatchAnyKeyword, _In_ ULONGLONG MatchAllKeyword, _In_ ULONG Timeout, _In_opt_ PENABLE_TRACE_PARAMETERS EnableParameters);
对于manifest-based provider可以有最多8个Session进行启用和接收,但是对于classic provider只允许同时存在一个Session进行启用和记录数据,即: SessionA enabled Provider1,then SessionB enabled provider1,only SessionB would recv events from provider1.
____________________________________
在Winodows 8.1之后
可以使用EnableTraceEx2函数和ENABLE_TRACE_PARAMETERS和EVENT_FILTER_DESCRIPTOR结构体在会话中对payload,scope,stack walk进行过滤。
ENABLE_TRACE_PARAMETERS结构定义信息用于启用提供者。
typedef struct _ENABLE_TRACE_PARAMETERS { ULONG Version; ULONG EnableProperty; ULONG ControlFlags; GUID SourceId; PEVENT_FILTER_DESCRIPTOR EnableFilterDesc; ULONG FilterDescCount;} ENABLE_TRACE_PARAMETERS, *PENABLE_TRACE_PARAMETERS;
该EVENT_FILTER_DESCRIPTOR结构 定义一个过滤数据传递给会话的事件提供者的回调函数。
typedef struct _EVENT_FILTER_DESCRIPTOR { ULONGLONG Ptr; ULONG Size; ULONG Type;} EVENT_FILTER_DESCRIPTOR, *PEVENT_FILTER_DESCRIPTOR;
等等,一些函数 TdhCreatePayloadFilter, and TdhAggregatePayloadFilters functions(不理解)。
____________________________________
可以再事件供应者注册之前或之后启用事件供应者,ETW都会调用提供的回调函数,如果没有注册会自己注册(?)
可以使用EnableTrace或者EnableTraceEx2来关闭或者更新provider相关数据(开关标志)。每次调用这两个函数都会使得会话,ETW都会调用回调函数。provider保持开启状态,直到由会话将其关闭。
在停止跟踪会话的时候,使用ControlTrace函数,使用EVENT_TRACE_CONTROL_STOP。使用StartTrace函数获得的SessionHanle或者先前启动会话的名称。(要先关闭provider在关闭Session)
//待注释ULONG ControlTrace( _In_ TRACEHANDLE SessionHandle, _In_ LPCTSTR SessionName, _Inout_ PEVENT_TRACE_PROPERTIES Properties, _In_ ULONG ControlCode);/*eg: status = ControlTrace(SessionHandle, LOGSESSION_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP);*/
下面的几个例子进行参考分析和研究代码逻辑。
//待注释 //Example that Creates a Session and Enables a Manifest-based or Classic Provider//The following example shows how to start a trace session, enables a manifest-based or classic provider, disables the provider and then stops the session.#include <windows.h>#include <stdio.h>#include <conio.h>#include <strsafe.h>#include <wmistr.h>#include <evntrace.h>#define LOGFILE_PATH L"<FULLPATHTOLOGFILE.etl>"#define LOGSESSION_NAME L"My Event Trace Session"// GUID that identifies your trace session.// Remember to create your own session GUID.// {AE44CB98-BD11-4069-8093-770EC9258A12}static const GUID SessionGuid = { 0xae44cb98, 0xbd11, 0x4069, { 0x80, 0x93, 0x77, 0xe, 0xc9, 0x25, 0x8a, 0x12 } };// GUID that identifies the provider that you want// to enable to your session.// {D8909C24-5BE9-4502-98CA-AB7BDC24899D}static const GUID ProviderGuid = { 0xd8909c24, 0x5be9, 0x4502, {0x98, 0xca, 0xab, 0x7b, 0xdc, 0x24, 0x89, 0x9d } };void wmain(void){ ULONG status = ERROR_SUCCESS; TRACEHANDLE SessionHandle = 0; EVENT_TRACE_PROPERTIES* pSessionProperties = NULL; ULONG BufferSize = 0; BOOL TraceOn = TRUE; // Allocate memory for the session properties. The memory must // be large enough to include the log file name and session name, // which get appended to the end of the session properties structure. BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) + sizeof(LOGSESSION_NAME); pSessionProperties = (EVENT_TRACE_PROPERTIES*) malloc(BufferSize); if (NULL == pSessionProperties) { wprintf(L"Unable to allocate %d bytes for properties structure.\n", BufferSize); goto cleanup; } // Set the session properties. You only append the log file name // to the properties structure; the StartTrace function appends // the session name for you. ZeroMemory(pSessionProperties, BufferSize); pSessionProperties->Wnode.BufferSize = BufferSize; pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution pSessionProperties->Wnode.Guid = SessionGuid; pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL; pSessionProperties->MaximumFileSize = 1; // 1 MB pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME); StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH); // Create the trace session. status = StartTrace((PTRACEHANDLE)&SessionHandle, LOGSESSION_NAME, pSessionProperties); if (ERROR_SUCCESS != status) { wprintf(L"StartTrace() failed with %lu\n", status); goto cleanup; } // Enable the providers that you want to log events to your session. status = EnableTraceEx2( SessionHandle, (LPCGUID)&ProviderGuid, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL ); if (ERROR_SUCCESS != status) { wprintf(L"EnableTrace() failed with %lu\n", status); TraceOn = FALSE; goto cleanup; } wprintf(L"Run the provider application. Then hit any key to stop the session.\n"); _getch();cleanup: if (SessionHandle) { if (TraceOn) { status = EnableTraceEx2( SessionHandle, (LPCGUID)&ProviderGuid, EVENT_CONTROL_CODE_DISABLE_PROVIDER, TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL ); } status = ControlTrace(SessionHandle, LOGSESSION_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP); if (ERROR_SUCCESS != status) { wprintf(L"ControlTrace(stop) failed with %lu\n", status); } } if (pSessionProperties) { free(pSessionProperties); pSessionProperties = NULL; }}
下面的例子演示了如何配置和启动NT内核记录会议,收集网络中的TCP/ IP内核事件,并将其写入到一个5MB的循环文件。
//Configuring and Starting the NT Kernel Logger Session//There is only one NT Kernel Logger session. If the session is already in use, the StartTrace function returns ERROR_ALREADY_EXISTS.#define INITGUID // Include this #define to use SystemTraceControlGuid in Evntrace.h.#include <windows.h>#include <stdio.h>#include <conio.h>#include <strsafe.h>#include <wmistr.h>#include <evntrace.h>#define LOGFILE_PATH L"<FULLPATHTOTHELOGFILE.etl>"void wmain(void){ ULONG status = ERROR_SUCCESS; TRACEHANDLE SessionHandle = 0; EVENT_TRACE_PROPERTIES* pSessionProperties = NULL; ULONG BufferSize = 0; // Allocate memory for the session properties. The memory must // be large enough to include the log file name and session name, // which get appended to the end of the session properties structure. BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGFILE_PATH) + sizeof(KERNEL_LOGGER_NAME); pSessionProperties = (EVENT_TRACE_PROPERTIES*) malloc(BufferSize); if (NULL == pSessionProperties) { wprintf(L"Unable to allocate %d bytes for properties structure.\n", BufferSize); goto cleanup; } // Set the session properties. You only append the log file name // to the properties structure; the StartTrace function appends // the session name for you. ZeroMemory(pSessionProperties, BufferSize); pSessionProperties->Wnode.BufferSize = BufferSize; pSessionProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; pSessionProperties->Wnode.ClientContext = 1; //QPC clock resolution pSessionProperties->Wnode.Guid = SystemTraceControlGuid; pSessionProperties->EnableFlags = EVENT_TRACE_FLAG_NETWORK_TCPIP; pSessionProperties->LogFileMode = EVENT_TRACE_FILE_MODE_CIRCULAR; pSessionProperties->MaximumFileSize = 5; // 5 MB pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); pSessionProperties->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME); StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH); // Create the trace session. status = StartTrace((PTRACEHANDLE)&SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties); if (ERROR_SUCCESS != status) { if (ERROR_ALREADY_EXISTS == status) { wprintf(L"The NT Kernel Logger session is already in use.\n"); } else { wprintf(L"EnableTrace() failed with %lu\n", status); } goto cleanup; } wprintf(L"Press any key to end trace session "); _getch();cleanup: if (SessionHandle) { status = ControlTrace(SessionHandle, KERNEL_LOGGER_NAME, pSessionProperties, EVENT_TRACE_CONTROL_STOP); if (ERROR_SUCCESS != status) { wprintf(L"ControlTrace(stop) failed with %lu\n", status); } } if (pSessionProperties) free(pSessionProperties);}
用于分析etl文件的代码,参考和研究。
//Turns the DEFINE_GUID for EventTraceGuid into a const.#define INITGUID#include <windows.h>#include <stdio.h>#include <wbemidl.h>#include <wmistr.h>#include <evntrace.h>#include <tdh.h>#include <in6addr.h>#pragma comment(lib, "tdh.lib")#define LOGFILE_PATH L"C:\\Code\\etw\\V2EventTraceController\\mylogfile.etl"// Used to calculate CPU usageULONG g_TimerResolution = 0;// Used to determine if the session is a private session or kernel session.// You need to know this when accessing some members of the EVENT_TRACE.Header// member (for example, KernelTime or UserTime).BOOL g_bUserMode = FALSE;// Handle to the trace file that you opened.TRACEHANDLE g_hTrace = 0; // Prototypesvoid WINAPI ProcessEvent(PEVENT_RECORD pEvent);DWORD GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO & pInfo);PBYTE PrintProperties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, DWORD PointerSize, USHORT i, PBYTE pUserData, PBYTE pEndOfUserData);DWORD GetPropertyLength(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT PropertyLength);DWORD GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT ArraySize);DWORD GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, DWORD DecodingSource, PEVENT_MAP_INFO & pMapInfo);void RemoveTrailingSpace(PEVENT_MAP_INFO pMapInfo);void wmain(void){ TDHSTATUS status = ERROR_SUCCESS; EVENT_TRACE_LOGFILE trace; TRACE_LOGFILE_HEADER* pHeader = &trace.LogfileHeader; // Identify the log file from which you want to consume events // and the callbacks used to process the events and buffers. ZeroMemory(&trace, sizeof(EVENT_TRACE_LOGFILE)); trace.LogFileName = (LPWSTR) LOGFILE_PATH; trace.EventRecordCallback = (PEVENT_RECORD_CALLBACK) (ProcessEvent); trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; g_hTrace = OpenTrace(&trace); if (INVALID_PROCESSTRACE_HANDLE == g_hTrace) { wprintf(L"OpenTrace failed with %lu\n", GetLastError()); goto cleanup; } g_bUserMode = pHeader->LogFileMode & EVENT_TRACE_PRIVATE_LOGGER_MODE; if (pHeader->TimerResolution > 0) { g_TimerResolution = pHeader->TimerResolution / 10000; } wprintf(L"Number of events lost: %lu\n", pHeader->EventsLost); // Use pHeader to access all fields prior to LoggerName. // Adjust pHeader based on the pointer size to access // all fields after LogFileName. This is required only if // you are consuming events on an architecture that is // different from architecture used to write the events. if (pHeader->PointerSize != sizeof(PVOID)) { pHeader = (PTRACE_LOGFILE_HEADER)((PUCHAR)pHeader + 2 * (pHeader->PointerSize - sizeof(PVOID))); } wprintf(L"Number of buffers lost: %lu\n\n", pHeader->BuffersLost); status = ProcessTrace(&g_hTrace, 1, 0, 0); if (status != ERROR_SUCCESS && status != ERROR_CANCELLED) { wprintf(L"ProcessTrace failed with %lu\n", status); goto cleanup; }cleanup: if (INVALID_PROCESSTRACE_HANDLE != g_hTrace) { status = CloseTrace(g_hTrace); }}// Callback that receives the events. VOID WINAPI ProcessEvent(PEVENT_RECORD pEvent){ DWORD status = ERROR_SUCCESS; PTRACE_EVENT_INFO pInfo = NULL; LPWSTR pwsEventGuid = NULL; PBYTE pUserData = NULL; PBYTE pEndOfUserData = NULL; DWORD PointerSize = 0; ULONGLONG TimeStamp = 0; ULONGLONG Nanoseconds = 0; SYSTEMTIME st; SYSTEMTIME stLocal; FILETIME ft; // Skips the event if it is the event trace header. Log files contain this event // but real-time sessions do not. The event contains the same information as // the EVENT_TRACE_LOGFILE.LogfileHeader member that you can access when you open // the trace. if (IsEqualGUID(pEvent->EventHeader.ProviderId, EventTraceGuid) && pEvent->EventHeader.EventDescriptor.Opcode == EVENT_TRACE_TYPE_INFO) { ; // Skip this event. } else { // Process the event. The pEvent->UserData member is a pointer to // the event specific data, if it exists. status = GetEventInformation(pEvent, pInfo); if (ERROR_SUCCESS != status) { wprintf(L"GetEventInformation failed with %lu\n", status); goto cleanup; } // Determine whether the event is defined by a MOF class, in an // instrumentation manifest, or a WPP template; to use TDH to decode // the event, it must be defined by one of these three sources. if (DecodingSourceWbem == pInfo->DecodingSource) // MOF class { HRESULT hr = StringFromCLSID(pInfo->EventGuid, &pwsEventGuid); if (FAILED(hr)) { wprintf(L"StringFromCLSID failed with 0x%x\n", hr); status = hr; goto cleanup; } wprintf(L"\nEvent GUID: %s\n", pwsEventGuid); CoTaskMemFree(pwsEventGuid); pwsEventGuid = NULL; wprintf(L"Event version: %d\n", pEvent->EventHeader.EventDescriptor.Version); wprintf(L"Event type: %d\n", pEvent->EventHeader.EventDescriptor.Opcode); } else if (DecodingSourceXMLFile == pInfo->DecodingSource) // Instrumentation manifest { wprintf(L"Event ID: %d\n", pInfo->EventDescriptor.Id); } else // Not handling the WPP case { goto cleanup; } // Print the time stamp for when the event occurred. ft.dwHighDateTime = pEvent->EventHeader.TimeStamp.HighPart; ft.dwLowDateTime = pEvent->EventHeader.TimeStamp.LowPart; FileTimeToSystemTime(&ft, &st); SystemTimeToTzSpecificLocalTime(NULL, &st, &stLocal); TimeStamp = pEvent->EventHeader.TimeStamp.QuadPart; Nanoseconds = (TimeStamp % 10000000) * 100; wprintf(L"%02d/%02d/%02d %02d:%02d:%02d.%I64u\n", stLocal.wMonth, stLocal.wDay, stLocal.wYear, stLocal.wHour, stLocal.wMinute, stLocal.wSecond, Nanoseconds); // If the event contains event-specific data use TDH to extract // the event data. For this example, to extract the data, the event // must be defined by a MOF class or an instrumentation manifest. // Need to get the PointerSize for each event to cover the case where you are // consuming events from multiple log files that could have been generated on // different architectures. Otherwise, you could have accessed the pointer // size when you opened the trace above (see pHeader->PointerSize). if (EVENT_HEADER_FLAG_32_BIT_HEADER == (pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) { PointerSize = 4; } else { PointerSize = 8; } pUserData = (PBYTE)pEvent->UserData; pEndOfUserData = (PBYTE)pEvent->UserData + pEvent->UserDataLength; // Print the event data for all the top-level properties. Metadata for all the // top-level properties come before structure member properties in the // property information array. for (USHORT i = 0; i < pInfo->TopLevelPropertyCount; i++) { pUserData = PrintProperties(pEvent, pInfo, PointerSize, i, pUserData, pEndOfUserData); if (NULL == pUserData) { wprintf(L"Printing top level properties failed.\n"); goto cleanup; } } }cleanup: if (pInfo) { free(pInfo); } if (ERROR_SUCCESS != status || NULL == pUserData) { CloseTrace(g_hTrace); }}// Print the property.PBYTE PrintProperties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, DWORD PointerSize, USHORT i, PBYTE pUserData, PBYTE pEndOfUserData){ TDHSTATUS status = ERROR_SUCCESS; USHORT PropertyLength = 0; DWORD FormattedDataSize = 0; USHORT UserDataConsumed = 0; USHORT UserDataLength = 0; LPWSTR pFormattedData = NULL; DWORD LastMember = 0; // Last member of a structure USHORT ArraySize = 0; PEVENT_MAP_INFO pMapInfo = NULL; // Get the length of the property. status = GetPropertyLength(pEvent, pInfo, i, &PropertyLength); if (ERROR_SUCCESS != status) { wprintf(L"GetPropertyLength failed.\n"); pUserData = NULL; goto cleanup; } // Get the size of the array if the property is an array. status = GetArraySize(pEvent, pInfo, i, &ArraySize); for (USHORT k = 0; k < ArraySize; k++) { // If the property is a structure, print the members of the structure. if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) { LastMember = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex + pInfo->EventPropertyInfoArray[i].structType.NumOfStructMembers; for (USHORT j = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex; j < LastMember; j++) { pUserData = PrintProperties(pEvent, pInfo, PointerSize, j, pUserData, pEndOfUserData); if (NULL == pUserData) { wprintf(L"Printing the members of the structure failed.\n"); pUserData = NULL; goto cleanup; } } } else { // Get the name/value mapping if the property specifies a value map. status = GetMapInfo(pEvent, (PWCHAR)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset), pInfo->DecodingSource, pMapInfo); if (ERROR_SUCCESS != status) { wprintf(L"GetMapInfo failed\n"); pUserData = NULL; goto cleanup; } // Get the size of the buffer required for the formatted data. status = TdhFormatProperty( pInfo, pMapInfo, PointerSize, pInfo->EventPropertyInfoArray[i].nonStructType.InType, pInfo->EventPropertyInfoArray[i].nonStructType.OutType, PropertyLength, (USHORT)(pEndOfUserData - pUserData), pUserData, &FormattedDataSize, pFormattedData, &UserDataConsumed); if (ERROR_INSUFFICIENT_BUFFER == status) { if (pFormattedData) { free(pFormattedData); pFormattedData = NULL; } pFormattedData = (LPWSTR) malloc(FormattedDataSize); if (pFormattedData == NULL) { wprintf(L"Failed to allocate memory for formatted data (size=%lu).\n", FormattedDataSize); status = ERROR_OUTOFMEMORY; pUserData = NULL; goto cleanup; } // Retrieve the formatted data. status = TdhFormatProperty( pInfo, pMapInfo, PointerSize, pInfo->EventPropertyInfoArray[i].nonStructType.InType, pInfo->EventPropertyInfoArray[i].nonStructType.OutType, PropertyLength, (USHORT)(pEndOfUserData - pUserData), pUserData, &FormattedDataSize, pFormattedData, &UserDataConsumed); } if (ERROR_SUCCESS == status) { wprintf(L"%s: %s\n", (PWCHAR)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset), pFormattedData); pUserData += UserDataConsumed; } else { wprintf(L"TdhFormatProperty failed with %lu.\n", status); pUserData = NULL; goto cleanup; } } }cleanup: if (pFormattedData) { free(pFormattedData); pFormattedData = NULL; } if (pMapInfo) { free(pMapInfo); pMapInfo = NULL; } return pUserData;}// Get the length of the property data. For MOF-based events, the size is inferred from the data type// of the property. For manifest-based events, the property can specify the size of the property value// using the length attribute. The length attribue can specify the size directly or specify the name // of another property in the event data that contains the size. If the property does not include the // length attribute, the size is inferred from the data type. The length will be zero for variable// length, null-terminated strings and structures.DWORD GetPropertyLength(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT PropertyLength){ DWORD status = ERROR_SUCCESS; PROPERTY_DATA_DESCRIPTOR DataDescriptor; DWORD PropertySize = 0; // If the property is a binary blob and is defined in a manifest, the property can // specify the blob's size or it can point to another property that defines the // blob's size. The PropertyParamLength flag tells you where the blob's size is defined. if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamLength) == PropertyParamLength) { DWORD Length = 0; // Expects the length to be defined by a UINT16 or UINT32 DWORD j = pInfo->EventPropertyInfoArray[i].lengthPropertyIndex; ZeroMemory(&DataDescriptor, sizeof(PROPERTY_DATA_DESCRIPTOR)); DataDescriptor.PropertyName = (ULONGLONG)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[j].NameOffset); DataDescriptor.ArrayIndex = ULONG_MAX; status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize); status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Length); *PropertyLength = (USHORT)Length; } else { if (pInfo->EventPropertyInfoArray[i].length > 0) { *PropertyLength = pInfo->EventPropertyInfoArray[i].length; } else { // If the property is a binary blob and is defined in a MOF class, the extension // qualifier is used to determine the size of the blob. However, if the extension // is IPAddrV6, you must set the PropertyLength variable yourself because the // EVENT_PROPERTY_INFO.length field will be zero. if (TDH_INTYPE_BINARY == pInfo->EventPropertyInfoArray[i].nonStructType.InType && TDH_OUTTYPE_IPV6 == pInfo->EventPropertyInfoArray[i].nonStructType.OutType) { *PropertyLength = (USHORT)sizeof(IN6_ADDR); } else if (TDH_INTYPE_UNICODESTRING == pInfo->EventPropertyInfoArray[i].nonStructType.InType || TDH_INTYPE_ANSISTRING == pInfo->EventPropertyInfoArray[i].nonStructType.InType || (pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) { *PropertyLength = pInfo->EventPropertyInfoArray[i].length; } else { wprintf(L"Unexpected length of 0 for intype %d and outtype %d\n", pInfo->EventPropertyInfoArray[i].nonStructType.InType, pInfo->EventPropertyInfoArray[i].nonStructType.OutType); status = ERROR_EVT_INVALID_EVENT_DATA; goto cleanup; } } }cleanup: return status;}// Get the size of the array. For MOF-based events, the size is specified in the declaration or using // the MAX qualifier. For manifest-based events, the property can specify the size of the array// using the count attribute. The count attribue can specify the size directly or specify the name // of another property in the event data that contains the size.DWORD GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT ArraySize){ DWORD status = ERROR_SUCCESS; PROPERTY_DATA_DESCRIPTOR DataDescriptor; DWORD PropertySize = 0; if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamCount) == PropertyParamCount) { DWORD Count = 0; // Expects the count to be defined by a UINT16 or UINT32 DWORD j = pInfo->EventPropertyInfoArray[i].countPropertyIndex; ZeroMemory(&DataDescriptor, sizeof(PROPERTY_DATA_DESCRIPTOR)); DataDescriptor.PropertyName = (ULONGLONG)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[j].NameOffset); DataDescriptor.ArrayIndex = ULONG_MAX; status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize); status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Count); *ArraySize = (USHORT)Count; } else { *ArraySize = pInfo->EventPropertyInfoArray[i].count; } return status;}// Both MOF-based events and manifest-based events can specify name/value maps. The// map values can be integer values or bit values. If the property specifies a value// map, get the map.DWORD GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, DWORD DecodingSource, PEVENT_MAP_INFO & pMapInfo){ DWORD status = ERROR_SUCCESS; DWORD MapSize = 0; // Retrieve the required buffer size for the map info. status = TdhGetEventMapInformation(pEvent, pMapName, pMapInfo, &MapSize); if (ERROR_INSUFFICIENT_BUFFER == status) { pMapInfo = (PEVENT_MAP_INFO) malloc(MapSize); if (pMapInfo == NULL) { wprintf(L"Failed to allocate memory for map info (size=%lu).\n", MapSize); status = ERROR_OUTOFMEMORY; goto cleanup; } // Retrieve the map info. status = TdhGetEventMapInformation(pEvent, pMapName, pMapInfo, &MapSize); } if (ERROR_SUCCESS == status) { if (DecodingSourceXMLFile == DecodingSource) { RemoveTrailingSpace(pMapInfo); } } else { if (ERROR_NOT_FOUND == status) { status = ERROR_SUCCESS; // This case is okay. } else { wprintf(L"TdhGetEventMapInformation failed with 0x%x.\n", status); } }cleanup: return status;}// The mapped string values defined in a manifest will contain a trailing space// in the EVENT_MAP_ENTRY structure. Replace the trailing space with a null-// terminating character, so that the bit mapped strings are correctly formatted.void RemoveTrailingSpace(PEVENT_MAP_INFO pMapInfo){ DWORD ByteLength = 0; for (DWORD i = 0; i < pMapInfo->EntryCount; i++) { ByteLength = (wcslen((LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset)) - 1) * 2; *((LPWSTR)((PBYTE)pMapInfo + (pMapInfo->MapEntryArray[i].OutputOffset + ByteLength))) = L'\0'; }}// Get the metadata for the event.DWORD GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO & pInfo){ DWORD status = ERROR_SUCCESS; DWORD BufferSize = 0; // Retrieve the required buffer size for the event metadata. status = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &BufferSize); if (ERROR_INSUFFICIENT_BUFFER == status) { pInfo = (TRACE_EVENT_INFO*) malloc(BufferSize); if (pInfo == NULL) { wprintf(L"Failed to allocate memory for event info (size=%lu).\n", BufferSize); status = ERROR_OUTOFMEMORY; goto cleanup; } // Retrieve the event metadata. status = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &BufferSize); } if (ERROR_SUCCESS != status) { wprintf(L"TdhGetEventInformation failed with 0x%x.\n", status); }cleanup: return status;}
同上,分析etl文件的,这两个文件,感觉都是转化为xml对etl进行分析的。
//Turns the DEFINE_GUID for EventTraceGuid into a const.#define INITGUID#include <windows.h>#include <stdio.h>#include <strsafe.h>#include <wbemidl.h>#include <wmistr.h>#include <evntrace.h>#include <tdh.h>#include <in6addr.h>#pragma comment(lib, "tdh.lib")#pragma comment(lib, "ws2_32.lib") // For ntohs function#define LOGFILE_PATH L"C:\\Code\\etw\\V2EventTraceController\\mylogfile.etl"#define MAX_NAME 256// Used to determine the data size of property values that contain a// Pointer value. The value will be 4 or 8.USHORT g_PointerSize = 0;// Used to calculate CPU usageULONG g_TimerResolution = 0;// Used to determine if the session is a private session or kernel session.// You need to know this when accessing some members of the EVENT_TRACE.Header// member (for example, KernelTime or UserTime).BOOL g_bUserMode = FALSE;// Handle to the trace file that you opened.TRACEHANDLE g_hTrace = 0; // Prototypesvoid WINAPI ProcessEvent(PEVENT_RECORD pEvent);DWORD GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO & pInfo);DWORD PrintProperties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, LPWSTR pStructureName, USHORT StructIndex);DWORD FormatAndPrintData(PEVENT_RECORD pEvent, USHORT InType, USHORT OutType, PBYTE pData, DWORD DataSize, PEVENT_MAP_INFO pMapInfo); void PrintMapString(PEVENT_MAP_INFO pMapInfo, PBYTE pData);DWORD GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT ArraySize);DWORD GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, DWORD DecodingSource, PEVENT_MAP_INFO & pMapInfo);void RemoveTrailingSpace(PEVENT_MAP_INFO pMapInfo);typedef LPTSTR (NTAPI *PIPV6ADDRTOSTRING)( const IN6_ADDR *Addr, LPTSTR S);void wmain(void){ ULONG status = ERROR_SUCCESS; EVENT_TRACE_LOGFILE trace; TRACE_LOGFILE_HEADER* pHeader = &trace.LogfileHeader; // Identify the log file from which you want to consume events // and the callbacks used to process the events and buffers. ZeroMemory(&trace, sizeof(EVENT_TRACE_LOGFILE)); trace.LogFileName = (LPWSTR) LOGFILE_PATH; trace.EventRecordCallback = (PEVENT_RECORD_CALLBACK) (ProcessEvent); trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; g_hTrace = OpenTrace(&trace); if (INVALID_PROCESSTRACE_HANDLE == g_hTrace) { wprintf(L"OpenTrace failed with %lu\n", GetLastError()); goto cleanup; } g_bUserMode = pHeader->LogFileMode & EVENT_TRACE_PRIVATE_LOGGER_MODE; if (pHeader->TimerResolution > 0) { g_TimerResolution = pHeader->TimerResolution / 10000; } wprintf(L"Number of events lost: %lu\n", pHeader->EventsLost); // Use pHeader to access all fields prior to LoggerName. // Adjust pHeader based on the pointer size to access // all fields after LogFileName. This is required only if // you are consuming events on an architecture that is // different from architecture used to write the events. if (pHeader->PointerSize != sizeof(PVOID)) { pHeader = (PTRACE_LOGFILE_HEADER)((PUCHAR)pHeader + 2 * (pHeader->PointerSize - sizeof(PVOID))); } wprintf(L"Number of buffers lost: %lu\n\n", pHeader->BuffersLost); status = ProcessTrace(&g_hTrace, 1, 0, 0); if (status != ERROR_SUCCESS && status != ERROR_CANCELLED) { wprintf(L"ProcessTrace failed with %lu\n", status); goto cleanup; }cleanup: if (INVALID_PROCESSTRACE_HANDLE != g_hTrace) { status = CloseTrace(g_hTrace); }}// Callback that receives the events. VOID WINAPI ProcessEvent(PEVENT_RECORD pEvent){ DWORD status = ERROR_SUCCESS; PTRACE_EVENT_INFO pInfo = NULL; LPWSTR pwsEventGuid = NULL; ULONGLONG TimeStamp = 0; ULONGLONG Nanoseconds = 0; SYSTEMTIME st; SYSTEMTIME stLocal; FILETIME ft; // Skips the event if it is the event trace header. Log files contain this event // but real-time sessions do not. The event contains the same information as // the EVENT_TRACE_LOGFILE.LogfileHeader member that you can access when you open // the trace. if (IsEqualGUID(pEvent->EventHeader.ProviderId, EventTraceGuid) && pEvent->EventHeader.EventDescriptor.Opcode == EVENT_TRACE_TYPE_INFO) { ; // Skip this event. } else { // Process the event. The pEvent->UserData member is a pointer to // the event specific data, if it exists. status = GetEventInformation(pEvent, pInfo); if (ERROR_SUCCESS != status) { wprintf(L"GetEventInformation failed with %lu\n", status); goto cleanup; } // Determine whether the event is defined by a MOF class, in an // instrumentation manifest, or a WPP template; to use TDH to decode // the event, it must be defined by one of these three sources. if (DecodingSourceWbem == pInfo->DecodingSource) // MOF class { HRESULT hr = StringFromCLSID(pInfo->EventGuid, &pwsEventGuid); if (FAILED(hr)) { wprintf(L"StringFromCLSID failed with 0x%x\n", hr); status = hr; goto cleanup; } wprintf(L"\nEvent GUID: %s\n", pwsEventGuid); CoTaskMemFree(pwsEventGuid); pwsEventGuid = NULL; wprintf(L"Event version: %d\n", pEvent->EventHeader.EventDescriptor.Version); wprintf(L"Event type: %d\n", pEvent->EventHeader.EventDescriptor.Opcode); } else if (DecodingSourceXMLFile == pInfo->DecodingSource) // Instrumentation manifest { wprintf(L"Event ID: %d\n", pInfo->EventDescriptor.Id); } else // Not handling the WPP case { goto cleanup; } // Print the time stamp for when the event occurred. ft.dwHighDateTime = pEvent->EventHeader.TimeStamp.HighPart; ft.dwLowDateTime = pEvent->EventHeader.TimeStamp.LowPart; FileTimeToSystemTime(&ft, &st); SystemTimeToTzSpecificLocalTime(NULL, &st, &stLocal); TimeStamp = pEvent->EventHeader.TimeStamp.QuadPart; Nanoseconds = (TimeStamp % 10000000) * 100; wprintf(L"%02d/%02d/%02d %02d:%02d:%02d.%I64u\n", stLocal.wMonth, stLocal.wDay, stLocal.wYear, stLocal.wHour, stLocal.wMinute, stLocal.wSecond, Nanoseconds); // If the event contains event-specific data use TDH to extract // the event data. For this example, to extract the data, the event // must be defined by a MOF class or an instrumentation manifest. // Need to get the PointerSize for each event to cover the case where you are // consuming events from multiple log files that could have been generated on // different architectures. Otherwise, you could have accessed the pointer // size when you opened the trace above (see pHeader->PointerSize). if (EVENT_HEADER_FLAG_32_BIT_HEADER == (pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) { g_PointerSize = 4; } else { g_PointerSize = 8; } // Print the event data for all the top-level properties. Metadata for all the // top-level properties come before structure member properties in the // property information array. If the EVENT_HEADER_FLAG_STRING_ONLY flag is set, // the event data is a null-terminated string, so just print it. if (EVENT_HEADER_FLAG_STRING_ONLY == (pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_STRING_ONLY)) { wprintf(L"%s\n", (LPWSTR)pEvent->UserData); } else { for (USHORT i = 0; i < pInfo->TopLevelPropertyCount; i++) { status = PrintProperties(pEvent, pInfo, i, NULL, 0); if (ERROR_SUCCESS != status) { wprintf(L"Printing top level properties failed.\n"); goto cleanup; } } } }cleanup: if (pInfo) { free(pInfo); } if (ERROR_SUCCESS != status) { CloseTrace(g_hTrace); }}// Print the property.DWORD PrintProperties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, LPWSTR pStructureName, USHORT StructIndex){ DWORD status = ERROR_SUCCESS; DWORD LastMember = 0; // Last member of a structure USHORT ArraySize = 0; PEVENT_MAP_INFO pMapInfo = NULL; PROPERTY_DATA_DESCRIPTOR DataDescriptors[2]; ULONG DescriptorsCount = 0; DWORD PropertySize = 0; PBYTE pData = NULL; // Get the size of the array if the property is an array. status = GetArraySize(pEvent, pInfo, i, &ArraySize); for (USHORT k = 0; k < ArraySize; k++) { wprintf(L"%*s%s: ", (pStructureName) ? 4 : 0, L"", (LPWSTR)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset)); // If the property is a structure, print the members of the structure. if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) { wprintf(L"\n"); LastMember = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex + pInfo->EventPropertyInfoArray[i].structType.NumOfStructMembers; for (USHORT j = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex; j < LastMember; j++) { status = PrintProperties(pEvent, pInfo, j, (LPWSTR)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset), k); if (ERROR_SUCCESS != status) { wprintf(L"Printing the members of the structure failed.\n"); goto cleanup; } } } else { ZeroMemory(&DataDescriptors, sizeof(DataDescriptors)); // To retrieve a member of a structure, you need to specify an array of descriptors. // The first descriptor in the array identifies the name of the structure and the second // descriptor defines the member of the structure whose data you want to retrieve. if (pStructureName) { DataDescriptors[0].PropertyName = (ULONGLONG)pStructureName; DataDescriptors[0].ArrayIndex = StructIndex; DataDescriptors[1].PropertyName = (ULONGLONG)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset); DataDescriptors[1].ArrayIndex = k; DescriptorsCount = 2; } else { DataDescriptors[0].PropertyName = (ULONGLONG)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset); DataDescriptors[0].ArrayIndex = k; DescriptorsCount = 1; } // The TDH API does not support IPv6 addresses. If the output type is TDH_OUTTYPE_IPV6, // you will not be able to consume the rest of the event. If you try to consume the // remainder of the event, you will get ERROR_EVT_INVALID_EVENT_DATA. if (TDH_INTYPE_BINARY == pInfo->EventPropertyInfoArray[i].nonStructType.InType && TDH_OUTTYPE_IPV6 == pInfo->EventPropertyInfoArray[i].nonStructType.OutType) { wprintf(L"The event contains an IPv6 address. Skipping event.\n"); status = ERROR_EVT_INVALID_EVENT_DATA; break; } else { status = TdhGetPropertySize(pEvent, 0, NULL, DescriptorsCount, &DataDescriptors[0], &PropertySize); if (ERROR_SUCCESS != status) { wprintf(L"TdhGetPropertySize failed with %lu\n", status); goto cleanup; } pData = (PBYTE)malloc(PropertySize); if (NULL == pData) { wprintf(L"Failed to allocate memory for property data\n"); status = ERROR_OUTOFMEMORY; goto cleanup; } status = TdhGetProperty(pEvent, 0, NULL, DescriptorsCount, &DataDescriptors[0], PropertySize, pData); // Get the name/value mapping if the property specifies a value map. status = GetMapInfo(pEvent, (PWCHAR)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset), pInfo->DecodingSource, pMapInfo); if (ERROR_SUCCESS != status) { wprintf(L"GetMapInfo failed\n"); goto cleanup; } status = FormatAndPrintData(pEvent, pInfo->EventPropertyInfoArray[i].nonStructType.InType, pInfo->EventPropertyInfoArray[i].nonStructType.OutType, pData, PropertySize, pMapInfo ); if (ERROR_SUCCESS != status) { wprintf(L"GetMapInfo failed\n"); goto cleanup; } if (pData) { free(pData); pData = NULL; } if (pMapInfo) { free(pMapInfo); pMapInfo = NULL; } } } }cleanup: if (pData) { free(pData); pData = NULL; } if (pMapInfo) { free(pMapInfo); pMapInfo = NULL; } return status;}DWORD FormatAndPrintData(PEVENT_RECORD pEvent, USHORT InType, USHORT OutType, PBYTE pData, DWORD DataSize, PEVENT_MAP_INFO pMapInfo){ UNREFERENCED_PARAMETER(pEvent); DWORD status = ERROR_SUCCESS; switch (InType) { case TDH_INTYPE_UNICODESTRING: case TDH_INTYPE_COUNTEDSTRING: case TDH_INTYPE_REVERSEDCOUNTEDSTRING: case TDH_INTYPE_NONNULLTERMINATEDSTRING: { size_t StringLength = 0; if (TDH_INTYPE_COUNTEDSTRING == InType) { StringLength = *(PUSHORT)pData; } else if (TDH_INTYPE_REVERSEDCOUNTEDSTRING == InType) { StringLength = MAKEWORD(HIBYTE((PUSHORT)pData), LOBYTE((PUSHORT)pData)); } else if (TDH_INTYPE_NONNULLTERMINATEDSTRING == InType) { StringLength = DataSize; } else { StringLength = wcslen((LPWSTR)pData); } wprintf(L"%.*s\n", StringLength, (LPWSTR)pData); break; } case TDH_INTYPE_ANSISTRING: case TDH_INTYPE_COUNTEDANSISTRING: case TDH_INTYPE_REVERSEDCOUNTEDANSISTRING: case TDH_INTYPE_NONNULLTERMINATEDANSISTRING: { size_t StringLength = 0; if (TDH_INTYPE_COUNTEDANSISTRING == InType) { StringLength = *(PUSHORT)pData; } else if (TDH_INTYPE_REVERSEDCOUNTEDANSISTRING == InType) { StringLength = MAKEWORD(HIBYTE((PUSHORT)pData), LOBYTE((PUSHORT)pData)); } else if (TDH_INTYPE_NONNULLTERMINATEDANSISTRING == InType) { StringLength = DataSize; } else { StringLength = strlen((LPSTR)pData); } wprintf(L"%.*S\n", StringLength, (LPSTR)pData); break; } case TDH_INTYPE_INT8: { wprintf(L"%hd\n", *(PCHAR)pData); break; } case TDH_INTYPE_UINT8: { if (TDH_OUTTYPE_HEXINT8 == OutType) { wprintf(L"0x%x\n", *(PBYTE)pData); } else { wprintf(L"%hu\n", *(PBYTE)pData); } break; } case TDH_INTYPE_INT16: { wprintf(L"%hd\n", *(PSHORT)pData); break; } case TDH_INTYPE_UINT16: { if (TDH_OUTTYPE_HEXINT16 == OutType) { wprintf(L"0x%x\n", *(PUSHORT)pData); } else if (TDH_OUTTYPE_PORT == OutType) { wprintf(L"%hu\n", ntohs(*(PUSHORT)pData)); } else { wprintf(L"%hu\n", *(PUSHORT)pData); } break; } case TDH_INTYPE_INT32: { if (TDH_OUTTYPE_HRESULT == OutType) { wprintf(L"0x%x\n", *(PLONG)pData); } else { wprintf(L"%d\n", *(PLONG)pData); } break; } case TDH_INTYPE_UINT32: { if (TDH_OUTTYPE_HRESULT == OutType || TDH_OUTTYPE_WIN32ERROR == OutType || TDH_OUTTYPE_NTSTATUS == OutType || TDH_OUTTYPE_HEXINT32 == OutType) { wprintf(L"0x%x\n", *(PULONG)pData); } else if (TDH_OUTTYPE_IPV4 == OutType) { wprintf(L"%d.%d.%d.%d\n", (*(PLONG)pData >> 0) & 0xff, (*(PLONG)pData >> 8) & 0xff, (*(PLONG)pData >> 16) & 0xff, (*(PLONG)pData >> 24) & 0xff); } else { if (pMapInfo) { PrintMapString(pMapInfo, pData); } else { wprintf(L"%lu\n", *(PULONG)pData); } } break; } case TDH_INTYPE_INT64: { wprintf(L"%I64d\n", *(PLONGLONG)pData); break; } case TDH_INTYPE_UINT64: { if (TDH_OUTTYPE_HEXINT64 == OutType) { wprintf(L"0x%x\n", *(PULONGLONG)pData); } else { wprintf(L"%I64u\n", *(PULONGLONG)pData); } break; } case TDH_INTYPE_FLOAT: { wprintf(L"%f\n", *(PFLOAT)pData); break; } case TDH_INTYPE_DOUBLE: { wprintf(L"%I64f\n", *(DOUBLE*)pData); break; } case TDH_INTYPE_BOOLEAN: { wprintf(L"%s\n", (0 == (PBOOL)pData) ? L"false" : L"true"); break; } case TDH_INTYPE_BINARY: { if (TDH_OUTTYPE_IPV6 == OutType) { WCHAR IPv6AddressAsString[46]; PIPV6ADDRTOSTRING fnRtlIpv6AddressToString; fnRtlIpv6AddressToString = (PIPV6ADDRTOSTRING)GetProcAddress( GetModuleHandle(L"ntdll"), "RtlIpv6AddressToStringW"); if (NULL == fnRtlIpv6AddressToString) { wprintf(L"GetProcAddress failed with %lu.\n", status = GetLastError()); goto cleanup; } fnRtlIpv6AddressToString((IN6_ADDR*)pData, IPv6AddressAsString); wprintf(L"%s\n", IPv6AddressAsString); } else { for (DWORD i = 0; i < DataSize; i++) { wprintf(L"%.2x", pData[i]); } wprintf(L"\n"); } break; } case TDH_INTYPE_GUID: { WCHAR szGuid[50]; StringFromGUID2(*(GUID*)pData, szGuid, sizeof(szGuid)-1); wprintf(L"%s\n", szGuid); break; } case TDH_INTYPE_POINTER: case TDH_INTYPE_SIZET: { if (4 == g_PointerSize) { wprintf(L"0x%x\n", *(PULONG)pData); } else { wprintf(L"0x%x\n", *(PULONGLONG)pData); } break; } case TDH_INTYPE_FILETIME: { break; } case TDH_INTYPE_SYSTEMTIME: { break; } case TDH_INTYPE_SID: { WCHAR UserName[MAX_NAME]; WCHAR DomainName[MAX_NAME]; DWORD cchUserSize = MAX_NAME; DWORD cchDomainSize = MAX_NAME; SID_NAME_USE eNameUse; if (!LookupAccountSid(NULL, (PSID)pData, UserName, &cchUserSize, DomainName, &cchDomainSize, &eNameUse)) { if (ERROR_NONE_MAPPED == status) { wprintf(L"Unable to locate account for the specified SID\n"); status = ERROR_SUCCESS; } else { wprintf(L"LookupAccountSid failed with %lu\n", status = GetLastError()); } goto cleanup; } else { wprintf(L"%s\\%s\n", DomainName, UserName); } break; } case TDH_INTYPE_HEXINT32: { wprintf(L"0x%x\n", (PULONG)pData); break; } case TDH_INTYPE_HEXINT64: { wprintf(L"0x%x\n", (PULONGLONG)pData); break; } case TDH_INTYPE_UNICODECHAR: { wprintf(L"%c\n", *(PWCHAR)pData); break; } case TDH_INTYPE_ANSICHAR: { wprintf(L"%C\n", *(PCHAR)pData); break; } case TDH_INTYPE_WBEMSID: { WCHAR UserName[MAX_NAME]; WCHAR DomainName[MAX_NAME]; DWORD cchUserSize = MAX_NAME; DWORD cchDomainSize = MAX_NAME; SID_NAME_USE eNameUse; if ((PULONG)pData > 0) { // A WBEM SID is actually a TOKEN_USER structure followed // by the SID. The size of the TOKEN_USER structure differs // depending on whether the events were generated on a 32-bit // or 64-bit architecture. Also the structure is aligned // on an 8-byte boundary, so its size is 8 bytes on a // 32-bit computer and 16 bytes on a 64-bit computer. // Doubling the pointer size handles both cases. pData += g_PointerSize * 2; if (!LookupAccountSid(NULL, (PSID)pData, UserName, &cchUserSize, DomainName, &cchDomainSize, &eNameUse)) { if (ERROR_NONE_MAPPED == status) { wprintf(L"Unable to locate account for the specified SID\n"); status = ERROR_SUCCESS; } else { wprintf(L"LookupAccountSid failed with %lu\n", status = GetLastError()); } goto cleanup; } else { wprintf(L"%s\\%s\n", DomainName, UserName); } } break; } default: status = ERROR_NOT_FOUND; }cleanup: return status;}void PrintMapString(PEVENT_MAP_INFO pMapInfo, PBYTE pData){ BOOL MatchFound = FALSE; if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP) == EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP || ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_VALUEMAP) == EVENTMAP_INFO_FLAG_WBEM_VALUEMAP && (pMapInfo->Flag & (~EVENTMAP_INFO_FLAG_WBEM_VALUEMAP)) != EVENTMAP_INFO_FLAG_WBEM_FLAG)) { if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_NO_MAP) == EVENTMAP_INFO_FLAG_WBEM_NO_MAP) { wprintf(L"%s\n", (LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[*(PULONG)pData].OutputOffset)); } else { for (DWORD i = 0; i < pMapInfo->EntryCount; i++) { if (pMapInfo->MapEntryArray[i].Value == *(PULONG)pData) { wprintf(L"%s\n", (LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset)); MatchFound = TRUE; break; } } if (FALSE == MatchFound) { wprintf(L"%lu\n", *(PULONG)pData); } } } else if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_MANIFEST_BITMAP) == EVENTMAP_INFO_FLAG_MANIFEST_BITMAP || (pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_BITMAP) == EVENTMAP_INFO_FLAG_WBEM_BITMAP || ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_VALUEMAP) == EVENTMAP_INFO_FLAG_WBEM_VALUEMAP && (pMapInfo->Flag & (~EVENTMAP_INFO_FLAG_WBEM_VALUEMAP)) == EVENTMAP_INFO_FLAG_WBEM_FLAG)) { if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_NO_MAP) == EVENTMAP_INFO_FLAG_WBEM_NO_MAP) { DWORD BitPosition = 0; for (DWORD i = 0; i < pMapInfo->EntryCount; i++) { if ((*(PULONG)pData & (BitPosition = (1 << i))) == BitPosition) { wprintf(L"%s%s", (MatchFound) ? L" | " : L"", (LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset)); MatchFound = TRUE; } } } else { for (DWORD i = 0; i < pMapInfo->EntryCount; i++) { if ((pMapInfo->MapEntryArray[i].Value & *(PULONG)pData) == pMapInfo->MapEntryArray[i].Value) { wprintf(L"%s%s", (MatchFound) ? L" | " : L"", (LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset)); MatchFound = TRUE; } } } if (MatchFound) { wprintf(L"\n"); } else { wprintf(L"%lu\n", *(PULONG)pData); } }}// Get the size of the array. For MOF-based events, the size is specified in the declaration or using // the MAX qualifier. For manifest-based events, the property can specify the size of the array// using the count attribute. The count attribue can specify the size directly or specify the name // of another property in the event data that contains the size.DWORD GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT ArraySize){ DWORD status = ERROR_SUCCESS; PROPERTY_DATA_DESCRIPTOR DataDescriptor; DWORD PropertySize = 0; if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamCount) == PropertyParamCount) { DWORD Count = 0; // Expects the count to be defined by a UINT16 or UINT32 DWORD j = pInfo->EventPropertyInfoArray[i].countPropertyIndex; ZeroMemory(&DataDescriptor, sizeof(PROPERTY_DATA_DESCRIPTOR)); DataDescriptor.PropertyName = (ULONGLONG)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[j].NameOffset); DataDescriptor.ArrayIndex = ULONG_MAX; status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize); status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Count); *ArraySize = (USHORT)Count; } else { *ArraySize = pInfo->EventPropertyInfoArray[i].count; } return status;}// Both MOF-based events and manifest-based events can specify name/value maps. The// map values can be integer values or bit values. If the property specifies a value// map, get the map.DWORD GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, DWORD DecodingSource, PEVENT_MAP_INFO & pMapInfo){ DWORD status = ERROR_SUCCESS; DWORD MapSize = 0; // Retrieve the required buffer size for the map info. status = TdhGetEventMapInformation(pEvent, pMapName, pMapInfo, &MapSize); if (ERROR_INSUFFICIENT_BUFFER == status) { pMapInfo = (PEVENT_MAP_INFO) malloc(MapSize); if (pMapInfo == NULL) { wprintf(L"Failed to allocate memory for map info (size=%lu).\n", MapSize); status = ERROR_OUTOFMEMORY; goto cleanup; } // Retrieve the map info. status = TdhGetEventMapInformation(pEvent, pMapName, pMapInfo, &MapSize); } if (ERROR_SUCCESS == status) { if (DecodingSourceXMLFile == DecodingSource) { RemoveTrailingSpace(pMapInfo); } } else { if (ERROR_NOT_FOUND == status) { status = ERROR_SUCCESS; // This case is okay. } else { wprintf(L"TdhGetEventMapInformation failed with 0x%x.\n", status); } }cleanup: return status;}// The mapped string values defined in a manifest will contain a trailing space// in the EVENT_MAP_ENTRY structure. Replace the trailing space with a null-// terminating character, so that the bit mapped strings are correctly formatted.void RemoveTrailingSpace(PEVENT_MAP_INFO pMapInfo){ SIZE_T ByteLength = 0; for (DWORD i = 0; i < pMapInfo->EntryCount; i++) { ByteLength = (wcslen((LPWSTR)((PBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset)) - 1) * 2; *((LPWSTR)((PBYTE)pMapInfo + (pMapInfo->MapEntryArray[i].OutputOffset + ByteLength))) = L'\0'; }}// Get the metadata for the event.DWORD GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO & pInfo){ DWORD status = ERROR_SUCCESS; DWORD BufferSize = 0; // Retrieve the required buffer size for the event metadata. status = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &BufferSize); if (ERROR_INSUFFICIENT_BUFFER == status) { pInfo = (TRACE_EVENT_INFO*) malloc(BufferSize); if (pInfo == NULL) { wprintf(L"Failed to allocate memory for event info (size=%lu).\n", BufferSize); status = ERROR_OUTOFMEMORY; goto cleanup; } // Retrieve the event metadata. status = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &BufferSize); } if (ERROR_SUCCESS != status) { wprintf(L"TdhGetEventInformation failed with 0x%x.\n", status); }cleanup: return status;}
下面的代码是将etl中分析出的数据结构的属性列举并描述。(不清楚)
//Turns the DEFINE_GUID for EventTraceGuid into a const.#define INITGUID#include <windows.h>#include <stdio.h>#include <comdef.h>#include <guiddef.h>#include <wbemidl.h>#include <wmistr.h>#include <evntrace.h>#include <tdh.h>#pragma comment(lib, "tdh.lib")#define LOGFILE_PATH L"<FULLPATHTOTHELOGFILE.etl>"static const GUID GUID_NULL = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };// Strings that represent the source of the event metadata.WCHAR* pSource[] = {L"XML instrumentation manifest", L"WMI MOF class", L"WPP TMF file"};// Handle to the trace file that you opened.TRACEHANDLE g_hTrace = 0; // Prototypesvoid WINAPI ProcessEvent(PEVENT_RECORD pEvent);DWORD GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO & pInfo);DWORD PrintPropertyMetadata(TRACE_EVENT_INFO* pInfo, DWORD i, USHORT indent);void wmain(void){ ULONG status = ERROR_SUCCESS; EVENT_TRACE_LOGFILE trace; TRACE_LOGFILE_HEADER* pHeader = &trace.LogfileHeader; // Identify the log file from which you want to consume events // and the callbacks used to process the events and buffers. ZeroMemory(&trace, sizeof(EVENT_TRACE_LOGFILE)); trace.LogFileName = (LPWSTR) LOGFILE_PATH; trace.EventRecordCallback = (PEVENT_RECORD_CALLBACK) (ProcessEvent); trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; g_hTrace = OpenTrace(&trace); if (INVALID_PROCESSTRACE_HANDLE == g_hTrace) { wprintf(L"OpenTrace failed with %lu\n", GetLastError()); goto cleanup; } status = ProcessTrace(&g_hTrace, 1, 0, 0); if (status != ERROR_SUCCESS && status != ERROR_CANCELLED) { wprintf(L"ProcessTrace failed with %lu\n", status); goto cleanup; }cleanup: if (INVALID_PROCESSTRACE_HANDLE != g_hTrace) { status = CloseTrace(g_hTrace); }}VOID WINAPI ProcessEvent(PEVENT_RECORD pEvent){ DWORD status = ERROR_SUCCESS; HRESULT hr = S_OK; PTRACE_EVENT_INFO pInfo = NULL; LPWSTR pStringGuid = NULL; // Skips the event if it is the event trace header. Log files contain this event // but real-time sessions do not. The event contains the same information as // the EVENT_TRACE_LOGFILE.LogfileHeader member that you can access when you open // the trace. if (IsEqualGUID(pEvent->EventHeader.ProviderId, EventTraceGuid) && pEvent->EventHeader.EventDescriptor.Opcode == EVENT_TRACE_TYPE_INFO) { ; // Skip this event. } else { // Process the event. This example does not process the event data but // instead prints the metadata that describes each event. status = GetEventInformation(pEvent, pInfo); if (ERROR_SUCCESS != status) { wprintf(L"GetEventInformation failed with %lu\n", status); goto cleanup; } wprintf(L"Decoding source: %s\n", pSource[pInfo->DecodingSource]); if (DecodingSourceWPP == pInfo->DecodingSource) { // This example is not rendering WPP metadata. goto cleanup; } if (pInfo->ProviderNameOffset > 0) { wprintf(L"Provider name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->ProviderNameOffset)); } hr = StringFromCLSID(pInfo->ProviderGuid, &pStringGuid); if (FAILED(hr)) { wprintf(L"StringFromCLSID(ProviderGuid) failed with 0x%x\n", hr); status = hr; goto cleanup; } wprintf(L"\nProvider GUID: %s\n", pStringGuid); CoTaskMemFree(pStringGuid); pStringGuid = NULL; if (!IsEqualGUID(pInfo->EventGuid, GUID_NULL)) { hr = StringFromCLSID(pInfo->EventGuid, &pStringGuid); if (FAILED(hr)) { wprintf(L"StringFromCLSID(EventGuid) failed with 0x%x\n", hr); status = hr; goto cleanup; } wprintf(L"\nEvent GUID: %s\n", pStringGuid); CoTaskMemFree(pStringGuid); pStringGuid = NULL; } if (DecodingSourceXMLFile == pInfo->DecodingSource) { wprintf(L"Event ID: %hu\n", pInfo->EventDescriptor.Id); } wprintf(L"Version: %d\n", pInfo->EventDescriptor.Version); if (pInfo->ChannelNameOffset > 0) { wprintf(L"Channel name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->ChannelNameOffset)); } if (pInfo->LevelNameOffset > 0) { wprintf(L"Level name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->LevelNameOffset)); } else { wprintf(L"Level: %hu\n", pInfo->EventDescriptor.Level); } if (DecodingSourceXMLFile == pInfo->DecodingSource) { if (pInfo->OpcodeNameOffset > 0) { wprintf(L"Opcode name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->OpcodeNameOffset)); } } else { wprintf(L"Type: %hu\n", pInfo->EventDescriptor.Opcode); } if (DecodingSourceXMLFile == pInfo->DecodingSource) { if (pInfo->TaskNameOffset > 0) { wprintf(L"Task name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->TaskNameOffset)); } } else { wprintf(L"Task: %hu\n", pInfo->EventDescriptor.Task); } wprintf(L"Keyword mask: 0x%x\n", pInfo->EventDescriptor.Keyword); if (pInfo->KeywordsNameOffset) { LPWSTR pKeyword = (LPWSTR)((PBYTE)(pInfo) + pInfo->KeywordsNameOffset); for (; *pKeyword != 0; pKeyword += (wcslen(pKeyword) + 1)) wprintf(L" Keyword name: %s\n", pKeyword); } if (pInfo->EventMessageOffset > 0) { wprintf(L"Event message: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->EventMessageOffset)); } if (pInfo->ActivityIDNameOffset > 0) { wprintf(L"Activity ID name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->ActivityIDNameOffset)); } if (pInfo->RelatedActivityIDNameOffset > 0) { wprintf(L"Related activity ID name: %s\n", (LPWSTR)((PBYTE)(pInfo) + pInfo->RelatedActivityIDNameOffset)); } wprintf(L"Number of top-level properties: %lu\n", pInfo->TopLevelPropertyCount); wprintf(L"Total number of properties: %lu\n", pInfo->PropertyCount); // Print the metadata for all the top-level properties. Metadata for all the // top-level properties come before structure member properties in the // property information array. if (pInfo->TopLevelPropertyCount > 0) { wprintf(L"\nThe following are the user data properties defined for this event:\n"); for (USHORT i = 0; i < pInfo->TopLevelPropertyCount; i++) { status = PrintPropertyMetadata(pInfo, i, 0); if (ERROR_SUCCESS != status) { wprintf(L"Printing metadata for top-level properties failed.\n"); goto cleanup; } } } else { wprintf(L"\nThe event does not define any user data properties.\n"); } wprintf(L"\n"); }cleanup: if (pInfo) { free(pInfo); } if (ERROR_SUCCESS != status) { CloseTrace(g_hTrace); }}DWORD GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO & pInfo){ DWORD status = ERROR_SUCCESS; DWORD BufferSize = 0; // Retrieve the required buffer size for the event metadata. status = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &BufferSize); if (ERROR_INSUFFICIENT_BUFFER == status) { pInfo = (TRACE_EVENT_INFO*) malloc(BufferSize); if (pInfo == NULL) { wprintf(L"Failed to allocate memory for event info (size=%lu).\n", BufferSize); status = ERROR_OUTOFMEMORY; goto cleanup; } // Retrieve the event metadata. status = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &BufferSize); } if (ERROR_SUCCESS != status) { wprintf(L"TdhGetEventInformation failed with 0x%x.\n", status); }cleanup: return status;}// Print the metadata for each property.DWORD PrintPropertyMetadata(TRACE_EVENT_INFO* pinfo, DWORD i, USHORT indent){ DWORD status = ERROR_SUCCESS; DWORD j = 0; DWORD lastMember = 0; // Last member of a structure // Print property name. wprintf(L"%*s%s", indent, L"", (LPWSTR)((PBYTE)(pinfo) + pinfo->EventPropertyInfoArray[i].NameOffset)); // If the property is an array, the property can define the array size or it can // point to another property whose value defines the array size. The PropertyParamCount // flag tells you where the array size is defined. if ((pinfo->EventPropertyInfoArray[i].Flags & PropertyParamCount) == PropertyParamCount) { j = pinfo->EventPropertyInfoArray[i].countPropertyIndex; wprintf(L" (array size is defined by %s)", (LPWSTR)((PBYTE)(pinfo) + pinfo->EventPropertyInfoArray[j].NameOffset)); } else { if (pinfo->EventPropertyInfoArray[i].count > 1) wprintf(L" (array size is %lu)", pinfo->EventPropertyInfoArray[i].count); } // If the property is a buffer, the property can define the buffer size or it can // point to another property whose value defines the buffer size. The PropertyParamLength // flag tells you where the buffer size is defined. if ((pinfo->EventPropertyInfoArray[i].Flags & PropertyParamLength) == PropertyParamLength) { j = pinfo->EventPropertyInfoArray[i].lengthPropertyIndex; wprintf(L" (size is defined by %s)", (LPWSTR)((PBYTE)(pinfo) + pinfo->EventPropertyInfoArray[j].NameOffset)); } else { // Variable length properties such as structures and some strings do not have // length definitions. if (pinfo->EventPropertyInfoArray[i].length > 0) wprintf(L" (size is %lu bytes)", pinfo->EventPropertyInfoArray[i].length); else wprintf(L" (size is unknown)"); } wprintf(L"\n"); // If the property is a structure, print the members of the structure. if ((pinfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) { wprintf(L"%*s(The property is a structure and has the following %hu members:)\n", 4, L"", pinfo->EventPropertyInfoArray[i].structType.NumOfStructMembers); lastMember = pinfo->EventPropertyInfoArray[i].structType.StructStartIndex + pinfo->EventPropertyInfoArray[i].structType.NumOfStructMembers; for (j = pinfo->EventPropertyInfoArray[i].structType.StructStartIndex; j < lastMember; j++) { PrintPropertyMetadata(pinfo, j, 4); } } else { // You can use InType to determine the data type of the member and OutType // to determine the output format of the data. if (pinfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset) { // You can pass the name to the TdhGetEventMapInformation function to // retrieve metadata about the value map. wprintf(L"%*s(Map attribute name is %s)\n", indent, L"", (PWCHAR)((PBYTE)(pinfo) + pinfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset)); } } return status;}
参考:
https://msdn.microsoft.com/en-us/library/windows/desktop/aa363688(v=vs.85).aspx
- Windows Event Trace 编程(TDH.lib)
- tdh
- windows事件跟踪--ETW(Event Trace For Windows)
- Windows software trace preprocessor (WPP)&Event Tracing for Windows(ETW)
- Windows SDK 编程调试 TRACE for SDK
- Oracle event trace types
- Oracle Trace Event 10046 Notes
- Windows Socket编程头文件以及lib
- sql trace & event 10046,10053使用方法
- trace: Add user-space event tracing/injection
- Windows NT Stack Trace
- windows stack trace
- Windows多线程编程(3)同步对象——Event对象
- windows下sqlite的.dll生成.lib和c编程
- 从零开始,学习windows编程(4)--从libc.lib开始
- ExtJS学习------Ext.lib.event函数介绍
- Oracle tkprof工具格式化 10046 event trace文件
- windows service event log
- Python操作Reids之任务队列
- JavaScript、 jQuery行拖拽、排序
- 编程之美2.20 程序理解和时间分析
- GridView 设置其高度属性为 wrap_content
- 八大种必知排序算法(二) 选择排序,插入排序,希尔算法
- Windows Event Trace 编程(TDH.lib)
- 和为S的连续正数序列
- Android调试中遇到的bug
- Java写文件的方式:FileOutputStream vs. FileWriter
- mysql查询今天、昨天、7天、近30天、本月、上一月 数据
- C++类内存分布
- 最好的epoll讲解
- JOS 系统中第一个用户进程的建立和运行
- 结果集(ResultSet)用法