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.
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Runtime.InteropServices;
10 using System.Security;
11 #if !CORECLR && !ES_BUILD_PN
12 using System.Security.Permissions;
13 #endif // !CORECLR && !ES_BUILD_PN
14 using System.Threading;
17 #if !ES_BUILD_AGAINST_DOTNET_V35
18 using Contract = System.Diagnostics.Contracts.Contract;
20 using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
23 #if ES_BUILD_AGAINST_DOTNET_V35
24 using Microsoft.Internal; // for Tuple (can't define alias for open generic types so we "use" the whole namespace)
27 #if ES_BUILD_STANDALONE
28 namespace Microsoft.Diagnostics.Tracing
30 namespace System.Diagnostics.Tracing
34 internal enum ControllerCommand
36 // Strictly Positive numbers are for provider-specific commands, negative number are for 'shared' commands. 256
37 // The first 256 negative numbers are reserved for the framework.
38 Update = 0, // Not used by EventPrividerBase.
45 /// Only here because System.Diagnostics.EventProvider needs one more extensibility hook (when it gets a
46 /// controller callback)
48 #if !CORECLR && !ES_BUILD_PN
49 [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
50 #endif // !CORECLR && !ES_BUILD_PN
51 internal partial class EventProvider : IDisposable
53 // This is the windows EVENT_DATA_DESCRIPTOR structure. We expose it because this is what
54 // subclasses of EventProvider use when creating efficient (but unsafe) version of
55 // EventWrite. We do make it a nested type because we really don't expect anyone to use
56 // it except subclasses (and then only rarely).
57 public struct EventData
59 internal unsafe ulong Ptr;
61 internal uint Reserved;
65 /// A struct characterizing ETW sessions (identified by the etwSessionId) as
66 /// activity-tracing-aware or legacy. A session that's activity-tracing-aware
67 /// has specified one non-zero bit in the reserved range 44-47 in the
68 /// 'allKeywords' value it passed in for a specific EventProvider.
70 public struct SessionInfo
72 internal int sessionIdBit; // the index of the bit used for tracing in the "reserved" field of AllKeywords
73 internal int etwSessionId; // the machine-wide ETW session ID
75 internal SessionInfo(int sessionIdBit_, int etwSessionId_)
76 { sessionIdBit = sessionIdBit_; etwSessionId = etwSessionId_; }
79 private static bool m_setInformationMissing;
81 private IEventProvider m_eventProvider; // The interface that implements the specific logging mechanism functions.
82 UnsafeNativeMethods.ManifestEtw.EtwEnableCallback m_etwCallback; // Trace Callback function
83 private long m_regHandle; // Trace Registration Handle
84 private byte m_level; // Tracing Level
85 private long m_anyKeywordMask; // Trace Enable Flags
86 private long m_allKeywordMask; // Match all keyword
87 private List<SessionInfo> m_liveSessions; // current live sessions (Tuple<sessionIdBit, etwSessionId>)
88 private bool m_enabled; // Enabled flag from Trace callback
89 private Guid m_providerId; // Control Guid
90 internal bool m_disposed; // when true provider has unregistered
93 private static WriteEventErrorCode s_returnCode; // The last return code
95 private const int s_basicTypeAllocationBufferSize = 16;
96 private const int s_etwMaxNumberArguments = 128;
97 private const int s_etwAPIMaxRefObjCount = 8;
98 private const int s_maxEventDataDescriptors = 128;
99 private const int s_traceEventMaximumSize = 65482;
100 private const int s_traceEventMaximumStringSize = 32724;
102 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
103 public enum WriteEventErrorCode : int
105 //check mapping to runtime codes
114 // Because callbacks happen on registration, and we need the callbacks for those setup
115 // we can't call Register in the constructor.
117 // Note that EventProvider should ONLY be used by EventSource. In particular because
118 // it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise
119 // you may get native callbacks during shutdown when we have destroyed the delegate.
120 // EventSource has special logic to do this, no one else should be calling EventProvider.
121 internal EventProvider()
124 m_eventProvider = new EtwEventProvider();
125 #elif FEATURE_PERFTRACING
126 m_eventProvider = new EventPipeEventProvider();
131 /// This method registers the controlGuid of this class with ETW. We need to be running on
132 /// Vista or above. If not a PlatformNotSupported exception will be thrown. If for some
133 /// reason the ETW Register call failed a NotSupported exception will be thrown.
135 // <SecurityKernel Critical="True" Ring="0">
136 // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventRegister(System.Guid&,Microsoft.Win32.UnsafeNativeMethods.ManifestEtw+EtwEnableCallback,System.Void*,System.Int64&):System.UInt32" />
137 // <SatisfiesLinkDemand Name="Win32Exception..ctor(System.Int32)" />
138 // <ReferencesCritical Name="Method: EtwEnableCallBack(Guid&, Int32, Byte, Int64, Int64, Void*, Void*):Void" Ring="1" />
140 internal unsafe void Register(Guid providerGuid)
142 m_providerId = providerGuid;
144 m_etwCallback = new UnsafeNativeMethods.ManifestEtw.EtwEnableCallback(EtwEnableCallBack);
146 status = EventRegister(ref m_providerId, m_etwCallback);
149 throw new ArgumentException(Win32Native.GetMessage(unchecked((int)status)));
154 // implement Dispose Pattern to early deregister from ETW insted of waiting for
155 // the finalizer to call deregistration.
156 // Once the user is done with the provider it needs to call Close() or Dispose()
157 // If neither are called the finalizer will unregister the provider anyway
159 public void Dispose()
162 GC.SuppressFinalize(this);
165 // <SecurityKernel Critical="True" TreatAsSafe="Does not expose critical resource" Ring="1">
166 // <ReferencesCritical Name="Method: Deregister():Void" Ring="1" />
168 protected virtual void Dispose(bool disposing)
171 // explicit cleanup is done by calling Dispose with true from
172 // Dispose() or Close(). The disposing arguement is ignored because there
173 // are no unmanaged resources.
174 // The finalizer calls Dispose with false.
178 // check if the object has been allready disposed
180 if (m_disposed) return;
182 // Disable the provider.
185 // Do most of the work under a lock to avoid shutdown race.
187 long registrationHandle = 0;
188 lock (EventListener.EventListenersLock)
194 registrationHandle = m_regHandle;
199 // We do the Unregistration outside the EventListenerLock because there is a lock
200 // inside the ETW routines. This lock is taken before ETW issues commands
201 // Thus the ETW lock gets taken first and then our EventListenersLock gets taken
202 // in SendCommand(), and also here. If we called EventUnregister after taking
203 // the EventListenersLock then the take-lock order is reversed and we can have
204 // deadlocks in race conditions (dispose racing with an ETW command).
206 // We solve by Unregistering after releasing the EventListenerLock.
207 if (registrationHandle != 0)
208 EventUnregister(registrationHandle);
213 /// This method deregisters the controlGuid of this class with ETW.
216 public virtual void Close()
226 // <SecurityKernel Critical="True" Ring="0">
227 // <UsesUnsafeCode Name="Parameter filterData of type: Void*" />
228 // <UsesUnsafeCode Name="Parameter callbackContext of type: Void*" />
230 unsafe void EtwEnableCallBack(
231 [In] ref System.Guid sourceId,
232 [In] int controlCode,
234 [In] long anyKeyword,
235 [In] long allKeyword,
236 [In] UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData,
237 [In] void* callbackContext
240 // This is an optional callback API. We will therefore ignore any failures that happen as a
241 // result of turning on this provider as to not crash the app.
242 // EventSource has code to validate whether initialization it expected to occur actually occurred
245 ControllerCommand command = ControllerCommand.Update;
246 IDictionary<string, string> args = null;
247 bool skipFinalOnControllerCommand = false;
248 if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER)
252 m_anyKeywordMask = anyKeyword;
253 m_allKeywordMask = allKeyword;
255 // ES_SESSION_INFO is a marker for additional places we #ifdeffed out to remove
256 // references to EnumerateTraceGuidsEx. This symbol is actually not used because
257 // today we use FEATURE_ACTIVITYSAMPLING to determine if this code is there or not.
258 // However we put it in the #if so that we don't lose the fact that this feature
259 // switch is at least partially independent of FEATURE_ACTIVITYSAMPLING
261 List<Tuple<SessionInfo, bool>> sessionsChanged = GetSessions();
262 foreach (var session in sessionsChanged)
264 int sessionChanged = session.Item1.sessionIdBit;
265 int etwSessionId = session.Item1.etwSessionId;
266 bool bEnabling = session.Item2;
268 skipFinalOnControllerCommand = true;
269 args = null; // reinitialize args for every session...
271 // if we get more than one session changed we have no way
272 // of knowing which one "filterData" belongs to
273 if (sessionsChanged.Count > 1)
276 // read filter data only when a session is being *added*
280 GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex))
282 args = new Dictionary<string, string>(4);
283 while (keyIndex < data.Length)
285 int keyEnd = FindNull(data, keyIndex);
286 int valueIdx = keyEnd + 1;
287 int valueEnd = FindNull(data, valueIdx);
288 if (valueEnd < data.Length)
290 string key = System.Text.Encoding.UTF8.GetString(data, keyIndex, keyEnd - keyIndex);
291 string value = System.Text.Encoding.UTF8.GetString(data, valueIdx, valueEnd - valueIdx);
294 keyIndex = valueEnd + 1;
298 // execute OnControllerCommand once for every session that has changed.
299 OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId);
302 else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_DISABLE_PROVIDER)
306 m_anyKeywordMask = 0;
307 m_allKeywordMask = 0;
308 m_liveSessions = null;
310 else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_CAPTURE_STATE)
312 command = ControllerCommand.SendManifest;
315 return; // per spec you ignore commands you don't recognize.
317 if (!skipFinalOnControllerCommand)
318 OnControllerCommand(command, args, 0, 0);
322 // We want to ignore any failures that happen as a result of turning on this provider as to
323 // not crash the app.
328 protected virtual void OnControllerCommand(ControllerCommand command, IDictionary<string, string> arguments, int sessionId, int etwSessionId) { }
329 protected EventLevel Level { get { return (EventLevel)m_level; } set { m_level = (byte)value; } }
330 protected EventKeywords MatchAnyKeyword { get { return (EventKeywords)m_anyKeywordMask; } set { m_anyKeywordMask = unchecked((long)value); } }
331 protected EventKeywords MatchAllKeyword { get { return (EventKeywords)m_allKeywordMask; } set { m_allKeywordMask = unchecked((long)value); } }
333 static private int FindNull(byte[] buffer, int idx)
335 while (idx < buffer.Length && buffer[idx] != 0)
341 /// Determines the ETW sessions that have been added and/or removed to the set of
342 /// sessions interested in the current provider. It does so by (1) enumerating over all
343 /// ETW sessions that enabled 'this.m_Guid' for the current process ID, and (2)
344 /// comparing the current list with a list it cached on the previous invocation.
346 /// The return value is a list of tuples, where the SessionInfo specifies the
347 /// ETW session that was added or remove, and the bool specifies whether the
348 /// session was added or whether it was removed from the set.
350 private List<Tuple<SessionInfo, bool>> GetSessions()
352 List<SessionInfo> liveSessionList = null;
355 (int etwSessionId, long matchAllKeywords, ref List<SessionInfo> sessionList) =>
356 GetSessionInfoCallback(etwSessionId, matchAllKeywords, ref sessionList),
357 ref liveSessionList);
359 List<Tuple<SessionInfo, bool>> changedSessionList = new List<Tuple<SessionInfo, bool>>();
361 // first look for sessions that have gone away (or have changed)
362 // (present in the m_liveSessions but not in the new liveSessionList)
363 if (m_liveSessions != null)
365 foreach (SessionInfo s in m_liveSessions)
368 if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 ||
369 (liveSessionList[idx].sessionIdBit != s.sessionIdBit))
370 changedSessionList.Add(Tuple.Create(s, false));
374 // next look for sessions that were created since the last callback (or have changed)
375 // (present in the new liveSessionList but not in m_liveSessions)
376 if (liveSessionList != null)
378 foreach (SessionInfo s in liveSessionList)
381 if ((idx = IndexOfSessionInList(m_liveSessions, s.etwSessionId)) < 0 ||
382 (m_liveSessions[idx].sessionIdBit != s.sessionIdBit))
383 changedSessionList.Add(Tuple.Create(s, true));
387 m_liveSessions = liveSessionList;
388 return changedSessionList;
393 /// This method is the callback used by GetSessions() when it calls into GetSessionInfo().
394 /// It updates a List{SessionInfo} based on the etwSessionId and matchAllKeywords that
395 /// GetSessionInfo() passes in.
397 private static void GetSessionInfoCallback(int etwSessionId, long matchAllKeywords,
398 ref List<SessionInfo> sessionList)
400 uint sessionIdBitMask = (uint)SessionMask.FromEventKeywords(unchecked((ulong)matchAllKeywords));
401 // an ETW controller that specifies more than the mandated bit for our EventSource
402 // will be ignored...
403 if (bitcount(sessionIdBitMask) > 1)
406 if (sessionList == null)
407 sessionList = new List<SessionInfo>(8);
409 if (bitcount(sessionIdBitMask) == 1)
411 // activity-tracing-aware etw session
412 sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask) + 1, etwSessionId));
416 // legacy etw session
417 sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All) + 1, etwSessionId));
421 private delegate void SessionInfoCallback(int etwSessionId, long matchAllKeywords, ref List<SessionInfo> sessionList);
424 /// This method enumerates over all active ETW sessions that have enabled 'this.m_Guid'
425 /// for the current process ID, calling 'action' for each session, and passing it the
426 /// ETW session and the 'AllKeywords' the session enabled for the current provider.
428 private unsafe void GetSessionInfo(SessionInfoCallback action, ref List<SessionInfo> sessionList)
430 // We wish the EventSource package to be legal for Windows Store applications.
431 // Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here
432 // and use the information in the registry instead. This means that ETW controllers
433 // that do not publish their intent to the registry (basically all controllers EXCEPT
434 // TraceEventSesion) will not work properly
436 // However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus
437 // does not have this issue.
438 #if (PLATFORM_WINDOWS && (ES_SESSION_INFO || !ES_BUILD_STANDALONE))
439 int buffSize = 256; // An initial guess that probably works most of the time.
443 var space = stackalloc byte[buffSize];
447 fixed (Guid* provider = &m_providerId)
449 hr = UnsafeNativeMethods.ManifestEtw.EnumerateTraceGuidsEx(UnsafeNativeMethods.ManifestEtw.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
450 provider, sizeof(Guid), buffer, buffSize, ref buffSize);
454 if (hr != 122 /* ERROR_INSUFFICIENT_BUFFER */)
458 var providerInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_GUID_INFO*)buffer;
459 var providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1];
460 int processId = unchecked((int)Win32Native.GetCurrentProcessId());
461 // iterate over the instances of the EventProvider in all processes
462 for (int i = 0; i < providerInfos->InstanceCount; i++)
464 if (providerInstance->Pid == processId)
466 var enabledInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_ENABLE_INFO*)&providerInstance[1];
467 // iterate over the list of active ETW sessions "listening" to the current provider
468 for (int j = 0; j < providerInstance->EnableCount; j++)
469 action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList);
471 if (providerInstance->NextOffset == 0)
473 Debug.Assert(0 <= providerInstance->NextOffset && providerInstance->NextOffset < buffSize);
474 var structBase = (byte*)providerInstance;
475 providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset];
478 #if !ES_BUILD_PCL && !FEATURE_PAL // TODO command arguments don't work on PCL builds...
479 // This code is only used in the Nuget Package Version of EventSource. because
480 // the code above is using APIs baned from UWP apps.
482 // TODO: In addition to only working when TraceEventSession enables the provider, this code
483 // also has a problem because TraceEvent does not clean up if the registry is stale
484 // It is unclear if it is worth keeping, but for now we leave it as it does work
485 // at least some of the time.
487 // Determine our session from what is in the registry.
488 string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
489 if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
490 regKey = @"Software" + @"\Wow6432Node" + regKey;
492 regKey = @"Software" + regKey;
494 var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKey);
497 foreach (string valueName in key.GetValueNames())
499 if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal))
501 string strId = valueName.Substring(23); // strip of the ControllerData_Session_
503 if (int.TryParse(strId, out etwSessionId))
505 // we need to assert this permission for partial trust scenarios
506 (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
507 var data = key.GetValue(valueName) as byte[];
510 var dataAsString = System.Text.Encoding.UTF8.GetString(data);
511 int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal);
514 int startIdx = keywordIdx + 18;
515 int endIdx = dataAsString.IndexOf('\0', startIdx);
516 string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx);
518 if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit))
519 action(etwSessionId, 1L << keywordBit, ref sessionList);
531 /// Returns the index of the SesisonInfo from 'sessions' that has the specified 'etwSessionId'
532 /// or -1 if the value is not present.
534 private static int IndexOfSessionInList(List<SessionInfo> sessions, int etwSessionId)
536 if (sessions == null)
538 // for non-coreclr code we could use List<T>.FindIndex(Predicate<T>), but we need this to compile
539 // on coreclr as well
540 for (int i = 0; i < sessions.Count; ++i)
541 if (sessions[i].etwSessionId == etwSessionId)
548 /// Gets any data to be passed from the controller to the provider. It starts with what is passed
549 /// into the callback, but unfortunately this data is only present for when the provider is active
550 /// at the time the controller issues the command. To allow for providers to activate after the
551 /// controller issued a command, we also check the registry and use that to get the data. The function
552 /// returns an array of bytes representing the data, the index into that byte array where the data
553 /// starts, and the command being issued associated with that data.
555 private unsafe bool GetDataFromController(int etwSessionId,
556 UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart)
560 if (filterData == null)
562 #if (!ES_BUILD_PCL && !ES_BUILD_PN && !FEATURE_PAL)
563 string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
564 if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
565 regKey = @"HKEY_LOCAL_MACHINE\Software" + @"\Wow6432Node" + regKey;
567 regKey = @"HKEY_LOCAL_MACHINE\Software" + regKey;
569 string valueName = "ControllerData_Session_" + etwSessionId.ToString(CultureInfo.InvariantCulture);
571 // we need to assert this permission for partial trust scenarios
573 (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
575 data = Microsoft.Win32.Registry.GetValue(regKey, valueName, null) as byte[];
578 // We only used the persisted data from the registry for updates.
579 command = ControllerCommand.Update;
586 if (filterData->Ptr != 0 && 0 < filterData->Size && filterData->Size <= 1024)
588 data = new byte[filterData->Size];
589 Marshal.Copy((IntPtr)filterData->Ptr, data, 0, data.Length);
591 command = (ControllerCommand)filterData->Type;
595 command = ControllerCommand.Update;
600 /// IsEnabled, method used to test if provider is enabled
602 public bool IsEnabled()
608 /// IsEnabled, method used to test if event is enabled
610 /// <param name="level">
613 /// <param name="keywords">
616 public bool IsEnabled(byte level, long keywords)
619 // If not enabled at all, return false.
626 // This also covers the case of Level == 0.
627 if ((level <= m_level) ||
632 // Check if Keyword is enabled
635 if ((keywords == 0) ||
636 (((keywords & m_anyKeywordMask) != 0) &&
637 ((keywords & m_allKeywordMask) == m_allKeywordMask)))
646 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
647 public static WriteEventErrorCode GetLastWriteEventError()
653 // Helper function to set the last error on the thread
655 private static void SetLastError(int error)
659 case UnsafeNativeMethods.ManifestEtw.ERROR_ARITHMETIC_OVERFLOW:
660 case UnsafeNativeMethods.ManifestEtw.ERROR_MORE_DATA:
661 s_returnCode = WriteEventErrorCode.EventTooBig;
663 case UnsafeNativeMethods.ManifestEtw.ERROR_NOT_ENOUGH_MEMORY:
664 s_returnCode = WriteEventErrorCode.NoFreeBuffers;
669 // <SecurityKernel Critical="True" Ring="0">
670 // <UsesUnsafeCode Name="Local intptrPtr of type: IntPtr*" />
671 // <UsesUnsafeCode Name="Local intptrPtr of type: Int32*" />
672 // <UsesUnsafeCode Name="Local longptr of type: Int64*" />
673 // <UsesUnsafeCode Name="Local uintptr of type: UInt32*" />
674 // <UsesUnsafeCode Name="Local ulongptr of type: UInt64*" />
675 // <UsesUnsafeCode Name="Local charptr of type: Char*" />
676 // <UsesUnsafeCode Name="Local byteptr of type: Byte*" />
677 // <UsesUnsafeCode Name="Local shortptr of type: Int16*" />
678 // <UsesUnsafeCode Name="Local sbyteptr of type: SByte*" />
679 // <UsesUnsafeCode Name="Local ushortptr of type: UInt16*" />
680 // <UsesUnsafeCode Name="Local floatptr of type: Single*" />
681 // <UsesUnsafeCode Name="Local doubleptr of type: Double*" />
682 // <UsesUnsafeCode Name="Local boolptr of type: Boolean*" />
683 // <UsesUnsafeCode Name="Local guidptr of type: Guid*" />
684 // <UsesUnsafeCode Name="Local decimalptr of type: Decimal*" />
685 // <UsesUnsafeCode Name="Local booleanptr of type: Boolean*" />
686 // <UsesUnsafeCode Name="Parameter dataDescriptor of type: EventData*" />
687 // <UsesUnsafeCode Name="Parameter dataBuffer of type: Byte*" />
689 private static unsafe object EncodeObject(ref object data, ref EventData* dataDescriptor, ref byte* dataBuffer, ref uint totalEventSize)
694 This routine is used by WriteEvent to unbox the object type and
695 to fill the passed in ETW data descriptor.
699 data - argument to be decoded
701 dataDescriptor - pointer to the descriptor to be filled (updated to point to the next empty entry)
703 dataBuffer - storage buffer for storing user data, needed because cant get the address of the object
704 (updated to point to the next empty entry)
708 null if the object is a basic type other than string or byte[]. String otherwise
713 dataDescriptor->Reserved = 0;
715 string sRet = data as string;
716 byte[] blobRet = null;
720 dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
722 else if ((blobRet = data as byte[]) != null)
724 // first store array length
725 *(int*)dataBuffer = blobRet.Length;
726 dataDescriptor->Ptr = (ulong)dataBuffer;
727 dataDescriptor->Size = 4;
728 totalEventSize += dataDescriptor->Size;
730 // then the array parameters
732 dataBuffer += s_basicTypeAllocationBufferSize;
733 dataDescriptor->Size = (uint)blobRet.Length;
735 else if (data is IntPtr)
737 dataDescriptor->Size = (uint)sizeof(IntPtr);
738 IntPtr* intptrPtr = (IntPtr*)dataBuffer;
739 *intptrPtr = (IntPtr)data;
740 dataDescriptor->Ptr = (ulong)intptrPtr;
742 else if (data is int)
744 dataDescriptor->Size = (uint)sizeof(int);
745 int* intptr = (int*)dataBuffer;
747 dataDescriptor->Ptr = (ulong)intptr;
749 else if (data is long)
751 dataDescriptor->Size = (uint)sizeof(long);
752 long* longptr = (long*)dataBuffer;
753 *longptr = (long)data;
754 dataDescriptor->Ptr = (ulong)longptr;
756 else if (data is uint)
758 dataDescriptor->Size = (uint)sizeof(uint);
759 uint* uintptr = (uint*)dataBuffer;
760 *uintptr = (uint)data;
761 dataDescriptor->Ptr = (ulong)uintptr;
763 else if (data is UInt64)
765 dataDescriptor->Size = (uint)sizeof(ulong);
766 UInt64* ulongptr = (ulong*)dataBuffer;
767 *ulongptr = (ulong)data;
768 dataDescriptor->Ptr = (ulong)ulongptr;
770 else if (data is char)
772 dataDescriptor->Size = (uint)sizeof(char);
773 char* charptr = (char*)dataBuffer;
774 *charptr = (char)data;
775 dataDescriptor->Ptr = (ulong)charptr;
777 else if (data is byte)
779 dataDescriptor->Size = (uint)sizeof(byte);
780 byte* byteptr = (byte*)dataBuffer;
781 *byteptr = (byte)data;
782 dataDescriptor->Ptr = (ulong)byteptr;
784 else if (data is short)
786 dataDescriptor->Size = (uint)sizeof(short);
787 short* shortptr = (short*)dataBuffer;
788 *shortptr = (short)data;
789 dataDescriptor->Ptr = (ulong)shortptr;
791 else if (data is sbyte)
793 dataDescriptor->Size = (uint)sizeof(sbyte);
794 sbyte* sbyteptr = (sbyte*)dataBuffer;
795 *sbyteptr = (sbyte)data;
796 dataDescriptor->Ptr = (ulong)sbyteptr;
798 else if (data is ushort)
800 dataDescriptor->Size = (uint)sizeof(ushort);
801 ushort* ushortptr = (ushort*)dataBuffer;
802 *ushortptr = (ushort)data;
803 dataDescriptor->Ptr = (ulong)ushortptr;
805 else if (data is float)
807 dataDescriptor->Size = (uint)sizeof(float);
808 float* floatptr = (float*)dataBuffer;
809 *floatptr = (float)data;
810 dataDescriptor->Ptr = (ulong)floatptr;
812 else if (data is double)
814 dataDescriptor->Size = (uint)sizeof(double);
815 double* doubleptr = (double*)dataBuffer;
816 *doubleptr = (double)data;
817 dataDescriptor->Ptr = (ulong)doubleptr;
819 else if (data is bool)
821 // WIN32 Bool is 4 bytes
822 dataDescriptor->Size = 4;
823 int* intptr = (int*)dataBuffer;
832 dataDescriptor->Ptr = (ulong)intptr;
834 else if (data is Guid)
836 dataDescriptor->Size = (uint)sizeof(Guid);
837 Guid* guidptr = (Guid*)dataBuffer;
838 *guidptr = (Guid)data;
839 dataDescriptor->Ptr = (ulong)guidptr;
841 else if (data is decimal)
843 dataDescriptor->Size = (uint)sizeof(decimal);
844 decimal* decimalptr = (decimal*)dataBuffer;
845 *decimalptr = (decimal)data;
846 dataDescriptor->Ptr = (ulong)decimalptr;
848 else if (data is DateTime)
850 const long UTCMinTicks = 504911232000000000;
851 long dateTimeTicks = 0;
852 // We cannot translate dates sooner than 1/1/1601 in UTC.
853 // To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks
854 if (((DateTime)data).Ticks > UTCMinTicks)
855 dateTimeTicks = ((DateTime)data).ToFileTimeUtc();
856 dataDescriptor->Size = (uint)sizeof(long);
857 long* longptr = (long*)dataBuffer;
858 *longptr = dateTimeTicks;
859 dataDescriptor->Ptr = (ulong)longptr;
863 if (data is System.Enum)
865 Type underlyingType = Enum.GetUnderlyingType(data.GetType());
866 if (underlyingType == typeof(int))
869 data = ((IConvertible)data).ToInt32(null);
875 else if (underlyingType == typeof(long))
878 data = ((IConvertible)data).ToInt64(null);
886 // To our eyes, everything else is a just a string
890 sRet = data.ToString();
891 dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
894 totalEventSize += dataDescriptor->Size;
898 dataBuffer += s_basicTypeAllocationBufferSize;
900 return (object)sRet ?? (object)blobRet;
904 /// WriteEvent, method to write a parameters with event schema properties
906 /// <param name="eventDescriptor">
907 /// Event Descriptor for this event.
909 /// <param name="activityID">
910 /// A pointer to the activity ID GUID to log
912 /// <param name="childActivityID">
913 /// childActivityID is marked as 'related' to the current activity ID.
915 /// <param name="eventPayload">
916 /// Payload for the ETW event.
918 // <SecurityKernel Critical="True" Ring="0">
919 // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
920 // <UsesUnsafeCode Name="Local dataBuffer of type: Byte*" />
921 // <UsesUnsafeCode Name="Local pdata of type: Char*" />
922 // <UsesUnsafeCode Name="Local userData of type: EventData*" />
923 // <UsesUnsafeCode Name="Local userDataPtr of type: EventData*" />
924 // <UsesUnsafeCode Name="Local currentBuffer of type: Byte*" />
925 // <UsesUnsafeCode Name="Local v0 of type: Char*" />
926 // <UsesUnsafeCode Name="Local v1 of type: Char*" />
927 // <UsesUnsafeCode Name="Local v2 of type: Char*" />
928 // <UsesUnsafeCode Name="Local v3 of type: Char*" />
929 // <UsesUnsafeCode Name="Local v4 of type: Char*" />
930 // <UsesUnsafeCode Name="Local v5 of type: Char*" />
931 // <UsesUnsafeCode Name="Local v6 of type: Char*" />
932 // <UsesUnsafeCode Name="Local v7 of type: Char*" />
933 // <ReferencesCritical Name="Method: EncodeObject(Object&, EventData*, Byte*):String" Ring="1" />
935 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Performance-critical code")]
936 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
937 internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, params object[] eventPayload)
941 if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords))
946 argCount = eventPayload.Length;
948 if (argCount > s_etwMaxNumberArguments)
950 s_returnCode = WriteEventErrorCode.TooManyArgs;
954 uint totalEventSize = 0;
957 List<int> refObjPosition = new List<int>(s_etwAPIMaxRefObjCount);
958 List<object> dataRefObj = new List<object>(s_etwAPIMaxRefObjCount);
959 EventData* userData = stackalloc EventData[2 * argCount];
960 EventData* userDataPtr = (EventData*)userData;
961 byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize * 2 * argCount]; // Assume 16 chars for non-string argument
962 byte* currentBuffer = dataBuffer;
965 // The loop below goes through all the arguments and fills in the data
966 // descriptors. For strings save the location in the dataString array.
967 // Calculates the total size of the event by adding the data descriptor
968 // size value set in EncodeObject method.
970 bool hasNonStringRefArgs = false;
971 for (index = 0; index < eventPayload.Length; index++)
973 if (eventPayload[index] != null)
975 object supportedRefObj;
976 supportedRefObj = EncodeObject(ref eventPayload[index], ref userDataPtr, ref currentBuffer, ref totalEventSize);
978 if (supportedRefObj != null)
980 // EncodeObject advanced userDataPtr to the next empty slot
981 int idx = (int)(userDataPtr - userData - 1);
982 if (!(supportedRefObj is string))
984 if (eventPayload.Length + idx + 1 - index > s_etwMaxNumberArguments)
986 s_returnCode = WriteEventErrorCode.TooManyArgs;
989 hasNonStringRefArgs = true;
991 dataRefObj.Add(supportedRefObj);
992 refObjPosition.Add(idx);
998 s_returnCode = WriteEventErrorCode.NullInput;
1003 // update argCount based on actual number of arguments written to 'userData'
1004 argCount = (int)(userDataPtr - userData);
1006 if (totalEventSize > s_traceEventMaximumSize)
1008 s_returnCode = WriteEventErrorCode.EventTooBig;
1012 // the optimized path (using "fixed" instead of allocating pinned GCHandles
1013 if (!hasNonStringRefArgs && (refObjIndex < s_etwAPIMaxRefObjCount))
1015 // Fast path: at most 8 string arguments
1017 // ensure we have at least s_etwAPIMaxStringCount in dataString, so that
1018 // the "fixed" statement below works
1019 while (refObjIndex < s_etwAPIMaxRefObjCount)
1021 dataRefObj.Add(null);
1026 // now fix any string arguments and set the pointer on the data descriptor
1028 fixed (char* v0 = (string)dataRefObj[0], v1 = (string)dataRefObj[1], v2 = (string)dataRefObj[2], v3 = (string)dataRefObj[3],
1029 v4 = (string)dataRefObj[4], v5 = (string)dataRefObj[5], v6 = (string)dataRefObj[6], v7 = (string)dataRefObj[7])
1031 userDataPtr = (EventData*)userData;
1032 if (dataRefObj[0] != null)
1034 userDataPtr[refObjPosition[0]].Ptr = (ulong)v0;
1036 if (dataRefObj[1] != null)
1038 userDataPtr[refObjPosition[1]].Ptr = (ulong)v1;
1040 if (dataRefObj[2] != null)
1042 userDataPtr[refObjPosition[2]].Ptr = (ulong)v2;
1044 if (dataRefObj[3] != null)
1046 userDataPtr[refObjPosition[3]].Ptr = (ulong)v3;
1048 if (dataRefObj[4] != null)
1050 userDataPtr[refObjPosition[4]].Ptr = (ulong)v4;
1052 if (dataRefObj[5] != null)
1054 userDataPtr[refObjPosition[5]].Ptr = (ulong)v5;
1056 if (dataRefObj[6] != null)
1058 userDataPtr[refObjPosition[6]].Ptr = (ulong)v6;
1060 if (dataRefObj[7] != null)
1062 userDataPtr[refObjPosition[7]].Ptr = (ulong)v7;
1065 status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, argCount, userData);
1070 // Slow path: use pinned handles
1071 userDataPtr = (EventData*)userData;
1073 GCHandle[] rgGCHandle = new GCHandle[refObjIndex];
1074 for (int i = 0; i < refObjIndex; ++i)
1076 // below we still use "fixed" to avoid taking dependency on the offset of the first field
1077 // in the object (the way we would need to if we used GCHandle.AddrOfPinnedObject)
1078 rgGCHandle[i] = GCHandle.Alloc(dataRefObj[i], GCHandleType.Pinned);
1079 if (dataRefObj[i] is string)
1081 fixed (char* p = (string)dataRefObj[i])
1082 userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
1086 fixed (byte* p = (byte[])dataRefObj[i])
1087 userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
1091 status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, argCount, userData);
1093 for (int i = 0; i < refObjIndex; ++i)
1095 rgGCHandle[i].Free();
1103 SetLastError((int)status);
1111 /// WriteEvent, method to be used by generated code on a derived class
1113 /// <param name="eventDescriptor">
1114 /// Event Descriptor for this event.
1116 /// <param name="activityID">
1117 /// A pointer to the activity ID to log
1119 /// <param name="childActivityID">
1120 /// If this event is generating a child activity (WriteEventTransfer related activity) this is child activity
1121 /// This can be null for events that do not generate a child activity.
1123 /// <param name="dataCount">
1124 /// number of event descriptors
1126 /// <param name="data">
1127 /// pointer do the event data
1129 // <SecurityKernel Critical="True" Ring="0">
1130 // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
1131 // </SecurityKernel>
1132 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
1133 internal unsafe protected bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, int dataCount, IntPtr data)
1135 if (childActivityID != null)
1137 // activity transfers are supported only for events that specify the Send or Receive opcode
1138 Debug.Assert((EventOpcode)eventDescriptor.Opcode == EventOpcode.Send ||
1139 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Receive ||
1140 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Start ||
1141 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Stop);
1144 int status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, dataCount, (EventData*)data);
1148 SetLastError(status);
1154 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
1155 internal unsafe bool WriteEventRaw(
1156 ref EventDescriptor eventDescriptor,
1158 Guid* relatedActivityID,
1164 status = m_eventProvider.EventWriteTransferWrapper(
1166 ref eventDescriptor,
1174 SetLastError(status);
1181 // These are look-alikes to the Manifest based ETW OS APIs that have been shimmed to work
1182 // either with Manifest ETW or Classic ETW (if Manifest based ETW is not available).
1183 private unsafe uint EventRegister(ref Guid providerId, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback)
1185 m_providerId = providerId;
1186 m_etwCallback = enableCallback;
1187 return m_eventProvider.EventRegister(ref providerId, enableCallback, null, ref m_regHandle);
1190 private uint EventUnregister(long registrationHandle)
1192 return m_eventProvider.EventUnregister(registrationHandle);
1195 static int[] nibblebits = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
1196 private static int bitcount(uint n)
1199 for (; n != 0; n = n >> 4)
1200 count += nibblebits[n & 0x0f];
1203 private static int bitindex(uint n)
1205 Debug.Assert(bitcount(n) == 1);
1207 while ((n & (1 << idx)) == 0)
1213 #if PLATFORM_WINDOWS
1215 // A wrapper around the ETW-specific API calls.
1216 internal sealed class EtwEventProvider : IEventProvider
1218 // Register an event provider.
1219 unsafe uint IEventProvider.EventRegister(
1220 ref Guid providerId,
1221 UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback,
1222 void* callbackContext,
1223 ref long registrationHandle)
1225 return UnsafeNativeMethods.ManifestEtw.EventRegister(
1229 ref registrationHandle);
1232 // Unregister an event provider.
1233 uint IEventProvider.EventUnregister(long registrationHandle)
1235 return UnsafeNativeMethods.ManifestEtw.EventUnregister(registrationHandle);
1239 unsafe int IEventProvider.EventWriteTransferWrapper(
1240 long registrationHandle,
1241 ref EventDescriptor eventDescriptor,
1243 Guid* relatedActivityId,
1245 EventProvider.EventData* userData)
1247 return UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(
1249 ref eventDescriptor,
1256 // Get or set the per-thread activity ID.
1257 int IEventProvider.EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId)
1259 return UnsafeNativeMethods.ManifestEtw.EventActivityIdControl(