Source/Tx.Windows/EtwTdh/EtwTdhEventInfo.cs (373 lines of code) (raw):

// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Net; using System.Runtime.InteropServices; using System.Text; namespace Tx.Windows { /// <summary> /// Class representing metadata about particular {provider, eventId} /// </summary> unsafe class EtwTdhEventInfo { EtwPropertyInfo[] _properties; Dictionary<string, object> _template; string _formatString; public EtwTdhEventInfo(ref EtwNativeEvent e) { IntPtr buffer = IntPtr.Zero; try { buffer = ReadTdhMetadata(ref e); CopyMetadata(buffer, ref e); } finally { if (buffer != IntPtr.Zero) { Marshal.FreeHGlobal(buffer); } } } /// <summary> /// Method to read ETW event as per the metadata stored in this class /// </summary> /// <param name="e">The ETW native event helper class for one-time read</param> /// <returns></returns> public IDictionary<string, object> Deserialize(ref EtwNativeEvent e) { Dictionary<string, object> instance = new Dictionary<string, object>(_template) { { "EventId", e.Id }, { "Version", e.Version }, { "TimeCreated", e.TimeStamp.UtcDateTime }, { "ProcessId", e.ProcessId }, { "ThreadId", e.ThreadId }, { "ActivityId", e.ActivityId } }; Dictionary<string, object> eventData = new Dictionary<string, object>(); List<object> values = new List<object>(); foreach (var p in _properties) { uint len = p.Length; if (p.LengthPropertyName != null) { try { string num = Convert.ToString(eventData[p.LengthPropertyName]); if (num.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase)) { num = num.Substring(2); len = uint.Parse(num, System.Globalization.NumberStyles.HexNumber); } else { len = Convert.ToUInt32(num); } } catch (Exception ex) { ex.Data["ProviderGuid"] = e.ProviderId; throw; } } try { object value = GetValue(p.Type, len, ref e); value = FormatValue(p, value); value = EtwTdhPostFormat.ApplyFormatting(e.ProviderId, e.Id, p.Name, value); eventData.Add(p.Name, value); values.Add(value); } catch (Exception) { eventData.Add(p.Name, "Exception on retrieving value"); } } instance.Add("EventData", eventData); if (_formatString == null) { instance.Add("Message", null); } else { string message = string.Format(_formatString, values.ToArray()); instance.Add("Message", message); } return instance; } /// <summary> /// This function reads the event metadata from TDH into globally allocated buffer /// It is caller's responsibility to free the memory by calling Marshal.FreeHGlobal /// </summary> /// <param name="e">ETW native event interop wrapper structure</param> /// <returns>Pointer to newly allocated TRACE_EVENT_INFO structure</returns> IntPtr ReadTdhMetadata(ref EtwNativeEvent e) { int bufferSize = 0; int status = EtwNativeMethods.TdhGetEventInformation(ref *e.record, 0, IntPtr.Zero, IntPtr.Zero, ref bufferSize); if (122 != status) // ERROR_INSUFFICIENT_BUFFER { Exception ex = new Exception("Unexpected TDH status " + status); ex.Data["ProviderGuid"] = e.ProviderId; throw ex; } var mybuffer = Marshal.AllocHGlobal(bufferSize); status = EtwNativeMethods.TdhGetEventInformation(ref *e.record, 0, IntPtr.Zero, mybuffer, ref bufferSize); if (status != 0) { throw new Exception("TDH status " + status); } return mybuffer; } /// <summary> /// This function copies the information from the native TRACE_EVENT_INFO structure /// to property information in this oject /// </summary> /// <param name="buffer">IntPtr to TRACE_EVENT_INFO structure</param> void CopyMetadata(IntPtr buffer, ref EtwNativeEvent e) { TRACE_EVENT_INFO* info = (TRACE_EVENT_INFO*)buffer; byte* start = (byte*)info; byte* end = start + sizeof(TRACE_EVENT_INFO); _template = new Dictionary<string, object>(); _template.Add("Provider", CopyString(start, info->ProviderNameOffset)); _template.Add("Level", CopyString(start, info->LevelNameOffset)); _template.Add("Task", CopyString(start, info->TaskNameOffset)); _template.Add("Opcode", CopyString(start, info->OpcodeNameOffset)); _template.Add("Channel", CopyString(start, info->ChannelNameOffset)); _formatString = TranslateFormatString(CopyString(start, info->EventMessageOffset)); EVENT_PROPERTY_INFO* prop = (EVENT_PROPERTY_INFO*)end; var propList = new List<EtwPropertyInfo>(); for (int i = 0; i < info->TopLevelPropertyCount; i++) { var propInfo = prop + i; var name = CopyString(start, propInfo->NameOffset); var type = (TdhInType)(*propInfo).NonStructTypeValue.InType; var outType = (TdhOutType)(*propInfo).NonStructTypeValue.OutType; EtwPropertyInfo property = null; if (propInfo->Flags == PROPERTY_FLAGS.PropertyParamLength) { string lenPropertyName = propList[(int)(propInfo->LengthPropertyIndex)].Name; property = new EtwPropertyInfo { Name = name, Type = type, OutType = outType, LengthPropertyName = lenPropertyName }; } else { ushort len = (*propInfo).LengthPropertyIndex; property = new EtwPropertyInfo { Name = name, Length = len, Type = type, OutType = outType }; } if (propInfo->NonStructTypeValue.MapNameOffset > 0) { string mapName = CopyString(start, propInfo->NonStructTypeValue.MapNameOffset); property.ValueMap = ReadTdhMap(mapName, ref e); } propList.Add(property); } _properties = propList.ToArray(); } Dictionary<uint, string> ReadTdhMap(string mapName, ref EtwNativeEvent e) { IntPtr pMapName = Marshal.StringToBSTR(mapName); int bufferSize = 0; int status = EtwNativeMethods.TdhGetEventMapInformation( ref *e.record, pMapName, IntPtr.Zero, ref bufferSize); if (122 != status) // ERROR_INSUFFICIENT_BUFFER { throw new Exception("Unexpected TDH status " + status); } var mybuffer = Marshal.AllocHGlobal(bufferSize); status = EtwNativeMethods.TdhGetEventMapInformation( ref *e.record, pMapName, mybuffer, ref bufferSize); if (status != 0) { throw new Exception("TDH status " + status); } EVENT_MAP_INFO* mapInfo = (EVENT_MAP_INFO*)mybuffer; byte* startMap = (byte*)mapInfo; var name1 = CopyString(startMap, mapInfo->NameOffset); byte* endMap = startMap + sizeof(EVENT_MAP_INFO); var map = new Dictionary<uint, string>(); for (int i = 0; i < mapInfo->EntryCount; i++) { EVENT_MAP_ENTRY* mapEntry = (EVENT_MAP_ENTRY*)endMap + i; uint value = mapEntry->Value; string name = CopyString(startMap, mapEntry->OutputOffset); map.Add(value, name); } return map; } string CopyString(byte* start, uint offset) { if (offset == 0) return null; byte* pName = start + offset; var name = Marshal.PtrToStringUni((IntPtr)(pName)); return name; } static object GetValue(TdhInType type, uint len, ref EtwNativeEvent evt) { // please keep the code below in the same order is the reader methods in EtwNativeEvent switch (type) { case TdhInType.AnsiChar: return evt.ReadAnsiChar(); case TdhInType.UnicodeChar: return evt.ReadChar(); case TdhInType.Int8: return evt.ReadByte(); case TdhInType.UInt8: return evt.ReadUInt8(); case TdhInType.Int16: return evt.ReadInt16(); case TdhInType.UInt16: return evt.ReadUInt16(); case TdhInType.Int32: return evt.ReadInt32(); case TdhInType.UInt32: case TdhInType.HexInt32: return evt.ReadUInt32(); case TdhInType.Int64: return evt.ReadInt64(); case TdhInType.UInt64: case TdhInType.HexInt64: return evt.ReadUInt64(); case TdhInType.Pointer: return evt.ReadPointer(); case TdhInType.Boolean: return evt.ReadBoolean(); case TdhInType.Float: return evt.ReadFloat(); case TdhInType.Double: return evt.ReadDouble(); case TdhInType.FileTime: return evt.ReadFileTime(); case TdhInType.SystemTime: return evt.ReadSystemTime(); case TdhInType.Guid: return evt.ReadGuid(); case TdhInType.Binary: return evt.ReadBytes(len); case TdhInType.UnicodeString: case TdhInType.SID: if (len > 0) { return evt.ReadUnicodeString((int)len); } else { return evt.ReadUnicodeString(); } case TdhInType.AnsiString: return evt.ReadAnsiString(); default: var ex = new Exception("Unknown type " + type.ToString()); ex.Data ["ProviderGuid"] = evt.ProviderId; throw ex; } } static object FormatValue(EtwPropertyInfo propertyInfo, object value) { if (propertyInfo.ValueMap != null) { uint key = Convert.ToUInt32(value); if (!propertyInfo.ValueMap.TryGetValue(key, out string name)) { name = value.ToString(); } return name; } switch (propertyInfo.OutType) { case TdhOutType.SocketAddress: return FormatAsIpAddress(value); case TdhOutType.HexInt8: return "0x" + ((byte)value).ToString("X"); case TdhOutType.HexInt16: return "0x" + ((ushort)value).ToString("X"); case TdhOutType.HexInt32: return "0x" + ((uint)value).ToString("X"); case TdhOutType.HexInt64: return "0x" + ((ulong)value).ToString("X"); default: return value; } } static string FormatAsIpAddress(object value) { byte[] buffer = (byte[])value; if (buffer.Length == 0) { return IPAddress.None.ToString(); } if (buffer.Length == 0x10) { // Interpret the data as IPv4 address:port, represented as sockaddr_in structure int port = buffer[2] << 8 | buffer[3]; byte[] addr = new byte[4]; Array.Copy(buffer, 4, addr, 0, 4); var ipv4 = new IPAddress(addr); string s = ipv4.ToString().ToUpper() + ":" + port.ToString(); return s; } else { // Interpret the data as IPv4 address:port, represented as sockaddr_in6 structure int port = buffer[2] << 8 | buffer[3]; byte[] addr = new byte[16]; Array.Copy(buffer, 8, addr, 0, 16); var ipv6 = new IPAddress(addr); string s = ipv6.ToString().ToUpper() + ":" + port.ToString(); return s; } } string TranslateFormatString(string messageFormat) { if (messageFormat == null) { return null; } string format = messageFormat.Replace("%n", "\n"); format = format.Replace("%t", " "); StringBuilder sb = new StringBuilder(); int startIndex = 0; while (startIndex < format.Length - 1) { int percentIndex = format.IndexOf('%', startIndex); // no more arguments SkipEscapedPercent: if (percentIndex < 0) { string last = format.Substring(startIndex); sb.Append(last); break; } if (format[percentIndex + 1] == '%') // special case %% means % escaped { percentIndex = format.IndexOf('%', percentIndex + 2); goto SkipEscapedPercent; } string prefix = format.Substring(startIndex, percentIndex - startIndex); sb.Append(prefix); int beginNumberIndex = percentIndex + 1; int endNumberIndex = beginNumberIndex; while (endNumberIndex < format.Length) { if (format[endNumberIndex] < '0' || format[endNumberIndex] > '9') { break; } endNumberIndex++; } string s = format.Substring(beginNumberIndex, endNumberIndex - beginNumberIndex); int index = int.Parse(s) - 1; // The C# convention is to start from 0 and teh % notation in the manifests starts from 1 sb.Append('{'); sb.Append(index); sb.Append('}'); startIndex = endNumberIndex; } return sb.ToString(); } class EtwPropertyInfo { public string Name; public TdhInType Type; public TdhOutType OutType; public ushort Length; // used when the length is explicit public string LengthPropertyName; // used when the length is specified as previous property value public Dictionary<uint, string> ValueMap; public override string ToString() { if (Type == TdhInType.Binary) { return Type + "[" + Length + "] " + Name; } else { return Type + " " + Name; } } } } }