1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.Numerics;
11 using System.Runtime.InteropServices;
12 using System.Security;
13 #if ES_BUILD_STANDALONE
14 using System.Security.Permissions;
16 #if CORECLR && PLATFORM_WINDOWS
19 using System.Threading;
22 #if ES_BUILD_STANDALONE
23 using BitOperations = Microsoft.Diagnostics.Tracing.Internal.BitOperations;
26 #if !ES_BUILD_AGAINST_DOTNET_V35
27 using Contract = System.Diagnostics.Contracts.Contract;
29 using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
32 #if ES_BUILD_AGAINST_DOTNET_V35
33 using Microsoft.Internal; // for Tuple (can't define alias for open generic types so we "use" the whole namespace)
36 #if ES_BUILD_STANDALONE
37 namespace Microsoft.Diagnostics.Tracing
39 namespace System.Diagnostics.Tracing
42 internal enum EventProviderType
50 internal enum ControllerCommand
52 // Strictly Positive numbers are for provider-specific commands, negative number are for 'shared' commands. 256
53 // The first 256 negative numbers are reserved for the framework.
54 Update = 0, // Not used by EventProviderBase.
61 /// Only here because System.Diagnostics.EventProvider needs one more extensibility hook (when it gets a
62 /// controller callback)
64 #if ES_BUILD_STANDALONE
65 [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
67 internal class EventProvider : IDisposable
69 // This is the windows EVENT_DATA_DESCRIPTOR structure. We expose it because this is what
70 // subclasses of EventProvider use when creating efficient (but unsafe) version of
71 // EventWrite. We do make it a nested type because we really don't expect anyone to use
72 // it except subclasses (and then only rarely).
73 [StructLayout(LayoutKind.Sequential)]
74 public struct EventData
76 internal unsafe ulong Ptr;
78 internal uint Reserved;
82 /// A struct characterizing ETW sessions (identified by the etwSessionId) as
83 /// activity-tracing-aware or legacy. A session that's activity-tracing-aware
84 /// has specified one non-zero bit in the reserved range 44-47 in the
85 /// 'allKeywords' value it passed in for a specific EventProvider.
87 public struct SessionInfo
89 internal int sessionIdBit; // the index of the bit used for tracing in the "reserved" field of AllKeywords
90 internal int etwSessionId; // the machine-wide ETW session ID
92 internal SessionInfo(int sessionIdBit_, int etwSessionId_)
93 { sessionIdBit = sessionIdBit_; etwSessionId = etwSessionId_; }
96 internal IEventProvider m_eventProvider; // The interface that implements the specific logging mechanism functions.
97 Interop.Advapi32.EtwEnableCallback? m_etwCallback; // Trace Callback function
98 private long m_regHandle; // Trace Registration Handle
99 private byte m_level; // Tracing Level
100 private long m_anyKeywordMask; // Trace Enable Flags
101 private long m_allKeywordMask; // Match all keyword
102 private List<SessionInfo>? m_liveSessions; // current live sessions (Tuple<sessionIdBit, etwSessionId>)
103 private bool m_enabled; // Enabled flag from Trace callback
104 private string? m_providerName; // Control name
105 private Guid m_providerId; // Control Guid
106 internal bool m_disposed; // when true provider has unregistered
109 private static WriteEventErrorCode s_returnCode; // The last return code
111 private const int s_basicTypeAllocationBufferSize = 16;
112 private const int s_etwMaxNumberArguments = 128;
113 private const int s_etwAPIMaxRefObjCount = 8;
114 private const int s_maxEventDataDescriptors = 128;
115 private const int s_traceEventMaximumSize = 65482;
116 private const int s_traceEventMaximumStringSize = 32724;
118 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
119 public enum WriteEventErrorCode : int
121 //check mapping to runtime codes
130 // Because callbacks happen on registration, and we need the callbacks for those setup
131 // we can't call Register in the constructor.
133 // Note that EventProvider should ONLY be used by EventSource. In particular because
134 // it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise
135 // you may get native callbacks during shutdown when we have destroyed the delegate.
136 // EventSource has special logic to do this, no one else should be calling EventProvider.
137 internal EventProvider(EventProviderType providerType)
139 switch (providerType)
141 case EventProviderType.ETW:
143 m_eventProvider = new EtwEventProvider();
145 m_eventProvider = new NoOpEventProvider();
148 case EventProviderType.EventPipe:
149 #if FEATURE_PERFTRACING
150 m_eventProvider = new EventPipeEventProvider();
152 m_eventProvider = new NoOpEventProvider();
156 m_eventProvider = new NoOpEventProvider();
162 /// This method registers the controlGuid of this class with ETW. We need to be running on
163 /// Vista or above. If not a PlatformNotSupported exception will be thrown. If for some
164 /// reason the ETW Register call failed a NotSupported exception will be thrown.
166 // <SecurityKernel Critical="True" Ring="0">
167 // <CallsSuppressUnmanagedCode Name="Interop.Advapi32.EventRegister(System.Guid&,Microsoft.Win32.Interop.Advapi32+EtwEnableCallback,System.Void*,System.Int64&):System.UInt32" />
168 // <SatisfiesLinkDemand Name="Win32Exception..ctor(System.Int32)" />
169 // <ReferencesCritical Name="Method: EtwEnableCallBack(Guid&, Int32, Byte, Int64, Int64, Void*, Void*):Void" Ring="1" />
171 internal unsafe void Register(EventSource eventSource)
174 m_etwCallback = new Interop.Advapi32.EtwEnableCallback(EtwEnableCallBack);
176 status = EventRegister(eventSource, m_etwCallback);
179 #if PLATFORM_WINDOWS && !ES_BUILD_STANDALONE
180 throw new ArgumentException(Interop.Kernel32.GetMessage(unchecked((int)status)));
182 throw new ArgumentException(Convert.ToString(unchecked((int)status)));
188 // implement Dispose Pattern to early deregister from ETW insted of waiting for
189 // the finalizer to call deregistration.
190 // Once the user is done with the provider it needs to call Close() or Dispose()
191 // If neither are called the finalizer will unregister the provider anyway
193 public void Dispose()
196 GC.SuppressFinalize(this);
199 // <SecurityKernel Critical="True" TreatAsSafe="Does not expose critical resource" Ring="1">
200 // <ReferencesCritical Name="Method: Deregister():Void" Ring="1" />
202 protected virtual void Dispose(bool disposing)
205 // explicit cleanup is done by calling Dispose with true from
206 // Dispose() or Close(). The disposing arguement is ignored because there
207 // are no unmanaged resources.
208 // The finalizer calls Dispose with false.
212 // check if the object has been already disposed
217 // Disable the provider.
220 // Do most of the work under a lock to avoid shutdown race.
222 long registrationHandle = 0;
223 lock (EventListener.EventListenersLock)
229 registrationHandle = m_regHandle;
234 // We do the Unregistration outside the EventListenerLock because there is a lock
235 // inside the ETW routines. This lock is taken before ETW issues commands
236 // Thus the ETW lock gets taken first and then our EventListenersLock gets taken
237 // in SendCommand(), and also here. If we called EventUnregister after taking
238 // the EventListenersLock then the take-lock order is reversed and we can have
239 // deadlocks in race conditions (dispose racing with an ETW command).
241 // We solve by Unregistering after releasing the EventListenerLock.
242 if (registrationHandle != 0)
243 EventUnregister(registrationHandle);
248 /// This method deregisters the controlGuid of this class with ETW.
251 public virtual void Close()
261 // <SecurityKernel Critical="True" Ring="0">
262 // <UsesUnsafeCode Name="Parameter filterData of type: Void*" />
263 // <UsesUnsafeCode Name="Parameter callbackContext of type: Void*" />
265 unsafe void EtwEnableCallBack(
266 in System.Guid sourceId,
271 Interop.Advapi32.EVENT_FILTER_DESCRIPTOR* filterData,
272 void* callbackContext
275 // This is an optional callback API. We will therefore ignore any failures that happen as a
276 // result of turning on this provider as to not crash the app.
277 // EventSource has code to validate whether initialization it expected to occur actually occurred
280 ControllerCommand command = ControllerCommand.Update;
281 IDictionary<string, string?>? args = null;
282 bool skipFinalOnControllerCommand = false;
283 if (controlCode == Interop.Advapi32.EVENT_CONTROL_CODE_ENABLE_PROVIDER)
287 m_anyKeywordMask = anyKeyword;
288 m_allKeywordMask = allKeyword;
290 List<Tuple<SessionInfo, bool>> sessionsChanged = GetSessions();
292 // The GetSessions() logic was here to support the idea that different ETW sessions
293 // could have different user-defined filters. (I believe it is currently broken but that is another matter.)
294 // However in particular GetSessions() does not support EventPipe, only ETW, which is
295 // the immediate problem. We work-around establishing the invariant that we always get a
296 // OnControllerCallback under all circumstances, even if we can't find a delta in the
297 // ETW logic. This fixes things for the EventPipe case.
299 // All this session based logic should be reviewed and likely removed, but that is a larger
300 // change that needs more careful staging.
301 if (sessionsChanged.Count == 0)
302 sessionsChanged.Add(new Tuple<SessionInfo, bool>(new SessionInfo(0, 0), true));
304 foreach (var session in sessionsChanged)
306 int sessionChanged = session.Item1.sessionIdBit;
307 int etwSessionId = session.Item1.etwSessionId;
308 bool bEnabling = session.Item2;
310 skipFinalOnControllerCommand = true;
311 args = null; // reinitialize args for every session...
313 // if we get more than one session changed we have no way
314 // of knowing which one "filterData" belongs to
315 if (sessionsChanged.Count > 1)
318 // read filter data only when a session is being *added*
322 GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex))
324 args = new Dictionary<string, string?>(4);
325 Debug.Assert(data != null);
326 while (keyIndex < data.Length)
328 int keyEnd = FindNull(data, keyIndex);
329 int valueIdx = keyEnd + 1;
330 int valueEnd = FindNull(data, valueIdx);
331 if (valueEnd < data.Length)
333 string key = System.Text.Encoding.UTF8.GetString(data, keyIndex, keyEnd - keyIndex);
334 string value = System.Text.Encoding.UTF8.GetString(data, valueIdx, valueEnd - valueIdx);
337 keyIndex = valueEnd + 1;
341 // execute OnControllerCommand once for every session that has changed.
342 OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId);
345 else if (controlCode == Interop.Advapi32.EVENT_CONTROL_CODE_DISABLE_PROVIDER)
349 m_anyKeywordMask = 0;
350 m_allKeywordMask = 0;
351 m_liveSessions = null;
353 else if (controlCode == Interop.Advapi32.EVENT_CONTROL_CODE_CAPTURE_STATE)
355 command = ControllerCommand.SendManifest;
358 return; // per spec you ignore commands you don't recognize.
360 if (!skipFinalOnControllerCommand)
361 OnControllerCommand(command, args, 0, 0);
365 // We want to ignore any failures that happen as a result of turning on this provider as to
366 // not crash the app.
371 protected virtual void OnControllerCommand(ControllerCommand command, IDictionary<string, string?>? arguments, int sessionId, int etwSessionId) { }
372 protected EventLevel Level { get { return (EventLevel)m_level; } set { m_level = (byte)value; } }
373 protected EventKeywords MatchAnyKeyword { get { return (EventKeywords)m_anyKeywordMask; } set { m_anyKeywordMask = unchecked((long)value); } }
374 protected EventKeywords MatchAllKeyword { get { return (EventKeywords)m_allKeywordMask; } set { m_allKeywordMask = unchecked((long)value); } }
376 private static int FindNull(byte[] buffer, int idx)
378 while (idx < buffer.Length && buffer[idx] != 0)
384 /// Determines the ETW sessions that have been added and/or removed to the set of
385 /// sessions interested in the current provider. It does so by (1) enumerating over all
386 /// ETW sessions that enabled 'this.m_Guid' for the current process ID, and (2)
387 /// comparing the current list with a list it cached on the previous invocation.
389 /// The return value is a list of tuples, where the SessionInfo specifies the
390 /// ETW session that was added or remove, and the bool specifies whether the
391 /// session was added or whether it was removed from the set.
393 private List<Tuple<SessionInfo, bool>> GetSessions()
395 List<SessionInfo>? liveSessionList = null;
398 (int etwSessionId, long matchAllKeywords, ref List<SessionInfo>? sessionList) =>
399 GetSessionInfoCallback(etwSessionId, matchAllKeywords, ref sessionList),
400 ref liveSessionList);
402 List<Tuple<SessionInfo, bool>> changedSessionList = new List<Tuple<SessionInfo, bool>>();
404 // first look for sessions that have gone away (or have changed)
405 // (present in the m_liveSessions but not in the new liveSessionList)
406 if (m_liveSessions != null)
408 foreach (SessionInfo s in m_liveSessions)
411 if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 ||
412 (liveSessionList![idx].sessionIdBit != s.sessionIdBit))
413 changedSessionList.Add(Tuple.Create(s, false));
417 // next look for sessions that were created since the last callback (or have changed)
418 // (present in the new liveSessionList but not in m_liveSessions)
419 if (liveSessionList != null)
421 foreach (SessionInfo s in liveSessionList)
424 if ((idx = IndexOfSessionInList(m_liveSessions, s.etwSessionId)) < 0 ||
425 (m_liveSessions![idx].sessionIdBit != s.sessionIdBit))
426 changedSessionList.Add(Tuple.Create(s, true));
430 m_liveSessions = liveSessionList;
431 return changedSessionList;
435 /// This method is the callback used by GetSessions() when it calls into GetSessionInfo().
436 /// It updates a List{SessionInfo} based on the etwSessionId and matchAllKeywords that
437 /// GetSessionInfo() passes in.
439 private static void GetSessionInfoCallback(int etwSessionId, long matchAllKeywords,
440 ref List<SessionInfo>? sessionList)
442 uint sessionIdBitMask = (uint)SessionMask.FromEventKeywords(unchecked((ulong)matchAllKeywords));
443 // an ETW controller that specifies more than the mandated bit for our EventSource
444 // will be ignored...
445 int val = BitOperations.PopCount(sessionIdBitMask);
449 if (sessionList == null)
450 sessionList = new List<SessionInfo>(8);
454 // activity-tracing-aware etw session
455 val = BitOperations.TrailingZeroCount(sessionIdBitMask);
459 // legacy etw session
460 val = BitOperations.PopCount((uint)SessionMask.All);
463 sessionList.Add(new SessionInfo(val + 1, etwSessionId));
466 private delegate void SessionInfoCallback(int etwSessionId, long matchAllKeywords, ref List<SessionInfo>? sessionList);
469 /// This method enumerates over all active ETW sessions that have enabled 'this.m_Guid'
470 /// for the current process ID, calling 'action' for each session, and passing it the
471 /// ETW session and the 'AllKeywords' the session enabled for the current provider.
473 private unsafe void GetSessionInfo(SessionInfoCallback action, ref List<SessionInfo>? sessionList)
475 // We wish the EventSource package to be legal for Windows Store applications.
476 // Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here
477 // and use the information in the registry instead. This means that ETW controllers
478 // that do not publish their intent to the registry (basically all controllers EXCEPT
479 // TraceEventSesion) will not work properly
481 // However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus
482 // does not have this issue.
483 #if (PLATFORM_WINDOWS && (ES_SESSION_INFO || !ES_BUILD_STANDALONE))
484 int buffSize = 256; // An initial guess that probably works most of the time.
488 var space = stackalloc byte[buffSize];
492 fixed (Guid* provider = &m_providerId)
494 hr = Interop.Advapi32.EnumerateTraceGuidsEx(Interop.Advapi32.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
495 provider, sizeof(Guid), buffer, buffSize, out buffSize);
499 if (hr != Interop.Errors.ERROR_INSUFFICIENT_BUFFER)
503 var providerInfos = (Interop.Advapi32.TRACE_GUID_INFO*)buffer;
504 var providerInstance = (Interop.Advapi32.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1];
505 int processId = unchecked((int)Interop.Kernel32.GetCurrentProcessId());
506 // iterate over the instances of the EventProvider in all processes
507 for (int i = 0; i < providerInfos->InstanceCount; i++)
509 if (providerInstance->Pid == processId)
511 var enabledInfos = (Interop.Advapi32.TRACE_ENABLE_INFO*)&providerInstance[1];
512 // iterate over the list of active ETW sessions "listening" to the current provider
513 for (int j = 0; j < providerInstance->EnableCount; j++)
514 action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList);
516 if (providerInstance->NextOffset == 0)
518 Debug.Assert(0 <= providerInstance->NextOffset && providerInstance->NextOffset < buffSize);
519 var structBase = (byte*)providerInstance;
520 providerInstance = (Interop.Advapi32.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset];
523 #if !ES_BUILD_PCL && PLATFORM_WINDOWS // TODO command arguments don't work on PCL builds...
524 // This code is only used in the Nuget Package Version of EventSource. because
525 // the code above is using APIs baned from UWP apps.
527 // TODO: In addition to only working when TraceEventSession enables the provider, this code
528 // also has a problem because TraceEvent does not clean up if the registry is stale
529 // It is unclear if it is worth keeping, but for now we leave it as it does work
530 // at least some of the time.
532 // Determine our session from what is in the registry.
533 string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerName + "}";
534 if (IntPtr.Size == 8)
535 regKey = @"Software" + @"\Wow6432Node" + regKey;
537 regKey = @"Software" + regKey;
539 using (var key = Registry.LocalMachine.OpenSubKey(regKey))
543 foreach (string valueName in key.GetValueNames())
545 if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal))
547 string strId = valueName.Substring(23); // strip of the ControllerData_Session_
549 if (int.TryParse(strId, out etwSessionId))
551 #if ES_BUILD_STANDALONE
552 // we need to assert this permission for partial trust scenarios
553 (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
555 var data = key.GetValue(valueName) as byte[];
558 var dataAsString = System.Text.Encoding.UTF8.GetString(data);
559 int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal);
562 int startIdx = keywordIdx + 18;
563 int endIdx = dataAsString.IndexOf('\0', startIdx);
564 string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx);
566 if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit))
567 action(etwSessionId, 1L << keywordBit, ref sessionList);
580 /// Returns the index of the SesisonInfo from 'sessions' that has the specified 'etwSessionId'
581 /// or -1 if the value is not present.
583 private static int IndexOfSessionInList(List<SessionInfo>? sessions, int etwSessionId)
585 if (sessions == null)
587 // for non-coreclr code we could use List<T>.FindIndex(Predicate<T>), but we need this to compile
588 // on coreclr as well
589 for (int i = 0; i < sessions.Count; ++i)
590 if (sessions[i].etwSessionId == etwSessionId)
597 /// Gets any data to be passed from the controller to the provider. It starts with what is passed
598 /// into the callback, but unfortunately this data is only present for when the provider is active
599 /// at the time the controller issues the command. To allow for providers to activate after the
600 /// controller issued a command, we also check the registry and use that to get the data. The function
601 /// returns an array of bytes representing the data, the index into that byte array where the data
602 /// starts, and the command being issued associated with that data.
604 private unsafe bool GetDataFromController(int etwSessionId,
605 Interop.Advapi32.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[]? data, out int dataStart)
609 if (filterData == null)
611 #if (!ES_BUILD_PCL && !ES_BUILD_PN && PLATFORM_WINDOWS)
612 string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
613 if (IntPtr.Size == 8)
614 regKey = @"Software" + @"\Wow6432Node" + regKey;
616 regKey = @"Software" + regKey;
618 string valueName = "ControllerData_Session_" + etwSessionId.ToString(CultureInfo.InvariantCulture);
620 // we need to assert this permission for partial trust scenarios
621 #if ES_BUILD_STANDALONE
622 (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
624 using (var key = Registry.LocalMachine.OpenSubKey(regKey))
626 data = key?.GetValue(valueName, null) as byte[];
629 // We only used the persisted data from the registry for updates.
630 command = ControllerCommand.Update;
638 if (filterData->Ptr != 0 && 0 < filterData->Size && filterData->Size <= 1024)
640 data = new byte[filterData->Size];
641 Marshal.Copy((IntPtr)(void*)filterData->Ptr, data, 0, data.Length);
643 command = (ControllerCommand)filterData->Type;
647 command = ControllerCommand.Update;
652 /// IsEnabled, method used to test if provider is enabled
654 public bool IsEnabled()
660 /// IsEnabled, method used to test if event is enabled
662 /// <param name="level">
665 /// <param name="keywords">
668 public bool IsEnabled(byte level, long keywords)
671 // If not enabled at all, return false.
678 // This also covers the case of Level == 0.
679 if ((level <= m_level) ||
684 // Check if Keyword is enabled
687 if ((keywords == 0) ||
688 (((keywords & m_anyKeywordMask) != 0) &&
689 ((keywords & m_allKeywordMask) == m_allKeywordMask)))
698 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
699 public static WriteEventErrorCode GetLastWriteEventError()
705 // Helper function to set the last error on the thread
707 private static void SetLastError(WriteEventErrorCode error)
709 s_returnCode = error;
712 // <SecurityKernel Critical="True" Ring="0">
713 // <UsesUnsafeCode Name="Local intptrPtr of type: IntPtr*" />
714 // <UsesUnsafeCode Name="Local intptrPtr of type: Int32*" />
715 // <UsesUnsafeCode Name="Local longptr of type: Int64*" />
716 // <UsesUnsafeCode Name="Local uintptr of type: UInt32*" />
717 // <UsesUnsafeCode Name="Local ulongptr of type: UInt64*" />
718 // <UsesUnsafeCode Name="Local charptr of type: Char*" />
719 // <UsesUnsafeCode Name="Local byteptr of type: Byte*" />
720 // <UsesUnsafeCode Name="Local shortptr of type: Int16*" />
721 // <UsesUnsafeCode Name="Local sbyteptr of type: SByte*" />
722 // <UsesUnsafeCode Name="Local ushortptr of type: UInt16*" />
723 // <UsesUnsafeCode Name="Local floatptr of type: Single*" />
724 // <UsesUnsafeCode Name="Local doubleptr of type: Double*" />
725 // <UsesUnsafeCode Name="Local boolptr of type: Boolean*" />
726 // <UsesUnsafeCode Name="Local guidptr of type: Guid*" />
727 // <UsesUnsafeCode Name="Local decimalptr of type: Decimal*" />
728 // <UsesUnsafeCode Name="Local booleanptr of type: Boolean*" />
729 // <UsesUnsafeCode Name="Parameter dataDescriptor of type: EventData*" />
730 // <UsesUnsafeCode Name="Parameter dataBuffer of type: Byte*" />
732 private static unsafe object? EncodeObject(ref object? data, ref EventData* dataDescriptor, ref byte* dataBuffer, ref uint totalEventSize)
737 This routine is used by WriteEvent to unbox the object type and
738 to fill the passed in ETW data descriptor.
742 data - argument to be decoded
744 dataDescriptor - pointer to the descriptor to be filled (updated to point to the next empty entry)
746 dataBuffer - storage buffer for storing user data, needed because cant get the address of the object
747 (updated to point to the next empty entry)
751 null if the object is a basic type other than string or byte[]. String otherwise
756 dataDescriptor->Reserved = 0;
758 string? sRet = data as string;
759 byte[]? blobRet = null;
763 dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
765 else if ((blobRet = data as byte[]) != null)
767 // first store array length
768 *(int*)dataBuffer = blobRet.Length;
769 dataDescriptor->Ptr = (ulong)dataBuffer;
770 dataDescriptor->Size = 4;
771 totalEventSize += dataDescriptor->Size;
773 // then the array parameters
775 dataBuffer += s_basicTypeAllocationBufferSize;
776 dataDescriptor->Size = (uint)blobRet.Length;
778 else if (data is IntPtr)
780 dataDescriptor->Size = (uint)sizeof(IntPtr);
781 IntPtr* intptrPtr = (IntPtr*)dataBuffer;
782 *intptrPtr = (IntPtr)data;
783 dataDescriptor->Ptr = (ulong)intptrPtr;
785 else if (data is int)
787 dataDescriptor->Size = (uint)sizeof(int);
788 int* intptr = (int*)dataBuffer;
790 dataDescriptor->Ptr = (ulong)intptr;
792 else if (data is long)
794 dataDescriptor->Size = (uint)sizeof(long);
795 long* longptr = (long*)dataBuffer;
796 *longptr = (long)data;
797 dataDescriptor->Ptr = (ulong)longptr;
799 else if (data is uint)
801 dataDescriptor->Size = (uint)sizeof(uint);
802 uint* uintptr = (uint*)dataBuffer;
803 *uintptr = (uint)data;
804 dataDescriptor->Ptr = (ulong)uintptr;
806 else if (data is ulong)
808 dataDescriptor->Size = (uint)sizeof(ulong);
809 ulong* ulongptr = (ulong*)dataBuffer;
810 *ulongptr = (ulong)data;
811 dataDescriptor->Ptr = (ulong)ulongptr;
813 else if (data is char)
815 dataDescriptor->Size = (uint)sizeof(char);
816 char* charptr = (char*)dataBuffer;
817 *charptr = (char)data;
818 dataDescriptor->Ptr = (ulong)charptr;
820 else if (data is byte)
822 dataDescriptor->Size = (uint)sizeof(byte);
823 byte* byteptr = (byte*)dataBuffer;
824 *byteptr = (byte)data;
825 dataDescriptor->Ptr = (ulong)byteptr;
827 else if (data is short)
829 dataDescriptor->Size = (uint)sizeof(short);
830 short* shortptr = (short*)dataBuffer;
831 *shortptr = (short)data;
832 dataDescriptor->Ptr = (ulong)shortptr;
834 else if (data is sbyte)
836 dataDescriptor->Size = (uint)sizeof(sbyte);
837 sbyte* sbyteptr = (sbyte*)dataBuffer;
838 *sbyteptr = (sbyte)data;
839 dataDescriptor->Ptr = (ulong)sbyteptr;
841 else if (data is ushort)
843 dataDescriptor->Size = (uint)sizeof(ushort);
844 ushort* ushortptr = (ushort*)dataBuffer;
845 *ushortptr = (ushort)data;
846 dataDescriptor->Ptr = (ulong)ushortptr;
848 else if (data is float)
850 dataDescriptor->Size = (uint)sizeof(float);
851 float* floatptr = (float*)dataBuffer;
852 *floatptr = (float)data;
853 dataDescriptor->Ptr = (ulong)floatptr;
855 else if (data is double)
857 dataDescriptor->Size = (uint)sizeof(double);
858 double* doubleptr = (double*)dataBuffer;
859 *doubleptr = (double)data;
860 dataDescriptor->Ptr = (ulong)doubleptr;
862 else if (data is bool)
864 // WIN32 Bool is 4 bytes
865 dataDescriptor->Size = 4;
866 int* intptr = (int*)dataBuffer;
875 dataDescriptor->Ptr = (ulong)intptr;
877 else if (data is Guid)
879 dataDescriptor->Size = (uint)sizeof(Guid);
880 Guid* guidptr = (Guid*)dataBuffer;
881 *guidptr = (Guid)data;
882 dataDescriptor->Ptr = (ulong)guidptr;
884 else if (data is decimal)
886 dataDescriptor->Size = (uint)sizeof(decimal);
887 decimal* decimalptr = (decimal*)dataBuffer;
888 *decimalptr = (decimal)data;
889 dataDescriptor->Ptr = (ulong)decimalptr;
891 else if (data is DateTime)
893 const long UTCMinTicks = 504911232000000000;
894 long dateTimeTicks = 0;
895 // We cannot translate dates sooner than 1/1/1601 in UTC.
896 // To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks
897 if (((DateTime)data).Ticks > UTCMinTicks)
898 dateTimeTicks = ((DateTime)data).ToFileTimeUtc();
899 dataDescriptor->Size = (uint)sizeof(long);
900 long* longptr = (long*)dataBuffer;
901 *longptr = dateTimeTicks;
902 dataDescriptor->Ptr = (ulong)longptr;
906 if (data is System.Enum)
910 Type underlyingType = Enum.GetUnderlyingType(data.GetType());
911 if (underlyingType == typeof(ulong))
913 else if (underlyingType == typeof(long))
916 data = (int)Convert.ToInt64(data); // This handles all int/uint or below (we treat them like 32 bit ints)
919 catch { } // On wierd cases (e.g. enums of type double), give up and for compat simply tostring.
922 // To our eyes, everything else is a just a string
926 sRet = data.ToString()!;
927 dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
930 totalEventSize += dataDescriptor->Size;
934 dataBuffer += s_basicTypeAllocationBufferSize;
936 return (object?)sRet ?? (object?)blobRet;
940 /// WriteEvent, method to write a parameters with event schema properties
942 /// <param name="eventDescriptor">
943 /// Event Descriptor for this event.
945 /// <param name="activityID">
946 /// A pointer to the activity ID GUID to log
948 /// <param name="childActivityID">
949 /// childActivityID is marked as 'related' to the current activity ID.
951 /// <param name="eventPayload">
952 /// Payload for the ETW event.
954 // <SecurityKernel Critical="True" Ring="0">
955 // <CallsSuppressUnmanagedCode Name="Interop.Advapi32.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
956 // <UsesUnsafeCode Name="Local dataBuffer of type: Byte*" />
957 // <UsesUnsafeCode Name="Local pdata of type: Char*" />
958 // <UsesUnsafeCode Name="Local userData of type: EventData*" />
959 // <UsesUnsafeCode Name="Local userDataPtr of type: EventData*" />
960 // <UsesUnsafeCode Name="Local currentBuffer of type: Byte*" />
961 // <UsesUnsafeCode Name="Local v0 of type: Char*" />
962 // <UsesUnsafeCode Name="Local v1 of type: Char*" />
963 // <UsesUnsafeCode Name="Local v2 of type: Char*" />
964 // <UsesUnsafeCode Name="Local v3 of type: Char*" />
965 // <UsesUnsafeCode Name="Local v4 of type: Char*" />
966 // <UsesUnsafeCode Name="Local v5 of type: Char*" />
967 // <UsesUnsafeCode Name="Local v6 of type: Char*" />
968 // <UsesUnsafeCode Name="Local v7 of type: Char*" />
969 // <ReferencesCritical Name="Method: EncodeObject(Object&, EventData*, Byte*):String" Ring="1" />
971 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Performance-critical code")]
972 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
973 internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityID, Guid* childActivityID, params object?[] eventPayload)
975 WriteEventErrorCode status = WriteEventErrorCode.NoError;
977 if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords))
982 argCount = eventPayload.Length;
984 if (argCount > s_etwMaxNumberArguments)
986 s_returnCode = WriteEventErrorCode.TooManyArgs;
990 uint totalEventSize = 0;
993 List<int> refObjPosition = new List<int>(s_etwAPIMaxRefObjCount);
994 List<object?> dataRefObj = new List<object?>(s_etwAPIMaxRefObjCount);
995 EventData* userData = stackalloc EventData[2 * argCount];
996 for (int i = 0; i < 2 * argCount; i++)
997 userData[i] = default;
998 EventData* userDataPtr = (EventData*)userData;
999 byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize * 2 * argCount]; // Assume 16 chars for non-string argument
1000 byte* currentBuffer = dataBuffer;
1003 // The loop below goes through all the arguments and fills in the data
1004 // descriptors. For strings save the location in the dataString array.
1005 // Calculates the total size of the event by adding the data descriptor
1006 // size value set in EncodeObject method.
1008 bool hasNonStringRefArgs = false;
1009 for (index = 0; index < eventPayload.Length; index++)
1011 if (eventPayload[index] != null)
1013 object? supportedRefObj = EncodeObject(ref eventPayload[index], ref userDataPtr, ref currentBuffer, ref totalEventSize);
1015 if (supportedRefObj != null)
1017 // EncodeObject advanced userDataPtr to the next empty slot
1018 int idx = (int)(userDataPtr - userData - 1);
1019 if (!(supportedRefObj is string))
1021 if (eventPayload.Length + idx + 1 - index > s_etwMaxNumberArguments)
1023 s_returnCode = WriteEventErrorCode.TooManyArgs;
1026 hasNonStringRefArgs = true;
1028 dataRefObj.Add(supportedRefObj);
1029 refObjPosition.Add(idx);
1035 s_returnCode = WriteEventErrorCode.NullInput;
1040 // update argCount based on actual number of arguments written to 'userData'
1041 argCount = (int)(userDataPtr - userData);
1043 if (totalEventSize > s_traceEventMaximumSize)
1045 s_returnCode = WriteEventErrorCode.EventTooBig;
1049 // the optimized path (using "fixed" instead of allocating pinned GCHandles
1050 if (!hasNonStringRefArgs && (refObjIndex < s_etwAPIMaxRefObjCount))
1052 // Fast path: at most 8 string arguments
1054 // ensure we have at least s_etwAPIMaxStringCount in dataString, so that
1055 // the "fixed" statement below works
1056 while (refObjIndex < s_etwAPIMaxRefObjCount)
1058 dataRefObj.Add(null);
1063 // now fix any string arguments and set the pointer on the data descriptor
1065 fixed (char* v0 = (string?)dataRefObj[0], v1 = (string?)dataRefObj[1], v2 = (string?)dataRefObj[2], v3 = (string?)dataRefObj[3],
1066 v4 = (string?)dataRefObj[4], v5 = (string?)dataRefObj[5], v6 = (string?)dataRefObj[6], v7 = (string?)dataRefObj[7])
1068 userDataPtr = (EventData*)userData;
1069 if (dataRefObj[0] != null)
1071 userDataPtr[refObjPosition[0]].Ptr = (ulong)v0;
1073 if (dataRefObj[1] != null)
1075 userDataPtr[refObjPosition[1]].Ptr = (ulong)v1;
1077 if (dataRefObj[2] != null)
1079 userDataPtr[refObjPosition[2]].Ptr = (ulong)v2;
1081 if (dataRefObj[3] != null)
1083 userDataPtr[refObjPosition[3]].Ptr = (ulong)v3;
1085 if (dataRefObj[4] != null)
1087 userDataPtr[refObjPosition[4]].Ptr = (ulong)v4;
1089 if (dataRefObj[5] != null)
1091 userDataPtr[refObjPosition[5]].Ptr = (ulong)v5;
1093 if (dataRefObj[6] != null)
1095 userDataPtr[refObjPosition[6]].Ptr = (ulong)v6;
1097 if (dataRefObj[7] != null)
1099 userDataPtr[refObjPosition[7]].Ptr = (ulong)v7;
1102 status = m_eventProvider.EventWriteTransfer(m_regHandle, in eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData);
1107 // Slow path: use pinned handles
1108 userDataPtr = (EventData*)userData;
1110 GCHandle[] rgGCHandle = new GCHandle[refObjIndex];
1111 for (int i = 0; i < refObjIndex; ++i)
1113 // below we still use "fixed" to avoid taking dependency on the offset of the first field
1114 // in the object (the way we would need to if we used GCHandle.AddrOfPinnedObject)
1115 rgGCHandle[i] = GCHandle.Alloc(dataRefObj[i], GCHandleType.Pinned);
1116 if (dataRefObj[i] is string)
1118 fixed (char* p = (string?)dataRefObj[i])
1119 userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
1123 fixed (byte* p = (byte[]?)dataRefObj[i])
1124 userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
1128 status = m_eventProvider.EventWriteTransfer(m_regHandle, in eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData);
1130 for (int i = 0; i < refObjIndex; ++i)
1132 rgGCHandle[i].Free();
1138 if (status != WriteEventErrorCode.NoError)
1140 SetLastError(status);
1148 /// WriteEvent, method to be used by generated code on a derived class
1150 /// <param name="eventDescriptor">
1151 /// Event Descriptor for this event.
1153 /// <param name="activityID">
1154 /// A pointer to the activity ID to log
1156 /// <param name="childActivityID">
1157 /// If this event is generating a child activity (WriteEventTransfer related activity) this is child activity
1158 /// This can be null for events that do not generate a child activity.
1160 /// <param name="dataCount">
1161 /// number of event descriptors
1163 /// <param name="data">
1164 /// pointer do the event data
1166 // <SecurityKernel Critical="True" Ring="0">
1167 // <CallsSuppressUnmanagedCode Name="Interop.Advapi32.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
1168 // </SecurityKernel>
1169 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
1170 internal protected unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityID, Guid* childActivityID, int dataCount, IntPtr data)
1172 if (childActivityID != null)
1174 // activity transfers are supported only for events that specify the Send or Receive opcode
1175 Debug.Assert((EventOpcode)eventDescriptor.Opcode == EventOpcode.Send ||
1176 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Receive ||
1177 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Start ||
1178 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Stop);
1181 WriteEventErrorCode status = m_eventProvider.EventWriteTransfer(m_regHandle, in eventDescriptor, eventHandle, activityID, childActivityID, dataCount, (EventData*)data);
1185 SetLastError(status);
1191 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
1192 internal unsafe bool WriteEventRaw(
1193 ref EventDescriptor eventDescriptor,
1196 Guid* relatedActivityID,
1200 WriteEventErrorCode status;
1202 status = m_eventProvider.EventWriteTransfer(
1211 if (status != WriteEventErrorCode.NoError)
1213 SetLastError(status);
1221 // These are look-alikes to the Manifest based ETW OS APIs that have been shimmed to work
1222 // either with Manifest ETW or Classic ETW (if Manifest based ETW is not available).
1223 private unsafe uint EventRegister(EventSource eventSource, Interop.Advapi32.EtwEnableCallback enableCallback)
1225 m_providerName = eventSource.Name;
1226 m_providerId = eventSource.Guid;
1227 m_etwCallback = enableCallback;
1228 return m_eventProvider.EventRegister(eventSource, enableCallback, null, ref m_regHandle);
1231 private uint EventUnregister(long registrationHandle)
1233 return m_eventProvider.EventUnregister(registrationHandle);
1236 #if PLATFORM_WINDOWS
1237 private static bool m_setInformationMissing;
1239 internal unsafe int SetInformation(
1240 Interop.Advapi32.EVENT_INFO_CLASS eventInfoClass,
1244 int status = Interop.Errors.ERROR_NOT_SUPPORTED;
1246 if (!m_setInformationMissing)
1250 status = Interop.Advapi32.EventSetInformation(
1256 catch (TypeLoadException)
1258 m_setInformationMissing = true;
1267 #if PLATFORM_WINDOWS
1269 // A wrapper around the ETW-specific API calls.
1270 internal sealed class EtwEventProvider : IEventProvider
1272 // Register an event provider.
1273 unsafe uint IEventProvider.EventRegister(
1274 EventSource eventSource,
1275 Interop.Advapi32.EtwEnableCallback enableCallback,
1276 void* callbackContext,
1277 ref long registrationHandle)
1279 Guid providerId = eventSource.Guid;
1280 return Interop.Advapi32.EventRegister(
1284 ref registrationHandle);
1287 // Unregister an event provider.
1288 uint IEventProvider.EventUnregister(long registrationHandle)
1290 return Interop.Advapi32.EventUnregister(registrationHandle);
1294 unsafe EventProvider.WriteEventErrorCode IEventProvider.EventWriteTransfer(
1295 long registrationHandle,
1296 in EventDescriptor eventDescriptor,
1299 Guid* relatedActivityId,
1301 EventProvider.EventData* userData)
1303 int error = Interop.Advapi32.EventWriteTransfer(
1313 case Interop.Errors.ERROR_ARITHMETIC_OVERFLOW:
1314 case Interop.Errors.ERROR_MORE_DATA:
1315 return EventProvider.WriteEventErrorCode.EventTooBig;
1316 case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
1317 return EventProvider.WriteEventErrorCode.NoFreeBuffers;
1320 return EventProvider.WriteEventErrorCode.NoError;
1323 // Get or set the per-thread activity ID.
1324 int IEventProvider.EventActivityIdControl(Interop.Advapi32.ActivityControl ControlCode, ref Guid ActivityId)
1326 return Interop.Advapi32.EventActivityIdControl(
1331 // Define an EventPipeEvent handle.
1332 unsafe IntPtr IEventProvider.DefineEventHandle(uint eventID, string eventName, long keywords, uint eventVersion, uint level, byte* pMetadata, uint metadataLength)
1334 throw new System.NotSupportedException();
1339 internal sealed class NoOpEventProvider : IEventProvider
1341 unsafe uint IEventProvider.EventRegister(
1342 EventSource eventSource,
1343 Interop.Advapi32.EtwEnableCallback enableCallback,
1344 void* callbackContext,
1345 ref long registrationHandle)
1350 uint IEventProvider.EventUnregister(long registrationHandle)
1355 unsafe EventProvider.WriteEventErrorCode IEventProvider.EventWriteTransfer(
1356 long registrationHandle,
1357 in EventDescriptor eventDescriptor,
1360 Guid* relatedActivityId,
1362 EventProvider.EventData* userData)
1364 return EventProvider.WriteEventErrorCode.NoError;
1367 int IEventProvider.EventActivityIdControl(Interop.Advapi32.ActivityControl ControlCode, ref Guid ActivityId)
1372 // Define an EventPipeEvent handle.
1373 unsafe IntPtr IEventProvider.DefineEventHandle(uint eventID, string eventName, long keywords, uint eventVersion, uint level, byte* pMetadata, uint metadataLength)