diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e00851..aa450b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,14 +6,14 @@ jobs: steps: # setup .NET using setup-dotnet action - name: Setup .NET - uses: actions/setup-dotnet@v3.0.3 + uses: actions/setup-dotnet@v4 - name: checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v2 - name: Setup NuGet - uses: nuget/setup-nuget@v1 - - uses: actions/cache@v3 + uses: nuget/setup-nuget@v2 + - uses: actions/cache@v4 with: path: ~/.nuget/packages key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }} diff --git a/EnabledProvider.cs b/EnabledProvider.cs index a0c05ae..352cffb 100644 --- a/EnabledProvider.cs +++ b/EnabledProvider.cs @@ -399,7 +399,7 @@ public void SetFilteredPackageId(string PackageId) m_FilterDescriptors.Add(descriptor); } - public void SetStackwalkLevelKw(EventTraceLevel Level, ulong MatchAnyKeyword, ulong MatchAllKeyword, bool FilterIn) + public void SetStackwalkLevelKw(byte Level, ulong MatchAnyKeyword, ulong MatchAllKeyword, bool FilterIn) { foreach (var desc in m_FilterDescriptors) { @@ -419,7 +419,7 @@ public void SetStackwalkLevelKw(EventTraceLevel Level, ulong MatchAnyKeyword, ul } var filter = new EVENT_FILTER_LEVEL_KW(); - filter.Level = (byte)Level; + filter.Level = Level; filter.MatchAllKeyword = MatchAllKeyword; filter.MatchAnyKeyword = MatchAnyKeyword; filter.FilterIn = FilterIn; diff --git a/FileTrace.cs b/FileTrace.cs index 6fe2860..f679ed1 100644 --- a/FileTrace.cs +++ b/FileTrace.cs @@ -38,10 +38,24 @@ public FileTrace(string EtlFileName) public override void Start() { + // + // A FileTrace opens an existing ETL, so there is no start operation. + // Trace(TraceLoggerType.FileTrace, TraceEventType.Information, $"Starting FileTrace for log {m_LogFile.LogFileName}"); return; } + + public override void Stop() + { + // + // A FileTrace opens an existing ETL, so there is no stop operation. + // + Trace(TraceLoggerType.FileTrace, + TraceEventType.Information, + $"Stopping FileTrace for log {m_LogFile.LogFileName}"); + return; + } } } diff --git a/ParsedEtwManifestEvent.cs b/ParsedEtwManifestEvent.cs index 45ad924..cea0bb9 100644 --- a/ParsedEtwManifestEvent.cs +++ b/ParsedEtwManifestEvent.cs @@ -112,7 +112,45 @@ public int CompareTo(ParsedEtwManifestEvent? Other) public override string ToString() { return $"Id={Id}, Version={Version}, Task={Task}, Opcode={Opcode}, " + - $"Channel={Channel}, Level={Level}, Keywords={Keywords}"; + $"Channel={Channel}, Level={Level}, Keywords={Keywords}, Template={Template}"; + } + + public static ParsedEtwManifestEvent? FromString(string Value) + { + var values = Value.Split(',', StringSplitOptions.RemoveEmptyEntries); + if (values.Length != 8) + { + return null; + } + var dict = new Dictionary(); + foreach (var v in values) + { + var kvp = v.Split('=', StringSplitOptions.RemoveEmptyEntries); + if (kvp.Length < 1) + { + return null; + } + var key = kvp[0]; + var val = string.Empty; + if (kvp.Length == 2) + { + val = kvp[1]; + } + if (dict.ContainsKey(key)) + { + return null; + } + dict.Add(key, val); + } + return new ParsedEtwManifestEvent( + dict["Id"], + dict["Version"], + dict["Opcode"], + dict["Channel"], + dict["Level"], + dict["Keywords"], + dict["Task"], + dict["Template"]); } } } diff --git a/ProviderParser.cs b/ProviderParser.cs index d54cb2d..0e41c1a 100644 --- a/ProviderParser.cs +++ b/ProviderParser.cs @@ -163,6 +163,7 @@ public static class ProviderParser { var parsedManifest = new ParsedEtwManifest(); var fieldResults = new List(); + var manifestLoaded = false; // // If a manifest location was provided, ask TDH to load it. @@ -172,6 +173,7 @@ public static class ProviderParser try { LoadProviderManifest(ManifestLocation); + manifestLoaded = true; } catch (Exception ex) { @@ -492,13 +494,17 @@ public static class ProviderParser parsedManifest.StringTable = parsedManifest.StringTable.Distinct().ToList(); parsedManifest.StringTable.Sort(); + if (manifestLoaded) + { + _ = TdhUnloadManifest(ManifestLocation); + } return parsedManifest; } public static Dictionary - GetManifests() + GetManifests(int Limit = 0) { var results = new Dictionary(); var providers = GetProviders(); @@ -506,6 +512,13 @@ public static class ProviderParser { try { + if (Limit > 0 && results.Count >= Limit) + { + Trace(TraceLoggerType.EtwProviderParser, + TraceEventType.Verbose, + $"Processed {Limit} providers, stopping."); + break; + } if (results.ContainsKey(provider)) { // diff --git a/RealTimeTrace.cs b/RealTimeTrace.cs index 1b023d4..9bf4994 100644 --- a/RealTimeTrace.cs +++ b/RealTimeTrace.cs @@ -34,12 +34,12 @@ public class RealTimeTrace : TraceSession private nint m_PropertiesBuffer; private long m_SessionHandle; - public RealTimeTrace(string SessionName) + public RealTimeTrace(string SessionName) : base() { m_SessionName = SessionName; m_SessionGuid = Guid.NewGuid(); m_PropertiesBuffer = nint.Zero; - m_SessionHandle = 0; + m_SessionHandle = nint.Zero; m_LogFile.LoggerName = m_SessionName; m_LogFile.ProcessTraceMode = ProcessTraceMode.EventRecord | ProcessTraceMode.RealTime; } @@ -60,33 +60,7 @@ protected override void Dispose(bool disposing) TraceEventType.Information, "Disposing RealTimeTrace"); - if (m_SessionHandle != 0 && m_SessionHandle != -1) - { - uint result; - foreach (var provider in m_EnabledProviders) - { - result = provider.Disable(m_SessionHandle); - if (result != ERROR_SUCCESS) - { - Trace(TraceLoggerType.RealTimeTrace, - TraceEventType.Error, - $"RealTimeTrace dispose could not disable provider: " + - $"{result:X}"); - } - } - result = ControlTrace( - m_SessionHandle, - m_SessionName, - m_PropertiesBuffer, - ControlCode.Stop); - if (result != ERROR_SUCCESS) - { - Trace(TraceLoggerType.RealTimeTrace, - TraceEventType.Error, - $"RealTimeTrace dispose could not stop trace: " + - $"{result:X}"); - } - } + Stop(); if (m_PropertiesBuffer != nint.Zero) { @@ -183,6 +157,64 @@ public override void Start() } } + public override void Stop() + { + if (m_SessionHandle != 0 && m_SessionHandle != -1) + { + Trace(TraceLoggerType.RealTimeTrace, + TraceEventType.Information, + $"Stopping RealTimeTrace {m_SessionName}..."); + uint result; + foreach (var provider in m_EnabledProviders) + { + result = provider.Disable(m_SessionHandle); + if (result != ERROR_SUCCESS) + { + Trace(TraceLoggerType.RealTimeTrace, + TraceEventType.Error, + $"RealTimeTrace dispose could not disable provider: " + + $"{result:X}"); + } + } + result = ControlTrace( + m_SessionHandle, + m_SessionName, + m_PropertiesBuffer, + ControlCode.Stop); + if (result != ERROR_SUCCESS) + { + Trace(TraceLoggerType.RealTimeTrace, + TraceEventType.Error, + $"RealTimeTrace dispose could not stop trace: " + + $"{result:X}"); + } + } + } + + public static long Open(string Name) + { + EVENT_TRACE_LOGFILE logfile = new EVENT_TRACE_LOGFILE(); + logfile.LoggerName = Name; + logfile.ProcessTraceMode = ProcessTraceMode.EventRecord | ProcessTraceMode.RealTime; + Trace(TraceLoggerType.RealTimeTrace, + TraceEventType.Information, + $"Opening existing RealTimeTrace {Name}..."); + var logFilePointer = Marshal.AllocHGlobal(Marshal.SizeOf(logfile)); + Marshal.StructureToPtr(logfile, logFilePointer, false); + var handle = OpenTrace(logFilePointer); + Marshal.FreeHGlobal(logFilePointer); + if (handle == -1 || handle == 0) + { + var error = "OpenTrace() returned an invalid handle: 0x" + + Marshal.GetLastWin32Error().ToString("X"); + Trace(TraceLoggerType.TraceSession, + TraceEventType.Error, + error); + throw new Exception(error); + } + return handle; + } + private void GenerateTraceProperties() diff --git a/TraceSession.cs b/TraceSession.cs index e219005..cf7b9e3 100644 --- a/TraceSession.cs +++ b/TraceSession.cs @@ -78,6 +78,7 @@ public void AddProvider(EnabledProvider Provider) } public abstract void Start(); + public abstract void Stop(); public void Consume( EventRecordCallback EventCallback, diff --git a/UnitTests/FilterByStackwalkTests.cs b/UnitTests/FilterByStackwalkTests.cs index 5ff2338..5117a5c 100644 --- a/UnitTests/FilterByStackwalkTests.cs +++ b/UnitTests/FilterByStackwalkTests.cs @@ -56,7 +56,7 @@ public void LevelKw( { var provider = trace.AddProvider( s_WinKernelRegistryGuid, "WinKernelReg", Level, (ulong)MatchAnyKeyword, 0); - provider.SetStackwalkLevelKw(Level, (ulong)MatchAnyKeyword, 0, Enable); + provider.SetStackwalkLevelKw((byte)Level, (ulong)MatchAnyKeyword, 0, Enable); trace.Start(); // diff --git a/UnitTests/RealTimeTraceTests.cs b/UnitTests/RealTimeTraceTests.cs index cb1b5c9..536ca00 100644 --- a/UnitTests/RealTimeTraceTests.cs +++ b/UnitTests/RealTimeTraceTests.cs @@ -20,6 +20,7 @@ under the License. using System; using System.Runtime.InteropServices; using etwlib; +using System.Threading.Tasks; namespace UnitTests { @@ -98,6 +99,93 @@ public void Basic(EventTraceLevel Level) Assert.Fail($"An exception occurred when consuming events: {ex.Message}"); } } - } + } + + [DataTestMethod] + [DataRow(EventTraceLevel.Information)] + [DataRow(EventTraceLevel.LogAlways)] + public void BasicStartStop(EventTraceLevel Level) + { + int eventsConsumed = 0; + + ConfigureLoggers(); + + var trace = new RealTimeTrace("Unit Test Real-Time Tracing"); + + // + // Start a task to initiate a trace with no stop condition. + // + Task.Run(() => + { + using (var parserBuffers = new EventParserBuffers()) + { + try + { + var provider = trace.AddProvider(s_RpcEtwGuid, "RPC", Level, 0xFFFFFFFFFFFFFFFF, 0); + trace.Start(); + + // + // Begin consuming events. This is a blocking call. + // + trace.Consume(new EventRecordCallback((Event) => + { + var evt = (EVENT_RECORD)Marshal.PtrToStructure( + Event, typeof(EVENT_RECORD))!; + + var parser = new EventParser( + evt, + parserBuffers, + trace.GetPerfFreq()); + try + { + var result = parser.Parse(); + Assert.IsNotNull(result); + } + catch (Exception ex) + { + Assert.Fail($"Unable to parse event: {ex.Message}"); + } + eventsConsumed++; + }), + new BufferCallback((LogFile) => + { + var logfile = new EVENT_TRACE_LOGFILE(); + try + { + logfile = (EVENT_TRACE_LOGFILE) + Marshal.PtrToStructure(LogFile, typeof(EVENT_TRACE_LOGFILE))!; + } + catch (Exception ex) + { + Assert.Fail($"Unable to cast EVENT_TRACE_LOGFILE: {ex.Message}"); + } + return 1; + })); + } + catch (Exception ex) + { + Assert.Fail($"An exception occurred when consuming events: {ex.Message}"); + } + } + }); + + // + // Start a task to stop the trace. + // + Task.Run(() => + { + using (var trace = new RealTimeTrace("Unit Test Real-Time Tracing")) + { + try + { + trace.Stop(); + } + catch (Exception ex) + { + Assert.Fail($"An exception occurred when consuming events: {ex.Message}"); + } + } + }); + } } } diff --git a/etwlib.csproj b/etwlib.csproj index 0d2fac4..34a7997 100644 --- a/etwlib.csproj +++ b/etwlib.csproj @@ -17,7 +17,7 @@ True 1.1.0 1.1.0 - 1.6.0 + 1.10.0