Source/Tx.Windows.TypeGeneration/ManifestParser.cs (649 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.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml.Linq; namespace Tx.Windows { public class ManifestParser { private readonly Dictionary<string, string> _code; private readonly XElement _events; private readonly XElement _instrumentation; private readonly IEnumerable<XElement> _providers; private readonly XElement _root; private readonly XElement _stringTable; private Dictionary<int, XElement> _earliestVersions; public ManifestParser(string manifest) { XElement localization; XElement resources = null; _root = XElement.Parse(manifest); _instrumentation = _root.Element(ElementNames.Instrumentation); if (_instrumentation == null) { _instrumentation = _root.Element(ElementNames.Instrumentation1); localization = _root.Element(ElementNames.Localization1); if (localization != null) resources = localization.Element(ElementNames.Resources1); if (resources != null) _stringTable = resources.Element(ElementNames.StringTable1); } else { localization = _root.Element(ElementNames.Localization); if (localization != null) { resources = localization.Element(ElementNames.Resources); if (resources != null) _stringTable = resources.Element(ElementNames.StringTable); } } _events = _instrumentation.Element(ElementNames.Events); if (_events == null) throw new Exception("The element <events> in namespace http://schemas.microsoft.com/win/2004/08/events was not found"); _providers = _events.Elements(ElementNames.Provider); _code = new Dictionary<string, string>(); foreach (XElement provider in _providers) { // Itis unusual that the source attribute is missing. I send mail to Vance string source = provider.Attribute(AttributeNames.Source) == null ? "Xml" : provider.Attribute(AttributeNames.Source).Value; switch (source) { case "Xml": ParseManifestProvider(provider); break; case "Wbem": ParseClassicProvider(provider); break; default: throw new Exception( String.Format( "unknown source attribute {0} for provider {1}. The expexted values are Xml and Wbem", source, provider.Attribute(AttributeNames.Name).Value)); } } } public static Dictionary<string, string> Parse(string manifest) { var parser = new ManifestParser(manifest); return parser._code; } public static string[] ExtractFromTrace(string etlFile) { return EtwObservable.ExtractManifests(etlFile); } private void ParseManifestProvider(XElement provider) { string providerName = MakeIdentifier(provider.Attribute(AttributeNames.Name).Value); string providerGuid = provider.Attribute(AttributeNames.Guid).Value; GetEarliestVersions(provider); Func<XElement, string> nameFunction = FindNameFunction(provider); XElement events = provider.Element(ElementNames.Events); XElement templates = provider.Element(ElementNames.Templates); XElement opcodes = provider.Element(ElementNames.Opcodes); XElement channels = provider.Element(ElementNames.Channels); XElement keywords = provider.Element(ElementNames.Keywords); XElement maps = provider.Element(ElementNames.Maps); XElement tasks = provider.Element(ElementNames.Tasks); var sb = new StringBuilder( @"// // This code was generated by EtwEventTypeGen.exe // using System;"); sb.AppendLine(); sb.AppendLine(); sb.Append("namespace Tx.Windows."); sb.Append(providerName); sb.AppendLine(); sb.AppendLine("{"); if (tasks != null) { this.EmitTaskValue(tasks, sb); } if (maps!=null) { this.EmitMapValue(maps, sb); } foreach (XElement evt in events.Elements()) { string className = nameFunction(evt); string version = "0"; if (evt.Attribute(AttributeNames.Version) != null) { version = evt.Attribute(AttributeNames.Version).Value; } if (evt.Attribute(AttributeNames.Message) != null) { EmitFormatString(ref sb, evt.Attribute(AttributeNames.Message).Value); } else { EmitDefaultFormatString(ref sb, evt, templates); } sb.AppendLine(); sb.AppendFormat(" [ManifestEvent(\"{0}\", {1}, {2},", providerGuid, evt.Attribute(AttributeNames.Value).Value, version); sb.AppendLine(); sb.AppendFormat(" \"{0}\", \"{1}\", \"{2}\"", LookupOpcodeName(evt, opcodes), LookupLevel(evt), LookupChannelName(evt, channels)); foreach (string keyword in LookupKeywords(evt, keywords)) { sb.AppendFormat(", \"{0}\"", keyword); } sb.AppendLine(")]"); sb.AppendLine(); sb.AppendFormat(" public class {0}{1} : SystemEvent", className, VersionSuffix(evt)); sb.AppendLine(); sb.AppendLine(" {"); EmitTemplate(ref sb, evt, templates); sb.AppendLine(" }"); sb.AppendLine(); } sb.AppendLine("}"); _code.Add(providerName, sb.ToString()); } private void ParseClassicProvider(XElement provider) { string providerName = MakeIdentifier(provider.Attribute(AttributeNames.Name).Value); XElement templates = provider.Element(ElementNames.Templates); GetEarliestVersions(provider); Func<XElement, string> nameFunction = FindNameFunction(provider); XElement events = provider.Element(ElementNames.Events); XElement tasks = provider.Element(ElementNames.Tasks); XElement opcodes = provider.Element(ElementNames.Opcodes); var sb = new StringBuilder( @"// // This code was generated by EtwEventTypeGen.exe // using System;"); sb.AppendLine(); sb.AppendLine(); sb.Append("namespace Tx.Windows."); sb.Append(providerName); sb.AppendLine(); sb.AppendLine("{"); foreach (XElement evt in events.Elements()) { string className = nameFunction(evt); XElement task = (from t in tasks.Elements() where evt.Attribute(AttributeNames.Task).Value == t.Attribute(AttributeNames.Name).Value select t).First(); XElement opcode = (from o in opcodes.Elements() where evt.Attribute(AttributeNames.Opcode).Value == o.Attribute(AttributeNames.Name).Value select o).First(); string version = "0"; if (evt.Attribute(AttributeNames.Version) != null) { version = evt.Attribute(AttributeNames.Version).Value; } sb.AppendFormat(" [ClassicEvent(\"{0}\", {1}, {2})]", task.Attribute(AttributeNames.EventGuid).Value, opcode.Attribute(AttributeNames.MofValue).Value, version); sb.AppendLine(); sb.AppendFormat(" public class {0}{1} : SystemEvent", className, VersionSuffix(evt)); sb.AppendLine(); sb.AppendLine(" {"); EmitTemplate(ref sb, evt, templates); sb.AppendLine(" }"); sb.AppendLine(); } sb.AppendLine("}"); _code.Add(providerName, sb.ToString()); } private string MakeIdentifier(string name) { // I stumbled on case of using field name like "load/unload"... char[] chars = name.ToCharArray(); for (int i = 0; i < chars.Length; i++) { if (!char.IsLetterOrDigit(chars[i])) { chars[i] = '_'; } } return new string(chars); } private void EmitTemplate(ref StringBuilder sb, XElement evt, XElement templates) { if (evt.Attribute(AttributeNames.Template) == null) return; IEnumerable<XElement> template = from t in templates.Elements() where t.Attribute(AttributeNames.Tid).Value == evt.Attribute(AttributeNames.Template).Value select t; int order = 0; foreach (XElement f in template.Elements(ElementNames.Data)) { if (order > 0) sb.AppendLine(); var length = f.Attribute(AttributeNames.Length); if (null != length) { sb.AppendFormat(" [EventField(\"{0}\", \"{1}\")]", f.Attribute(AttributeNames.InType).Value, length.Value); } else { sb.AppendFormat(" [EventField(\"{0}\")]", f.Attribute(AttributeNames.InType).Value); } sb.AppendLine(); if (f.Attribute(AttributeNames.Map) == null) { sb.AppendFormat(" public {0} {1}", CleanType(f.Attribute(AttributeNames.InType).Value), NameUtils.CreateIdentifier(f.Attribute(AttributeNames.Name).Value)); } else { sb.AppendFormat(" public {0} {1}", NameUtils.CreateIdentifier(f.Attribute(AttributeNames.Map).Value), NameUtils.CreateIdentifier(f.Attribute(AttributeNames.Name).Value)); } sb.AppendLine(" { get; set; }"); order++; } } private void EmitDefaultFormatString(ref StringBuilder sb, XElement evt, XElement templates) { if (evt.Attribute(AttributeNames.Template) == null) return; IEnumerable<XElement> template = from t in templates.Elements() where t.Attribute(AttributeNames.Tid).Value == evt.Attribute(AttributeNames.Template).Value select t; sb.Append(" [Format(\""); int order = 0; foreach (XElement f in template.Elements(ElementNames.Data)) { if (order > 0) sb.Append(", "); order++; sb.Append(NameUtils.CreateIdentifier(f.Attribute(AttributeNames.Name).Value)); sb.Append("=%"); sb.Append(order); } sb.AppendLine("\")]"); } private void GetEarliestVersions(XElement provider) { _earliestVersions = new Dictionary<int, XElement>(); XElement events = provider.Element(ElementNames.Events); foreach (XElement evt in events.Elements()) { int id = IntAttribute(evt, AttributeNames.Value); XElement other; if (!_earliestVersions.TryGetValue(id, out other)) { _earliestVersions.Add(id, evt); continue; } int version = IntAttribute(evt, AttributeNames.Version); int earliestVersion = IntAttribute(other, AttributeNames.Version); if (version < earliestVersion) { _earliestVersions[id] = evt; } } } private int IntAttribute(XElement element, XName attributeName) { XAttribute attribute = element.Attribute(attributeName); if (attribute == null) return 0; string s = attribute.Value; if (s.StartsWith("0x")) { string v = s.Substring(2); return int.Parse(v, NumberStyles.AllowHexSpecifier); } return int.Parse(s); } private string VersionSuffix(XElement evt) { if (evt.Attribute(AttributeNames.Version) == null) return ""; int id = IntAttribute(evt, AttributeNames.Value); int version = IntAttribute(evt, AttributeNames.Version); int earliestVersion = IntAttribute(_earliestVersions[id], AttributeNames.Version); if (version == earliestVersion) return ""; return "_V" + version; } private Func<XElement, string> FindNameFunction(XElement provider) { Func<XElement, string> function = e => e.Attribute(AttributeNames.Symbol) != null ? e.Attribute(AttributeNames.Symbol).Value : null; IEnumerable<string> names = from e in _earliestVersions.Values select function(e); if (AreNamesUseful(names.ToArray())) { return function; } XElement opcodes = provider.Element(ElementNames.Opcodes); function = e => LookupOpcodeName(e, opcodes); names = from e in _earliestVersions.Values select function(e); if (AreNamesUseful(names.ToArray())) { return function; } XElement tasks = provider.Element(ElementNames.Tasks); function = e => LookupTaskName(e, tasks); names = from e in _earliestVersions.Values select function(e); if (AreNamesUseful(names.ToArray())) { return function; } function = e => e.Attribute(AttributeNames.Task) == null ? null : e.Attribute(AttributeNames.Task).Value; names = from e in _earliestVersions.Values select function(e); if (AreNamesUseful(names.ToArray())) { return function; } function = e => LookupTaskName(e, tasks) + "_" + (e.Attribute(AttributeNames.Opcode) == null ? "" : e.Attribute(AttributeNames.Opcode).Value.Replace("win:", "")); names = from e in _earliestVersions.Values select function(e); if (AreNamesUseful(names.ToArray())) { return function; } function = e => LookupTaskName(e, tasks) + "_" + (e.Attribute(AttributeNames.Opcode) == null ? "" : e.Attribute(AttributeNames.Opcode).Value.Replace("win:", "")) + "_" + e.Attribute(AttributeNames.Value).Value; names = from e in _earliestVersions.Values select function(e); if (AreNamesUseful(names.ToArray())) { return function; } // could not find useful heuristics // so, generate default names function = e => "Event_" + e.Attribute(AttributeNames.Value).Value + "_V" + e.Attribute(AttributeNames.Version).Value; return function; } private bool AreNamesUseful(string[] names) { for (int index = 0; index < names.Length; index++) { string name = names[index]; if (String.IsNullOrEmpty(name)) { return false; } // names must be valid identifiers if (!Regex.IsMatch(name, "^[A-Z_a-z][A-Z_a-z0-9]+")) { return false; } // there should be no duplicate names for (int other = index + 1; other < names.Length; other++) { if (name == names[other]) { return false; } } } return true; } private string LookupLevel(XElement evt) { XAttribute attribute = evt.Attribute(AttributeNames.Level); if (attribute == null) return "Informational"; return attribute.Value; } private string LookupOpcodeName(XElement evt, XElement opcodes) { if (opcodes == null) return null; if (evt.Attribute(AttributeNames.Opcode) == null) return null; string name = evt.Attribute(AttributeNames.Opcode).Value; string message = (from o in opcodes.Elements() where o.Attribute(AttributeNames.Name).Value == name && o.Attribute(AttributeNames.Message) != null select o.Attribute(AttributeNames.Message).Value).FirstOrDefault(); if (String.IsNullOrEmpty(message)) return NameUtils.CreateIdentifier(name); return NameUtils.CreateIdentifier(LookupResourceString(message)); } private string LookupChannelName(XElement evt, XElement channels) { if (channels == null) return null; string name = (from c in channels.Elements() where c.Attribute(AttributeNames.Chid) != null && evt.Attribute(AttributeNames.Channel) != null && evt.Attribute(AttributeNames.Channel).Value == c.Attribute(AttributeNames.Chid).Value select c.Attribute(AttributeNames.Name).Value).FirstOrDefault(); return name; } private string LookupTaskName(XElement evt, XElement tasks) { if (tasks == null) return null; string message = (from t in tasks.Elements() where t.Attribute(AttributeNames.Message) != null && evt.Attribute(AttributeNames.Task) != null && evt.Attribute(AttributeNames.Task).Value == t.Attribute(AttributeNames.Name).Value select t.Attribute(AttributeNames.Message).Value).FirstOrDefault(); if (String.IsNullOrEmpty(message)) return null; return LookupResourceString(message); } private string LookupResourceString(string message) { if (_stringTable == null) return message; string stringId = message.Substring(9) // skip "$(string." .TrimEnd(')'); return (from s in _stringTable.Elements() where s.Attribute(AttributeNames.Id).Value == stringId select s.Attribute(AttributeNames.Value).Value) .FirstOrDefault(); } private string[] LookupKeywords(XElement evt, XElement keywords) { if (keywords == null) return new string[0]; if (evt.Attribute(AttributeNames.Keywords) == null) return new string[0]; string[] names = evt.Attribute(AttributeNames.Keywords).Value.Split(' '); IEnumerable<string> x = from k in keywords.Elements() from name in names where k.Attribute(AttributeNames.Name).Value == name select k.Attribute(AttributeNames.Message) == null ? name : LookupResourceString(k.Attribute(AttributeNames.Message).Value); return x.ToArray(); } private void EmitFormatString(ref StringBuilder sb, string message) { string format = LookupResourceString(message); format = format .Replace("\\", "\\\\") .Replace("\"", "\\\""); sb.Append(" [Format(\""); sb.Append(format); sb.AppendLine("\")]"); } internal static string CleanType(string typeName) { switch (typeName) { case "win:Pointer": case "trace:SizeT": return "ulong"; // Address in the VS generation code case "win:Boolean": return "bool"; case "win:Int8": return "sbyte"; case "win:UInt8": return "byte"; case "win:HexInt8": return "uint"; case "win:Int16": return "short"; case "win:UInt16": case "win:HexInt16": case "trace:Port": return "ushort"; case "win:Int32": return "int"; case "win:UInt32": case "win:HexInt32": case "trace:IPAddr": case "trace:IPAddrV4": return "uint"; case "win:Double": return "double"; case "win:Float": return "float"; case "win:Int64": return "long"; case "win:SYSTEMTIME": return "DateTime"; case "trace:WmiTime": case "win:FILETIME": return "DateTime"; case "win:HexInt64": case "win:UInt64": return "ulong"; case "trace:UnicodeChar": return "string"; case "win:UnicodeString": case "win:UnicodeStringPref": return "string"; case "win:AnsiString": case "win:AnsiStringPref": return "string"; case "win:GUID": case "trace:WBEMSid": return "Guid"; case "win:Binary": return "byte[]"; case "win:SID": return "string"; default: throw new InvalidOperationException("unknown type " + typeName); } } private void EmitTaskValue(XElement tasks, StringBuilder sb) { sb.AppendFormat(" public enum EventTask : uint"); sb.AppendLine(" {"); var mapCollection = new Dictionary<string, string>(); foreach (var taskValue in tasks.Elements()) { var taskEnumIdentifier = NameUtils.CreateIdentifier(taskValue.Attribute(AttributeNames.Name).Value); var taskEnumValue = taskValue.Attribute(AttributeNames.Value).Value; sb.AppendFormat(" {0} = {1},", taskEnumIdentifier, taskEnumValue); sb.AppendLine(); } sb.AppendLine(" }"); sb.AppendLine(); } private void EmitMapValue(XElement maps, StringBuilder sb) { foreach (XElement map in maps.Elements()) { string className = map.Attribute(AttributeNames.Name).Value; bool isInt = map.Elements() .Select(e => e.Attribute(AttributeNames.Value).Value) .All(s => { int val; return Int32.TryParse(s, out val); }); string mapType = isInt ? "int" : "uint"; sb.AppendFormat(" public enum {0} : {1}", NameUtils.CreateIdentifier(className), mapType); sb.AppendLine(" {"); var mapCollection = new Dictionary<string, string>(); foreach (var mapValue in map.Elements()) { var mapEnumIdentifier = NameUtils.CreateIdentifier(LookupResourceString(mapValue.Attribute(AttributeNames.Message).Value)); var mapEnumValue = mapValue.Attribute(AttributeNames.Value).Value; if (mapCollection.ContainsKey(mapEnumIdentifier)) { mapCollection[mapEnumIdentifier] += " | " + mapEnumValue; } else { mapCollection[mapEnumIdentifier] = mapEnumValue; } } foreach (var mapValue in mapCollection) { sb.AppendFormat(" {0} = {1},", mapValue.Key, mapValue.Value); sb.AppendLine(); } sb.AppendLine(" }"); sb.AppendLine(); } } private class AttributeNames { public const string Source = "source"; public const string Name = "name"; public const string Guid = "guid"; public const string Value = "value"; public const string Symbol = "symbol"; public const string Task = "task"; public const string Map = "map"; public const string Template = "template"; public const string Tid = "tid"; public const string InType = "inType"; public const string Version = "version"; public const string Opcode = "opcode"; public const string Id = "id"; public const string Message = "message"; public const string EventGuid = "eventGUID"; public const string MofValue = "mofValue"; public const string Length = "length"; public const string Level = "level"; public const string Channel = "channel"; public const string Chid = "chid"; public const string Keywords = "keywords"; } private class ElementNames { private static readonly XNamespace ns1 = "urn:schemas-microsoft-com:asm.v3"; public static readonly XName Instrumentation1 = ns1 + "instrumentation"; public static readonly XName Localization1 = ns1 + "localization"; public static readonly XName Resources1 = ns1 + "resources"; public static readonly XName StringTable1 = ns1 + "stringTable"; private static readonly XNamespace ns = "http://schemas.microsoft.com/win/2004/08/events"; public static readonly XName Instrumentation = ns + "instrumentation"; public static readonly XName Provider = ns + "provider"; public static readonly XName Events = ns + "events"; public static readonly XName Tasks = ns + "tasks"; public static readonly XName Maps = ns + "maps"; public static readonly XName Templates = ns + "templates"; public static readonly XName Opcodes = ns + "opcodes"; public static readonly XName Localization = ns + "localization"; public static readonly XName Resources = ns + "resources"; public static readonly XName StringTable = ns + "stringTable"; public static readonly XName Data = ns + "data"; public static readonly XName Channels = ns + "channels"; public static readonly XName Keywords = ns + "keywords"; } } }