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 UnsafeNativeMethods.ManifestEtw.EtwEnableCallback m_etwCallback; // Trace Callback function
82 private long m_regHandle; // Trace Registration Handle
83 private byte m_level; // Tracing Level
84 private long m_anyKeywordMask; // Trace Enable Flags
85 private long m_allKeywordMask; // Match all keyword
86 private List<SessionInfo> m_liveSessions; // current live sessions (Tuple<sessionIdBit, etwSessionId>)
87 private bool m_enabled; // Enabled flag from Trace callback
88 private Guid m_providerId; // Control Guid
89 internal bool m_disposed; // when true provider has unregistered
92 private static WriteEventErrorCode s_returnCode; // The last return code
94 private const int s_basicTypeAllocationBufferSize = 16;
95 private const int s_etwMaxNumberArguments = 128;
96 private const int s_etwAPIMaxRefObjCount = 8;
97 private const int s_maxEventDataDescriptors = 128;
98 private const int s_traceEventMaximumSize = 65482;
99 private const int s_traceEventMaximumStringSize = 32724;
101 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
102 public enum WriteEventErrorCode : int
104 //check mapping to runtime codes
113 // Because callbacks happen on registration, and we need the callbacks for those setup
114 // we can't call Register in the constructor.
116 // Note that EventProvider should ONLY be used by EventSource. In particular because
117 // it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise
118 // you may get native callbacks during shutdown when we have destroyed the delegate.
119 // EventSource has special logic to do this, no one else should be calling EventProvider.
120 internal EventProvider()
125 /// This method registers the controlGuid of this class with ETW. We need to be running on
126 /// Vista or above. If not a PlatformNotSupported exception will be thrown. If for some
127 /// reason the ETW Register call failed a NotSupported exception will be thrown.
129 // <SecurityKernel Critical="True" Ring="0">
130 // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventRegister(System.Guid&,Microsoft.Win32.UnsafeNativeMethods.ManifestEtw+EtwEnableCallback,System.Void*,System.Int64&):System.UInt32" />
131 // <SatisfiesLinkDemand Name="Win32Exception..ctor(System.Int32)" />
132 // <ReferencesCritical Name="Method: EtwEnableCallBack(Guid&, Int32, Byte, Int64, Int64, Void*, Void*):Void" Ring="1" />
134 internal unsafe void Register(Guid providerGuid)
136 m_providerId = providerGuid;
138 m_etwCallback = new UnsafeNativeMethods.ManifestEtw.EtwEnableCallback(EtwEnableCallBack);
140 status = EventRegister(ref m_providerId, m_etwCallback);
143 throw new ArgumentException(Win32Native.GetMessage(unchecked((int)status)));
148 // implement Dispose Pattern to early deregister from ETW insted of waiting for
149 // the finalizer to call deregistration.
150 // Once the user is done with the provider it needs to call Close() or Dispose()
151 // If neither are called the finalizer will unregister the provider anyway
153 public void Dispose()
156 GC.SuppressFinalize(this);
159 // <SecurityKernel Critical="True" TreatAsSafe="Does not expose critical resource" Ring="1">
160 // <ReferencesCritical Name="Method: Deregister():Void" Ring="1" />
162 protected virtual void Dispose(bool disposing)
165 // explicit cleanup is done by calling Dispose with true from
166 // Dispose() or Close(). The disposing arguement is ignored because there
167 // are no unmanaged resources.
168 // The finalizer calls Dispose with false.
172 // check if the object has been allready disposed
174 if (m_disposed) return;
176 // Disable the provider.
179 // Do most of the work under a lock to avoid shutdown race.
181 long registrationHandle = 0;
182 lock (EventListener.EventListenersLock)
188 registrationHandle = m_regHandle;
193 // We do the Unregistration outside the EventListenerLock because there is a lock
194 // inside the ETW routines. This lock is taken before ETW issues commands
195 // Thus the ETW lock gets taken first and then our EventListenersLock gets taken
196 // in SendCommand(), and also here. If we called EventUnregister after taking
197 // the EventListenersLock then the take-lock order is reversed and we can have
198 // deadlocks in race conditions (dispose racing with an ETW command).
200 // We solve by Unregistering after releasing the EventListenerLock.
201 if (registrationHandle != 0)
202 EventUnregister(registrationHandle);
207 /// This method deregisters the controlGuid of this class with ETW.
210 public virtual void Close()
220 // <SecurityKernel Critical="True" Ring="0">
221 // <UsesUnsafeCode Name="Parameter filterData of type: Void*" />
222 // <UsesUnsafeCode Name="Parameter callbackContext of type: Void*" />
224 unsafe void EtwEnableCallBack(
225 [In] ref System.Guid sourceId,
226 [In] int controlCode,
228 [In] long anyKeyword,
229 [In] long allKeyword,
230 [In] UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData,
231 [In] void* callbackContext
234 // This is an optional callback API. We will therefore ignore any failures that happen as a
235 // result of turning on this provider as to not crash the app.
236 // EventSource has code to validate whether initialization it expected to occur actually occurred
239 ControllerCommand command = ControllerCommand.Update;
240 IDictionary<string, string> args = null;
241 bool skipFinalOnControllerCommand = false;
242 if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER)
246 m_anyKeywordMask = anyKeyword;
247 m_allKeywordMask = allKeyword;
249 // ES_SESSION_INFO is a marker for additional places we #ifdeffed out to remove
250 // references to EnumerateTraceGuidsEx. This symbol is actually not used because
251 // today we use FEATURE_ACTIVITYSAMPLING to determine if this code is there or not.
252 // However we put it in the #if so that we don't lose the fact that this feature
253 // switch is at least partially independent of FEATURE_ACTIVITYSAMPLING
255 List<Tuple<SessionInfo, bool>> sessionsChanged = GetSessions();
256 foreach (var session in sessionsChanged)
258 int sessionChanged = session.Item1.sessionIdBit;
259 int etwSessionId = session.Item1.etwSessionId;
260 bool bEnabling = session.Item2;
262 skipFinalOnControllerCommand = true;
263 args = null; // reinitialize args for every session...
265 // if we get more than one session changed we have no way
266 // of knowing which one "filterData" belongs to
267 if (sessionsChanged.Count > 1)
270 // read filter data only when a session is being *added*
274 GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex))
276 args = new Dictionary<string, string>(4);
277 while (keyIndex < data.Length)
279 int keyEnd = FindNull(data, keyIndex);
280 int valueIdx = keyEnd + 1;
281 int valueEnd = FindNull(data, valueIdx);
282 if (valueEnd < data.Length)
284 string key = System.Text.Encoding.UTF8.GetString(data, keyIndex, keyEnd - keyIndex);
285 string value = System.Text.Encoding.UTF8.GetString(data, valueIdx, valueEnd - valueIdx);
288 keyIndex = valueEnd + 1;
292 // execute OnControllerCommand once for every session that has changed.
293 OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId);
296 else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_DISABLE_PROVIDER)
300 m_anyKeywordMask = 0;
301 m_allKeywordMask = 0;
302 m_liveSessions = null;
304 else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_CAPTURE_STATE)
306 command = ControllerCommand.SendManifest;
309 return; // per spec you ignore commands you don't recognize.
311 if (!skipFinalOnControllerCommand)
312 OnControllerCommand(command, args, 0, 0);
316 // We want to ignore any failures that happen as a result of turning on this provider as to
317 // not crash the app.
322 protected virtual void OnControllerCommand(ControllerCommand command, IDictionary<string, string> arguments, int sessionId, int etwSessionId) { }
323 protected EventLevel Level { get { return (EventLevel)m_level; } set { m_level = (byte)value; } }
324 protected EventKeywords MatchAnyKeyword { get { return (EventKeywords)m_anyKeywordMask; } set { m_anyKeywordMask = unchecked((long)value); } }
325 protected EventKeywords MatchAllKeyword { get { return (EventKeywords)m_allKeywordMask; } set { m_allKeywordMask = unchecked((long)value); } }
327 static private int FindNull(byte[] buffer, int idx)
329 while (idx < buffer.Length && buffer[idx] != 0)
335 /// Determines the ETW sessions that have been added and/or removed to the set of
336 /// sessions interested in the current provider. It does so by (1) enumerating over all
337 /// ETW sessions that enabled 'this.m_Guid' for the current process ID, and (2)
338 /// comparing the current list with a list it cached on the previous invocation.
340 /// The return value is a list of tuples, where the SessionInfo specifies the
341 /// ETW session that was added or remove, and the bool specifies whether the
342 /// session was added or whether it was removed from the set.
344 private List<Tuple<SessionInfo, bool>> GetSessions()
346 List<SessionInfo> liveSessionList = null;
349 (int etwSessionId, long matchAllKeywords, ref List<SessionInfo> sessionList) =>
350 GetSessionInfoCallback(etwSessionId, matchAllKeywords, ref sessionList),
351 ref liveSessionList);
353 List<Tuple<SessionInfo, bool>> changedSessionList = new List<Tuple<SessionInfo, bool>>();
355 // first look for sessions that have gone away (or have changed)
356 // (present in the m_liveSessions but not in the new liveSessionList)
357 if (m_liveSessions != null)
359 foreach (SessionInfo s in m_liveSessions)
362 if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 ||
363 (liveSessionList[idx].sessionIdBit != s.sessionIdBit))
364 changedSessionList.Add(Tuple.Create(s, false));
368 // next look for sessions that were created since the last callback (or have changed)
369 // (present in the new liveSessionList but not in m_liveSessions)
370 if (liveSessionList != null)
372 foreach (SessionInfo s in liveSessionList)
375 if ((idx = IndexOfSessionInList(m_liveSessions, s.etwSessionId)) < 0 ||
376 (m_liveSessions[idx].sessionIdBit != s.sessionIdBit))
377 changedSessionList.Add(Tuple.Create(s, true));
381 m_liveSessions = liveSessionList;
382 return changedSessionList;
387 /// This method is the callback used by GetSessions() when it calls into GetSessionInfo().
388 /// It updates a List{SessionInfo} based on the etwSessionId and matchAllKeywords that
389 /// GetSessionInfo() passes in.
391 private static void GetSessionInfoCallback(int etwSessionId, long matchAllKeywords,
392 ref List<SessionInfo> sessionList)
394 uint sessionIdBitMask = (uint)SessionMask.FromEventKeywords(unchecked((ulong)matchAllKeywords));
395 // an ETW controller that specifies more than the mandated bit for our EventSource
396 // will be ignored...
397 if (bitcount(sessionIdBitMask) > 1)
400 if (sessionList == null)
401 sessionList = new List<SessionInfo>(8);
403 if (bitcount(sessionIdBitMask) == 1)
405 // activity-tracing-aware etw session
406 sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask) + 1, etwSessionId));
410 // legacy etw session
411 sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All) + 1, etwSessionId));
415 private delegate void SessionInfoCallback(int etwSessionId, long matchAllKeywords, ref List<SessionInfo> sessionList);
418 /// This method enumerates over all active ETW sessions that have enabled 'this.m_Guid'
419 /// for the current process ID, calling 'action' for each session, and passing it the
420 /// ETW session and the 'AllKeywords' the session enabled for the current provider.
422 private unsafe void GetSessionInfo(SessionInfoCallback action, ref List<SessionInfo> sessionList)
424 // We wish the EventSource package to be legal for Windows Store applications.
425 // Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here
426 // and use the information in the registry instead. This means that ETW controllers
427 // that do not publish their intent to the registry (basically all controllers EXCEPT
428 // TraceEventSesion) will not work properly
430 // However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus
431 // does not have this issue.
432 #if ES_SESSION_INFO || !ES_BUILD_STANDALONE
433 int buffSize = 256; // An initial guess that probably works most of the time.
437 var space = stackalloc byte[buffSize];
441 fixed (Guid* provider = &m_providerId)
443 hr = UnsafeNativeMethods.ManifestEtw.EnumerateTraceGuidsEx(UnsafeNativeMethods.ManifestEtw.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
444 provider, sizeof(Guid), buffer, buffSize, ref buffSize);
448 if (hr != 122 /* ERROR_INSUFFICIENT_BUFFER */)
452 var providerInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_GUID_INFO*)buffer;
453 var providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1];
454 int processId = unchecked((int)Win32Native.GetCurrentProcessId());
455 // iterate over the instances of the EventProvider in all processes
456 for (int i = 0; i < providerInfos->InstanceCount; i++)
458 if (providerInstance->Pid == processId)
460 var enabledInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_ENABLE_INFO*)&providerInstance[1];
461 // iterate over the list of active ETW sessions "listening" to the current provider
462 for (int j = 0; j < providerInstance->EnableCount; j++)
463 action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList);
465 if (providerInstance->NextOffset == 0)
467 Debug.Assert(0 <= providerInstance->NextOffset && providerInstance->NextOffset < buffSize);
468 var structBase = (byte*)providerInstance;
469 providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset];
472 #if !ES_BUILD_PCL && !FEATURE_PAL // TODO command arguments don't work on PCL builds...
473 // This code is only used in the Nuget Package Version of EventSource. because
474 // the code above is using APIs baned from UWP apps.
476 // TODO: In addition to only working when TraceEventSession enables the provider, this code
477 // also has a problem because TraceEvent does not clean up if the registry is stale
478 // It is unclear if it is worth keeping, but for now we leave it as it does work
479 // at least some of the time.
481 // Determine our session from what is in the registry.
482 string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
483 if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
484 regKey = @"Software" + @"\Wow6432Node" + regKey;
486 regKey = @"Software" + regKey;
488 var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKey);
491 foreach (string valueName in key.GetValueNames())
493 if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal))
495 string strId = valueName.Substring(23); // strip of the ControllerData_Session_
497 if (int.TryParse(strId, out etwSessionId))
499 // we need to assert this permission for partial trust scenarios
500 (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
501 var data = key.GetValue(valueName) as byte[];
504 var dataAsString = System.Text.Encoding.UTF8.GetString(data);
505 int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal);
508 int startIdx = keywordIdx + 18;
509 int endIdx = dataAsString.IndexOf('\0', startIdx);
510 string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx);
512 if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit))
513 action(etwSessionId, 1L << keywordBit, ref sessionList);
525 /// Returns the index of the SesisonInfo from 'sessions' that has the specified 'etwSessionId'
526 /// or -1 if the value is not present.
528 private static int IndexOfSessionInList(List<SessionInfo> sessions, int etwSessionId)
530 if (sessions == null)
532 // for non-coreclr code we could use List<T>.FindIndex(Predicate<T>), but we need this to compile
533 // on coreclr as well
534 for (int i = 0; i < sessions.Count; ++i)
535 if (sessions[i].etwSessionId == etwSessionId)
542 /// Gets any data to be passed from the controller to the provider. It starts with what is passed
543 /// into the callback, but unfortunately this data is only present for when the provider is active
544 /// at the time the controller issues the command. To allow for providers to activate after the
545 /// controller issued a command, we also check the registry and use that to get the data. The function
546 /// returns an array of bytes representing the data, the index into that byte array where the data
547 /// starts, and the command being issued associated with that data.
549 private unsafe bool GetDataFromController(int etwSessionId,
550 UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart)
554 if (filterData == null)
556 #if (!ES_BUILD_PCL && !ES_BUILD_PN && !FEATURE_PAL)
557 string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
558 if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
559 regKey = @"HKEY_LOCAL_MACHINE\Software" + @"\Wow6432Node" + regKey;
561 regKey = @"HKEY_LOCAL_MACHINE\Software" + regKey;
563 string valueName = "ControllerData_Session_" + etwSessionId.ToString(CultureInfo.InvariantCulture);
565 // we need to assert this permission for partial trust scenarios
567 (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
569 data = Microsoft.Win32.Registry.GetValue(regKey, valueName, null) as byte[];
572 // We only used the persisted data from the registry for updates.
573 command = ControllerCommand.Update;
580 if (filterData->Ptr != 0 && 0 < filterData->Size && filterData->Size <= 1024)
582 data = new byte[filterData->Size];
583 Marshal.Copy((IntPtr)filterData->Ptr, data, 0, data.Length);
585 command = (ControllerCommand)filterData->Type;
589 command = ControllerCommand.Update;
594 /// IsEnabled, method used to test if provider is enabled
596 public bool IsEnabled()
602 /// IsEnabled, method used to test if event is enabled
604 /// <param name="level">
607 /// <param name="keywords">
610 public bool IsEnabled(byte level, long keywords)
613 // If not enabled at all, return false.
620 // This also covers the case of Level == 0.
621 if ((level <= m_level) ||
626 // Check if Keyword is enabled
629 if ((keywords == 0) ||
630 (((keywords & m_anyKeywordMask) != 0) &&
631 ((keywords & m_allKeywordMask) == m_allKeywordMask)))
640 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
641 public static WriteEventErrorCode GetLastWriteEventError()
647 // Helper function to set the last error on the thread
649 private static void SetLastError(int error)
653 case UnsafeNativeMethods.ManifestEtw.ERROR_ARITHMETIC_OVERFLOW:
654 case UnsafeNativeMethods.ManifestEtw.ERROR_MORE_DATA:
655 s_returnCode = WriteEventErrorCode.EventTooBig;
657 case UnsafeNativeMethods.ManifestEtw.ERROR_NOT_ENOUGH_MEMORY:
658 s_returnCode = WriteEventErrorCode.NoFreeBuffers;
663 // <SecurityKernel Critical="True" Ring="0">
664 // <UsesUnsafeCode Name="Local intptrPtr of type: IntPtr*" />
665 // <UsesUnsafeCode Name="Local intptrPtr of type: Int32*" />
666 // <UsesUnsafeCode Name="Local longptr of type: Int64*" />
667 // <UsesUnsafeCode Name="Local uintptr of type: UInt32*" />
668 // <UsesUnsafeCode Name="Local ulongptr of type: UInt64*" />
669 // <UsesUnsafeCode Name="Local charptr of type: Char*" />
670 // <UsesUnsafeCode Name="Local byteptr of type: Byte*" />
671 // <UsesUnsafeCode Name="Local shortptr of type: Int16*" />
672 // <UsesUnsafeCode Name="Local sbyteptr of type: SByte*" />
673 // <UsesUnsafeCode Name="Local ushortptr of type: UInt16*" />
674 // <UsesUnsafeCode Name="Local floatptr of type: Single*" />
675 // <UsesUnsafeCode Name="Local doubleptr of type: Double*" />
676 // <UsesUnsafeCode Name="Local boolptr of type: Boolean*" />
677 // <UsesUnsafeCode Name="Local guidptr of type: Guid*" />
678 // <UsesUnsafeCode Name="Local decimalptr of type: Decimal*" />
679 // <UsesUnsafeCode Name="Local booleanptr of type: Boolean*" />
680 // <UsesUnsafeCode Name="Parameter dataDescriptor of type: EventData*" />
681 // <UsesUnsafeCode Name="Parameter dataBuffer of type: Byte*" />
683 private static unsafe object EncodeObject(ref object data, ref EventData* dataDescriptor, ref byte* dataBuffer, ref uint totalEventSize)
688 This routine is used by WriteEvent to unbox the object type and
689 to fill the passed in ETW data descriptor.
693 data - argument to be decoded
695 dataDescriptor - pointer to the descriptor to be filled (updated to point to the next empty entry)
697 dataBuffer - storage buffer for storing user data, needed because cant get the address of the object
698 (updated to point to the next empty entry)
702 null if the object is a basic type other than string or byte[]. String otherwise
707 dataDescriptor->Reserved = 0;
709 string sRet = data as string;
710 byte[] blobRet = null;
714 dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
716 else if ((blobRet = data as byte[]) != null)
718 // first store array length
719 *(int*)dataBuffer = blobRet.Length;
720 dataDescriptor->Ptr = (ulong)dataBuffer;
721 dataDescriptor->Size = 4;
722 totalEventSize += dataDescriptor->Size;
724 // then the array parameters
726 dataBuffer += s_basicTypeAllocationBufferSize;
727 dataDescriptor->Size = (uint)blobRet.Length;
729 else if (data is IntPtr)
731 dataDescriptor->Size = (uint)sizeof(IntPtr);
732 IntPtr* intptrPtr = (IntPtr*)dataBuffer;
733 *intptrPtr = (IntPtr)data;
734 dataDescriptor->Ptr = (ulong)intptrPtr;
736 else if (data is int)
738 dataDescriptor->Size = (uint)sizeof(int);
739 int* intptr = (int*)dataBuffer;
741 dataDescriptor->Ptr = (ulong)intptr;
743 else if (data is long)
745 dataDescriptor->Size = (uint)sizeof(long);
746 long* longptr = (long*)dataBuffer;
747 *longptr = (long)data;
748 dataDescriptor->Ptr = (ulong)longptr;
750 else if (data is uint)
752 dataDescriptor->Size = (uint)sizeof(uint);
753 uint* uintptr = (uint*)dataBuffer;
754 *uintptr = (uint)data;
755 dataDescriptor->Ptr = (ulong)uintptr;
757 else if (data is UInt64)
759 dataDescriptor->Size = (uint)sizeof(ulong);
760 UInt64* ulongptr = (ulong*)dataBuffer;
761 *ulongptr = (ulong)data;
762 dataDescriptor->Ptr = (ulong)ulongptr;
764 else if (data is char)
766 dataDescriptor->Size = (uint)sizeof(char);
767 char* charptr = (char*)dataBuffer;
768 *charptr = (char)data;
769 dataDescriptor->Ptr = (ulong)charptr;
771 else if (data is byte)
773 dataDescriptor->Size = (uint)sizeof(byte);
774 byte* byteptr = (byte*)dataBuffer;
775 *byteptr = (byte)data;
776 dataDescriptor->Ptr = (ulong)byteptr;
778 else if (data is short)
780 dataDescriptor->Size = (uint)sizeof(short);
781 short* shortptr = (short*)dataBuffer;
782 *shortptr = (short)data;
783 dataDescriptor->Ptr = (ulong)shortptr;
785 else if (data is sbyte)
787 dataDescriptor->Size = (uint)sizeof(sbyte);
788 sbyte* sbyteptr = (sbyte*)dataBuffer;
789 *sbyteptr = (sbyte)data;
790 dataDescriptor->Ptr = (ulong)sbyteptr;
792 else if (data is ushort)
794 dataDescriptor->Size = (uint)sizeof(ushort);
795 ushort* ushortptr = (ushort*)dataBuffer;
796 *ushortptr = (ushort)data;
797 dataDescriptor->Ptr = (ulong)ushortptr;
799 else if (data is float)
801 dataDescriptor->Size = (uint)sizeof(float);
802 float* floatptr = (float*)dataBuffer;
803 *floatptr = (float)data;
804 dataDescriptor->Ptr = (ulong)floatptr;
806 else if (data is double)
808 dataDescriptor->Size = (uint)sizeof(double);
809 double* doubleptr = (double*)dataBuffer;
810 *doubleptr = (double)data;
811 dataDescriptor->Ptr = (ulong)doubleptr;
813 else if (data is bool)
815 // WIN32 Bool is 4 bytes
816 dataDescriptor->Size = 4;
817 int* intptr = (int*)dataBuffer;
826 dataDescriptor->Ptr = (ulong)intptr;
828 else if (data is Guid)
830 dataDescriptor->Size = (uint)sizeof(Guid);
831 Guid* guidptr = (Guid*)dataBuffer;
832 *guidptr = (Guid)data;
833 dataDescriptor->Ptr = (ulong)guidptr;
835 else if (data is decimal)
837 dataDescriptor->Size = (uint)sizeof(decimal);
838 decimal* decimalptr = (decimal*)dataBuffer;
839 *decimalptr = (decimal)data;
840 dataDescriptor->Ptr = (ulong)decimalptr;
842 else if (data is DateTime)
844 const long UTCMinTicks = 504911232000000000;
845 long dateTimeTicks = 0;
846 // We cannot translate dates sooner than 1/1/1601 in UTC.
847 // To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks
848 if (((DateTime)data).Ticks > UTCMinTicks)
849 dateTimeTicks = ((DateTime)data).ToFileTimeUtc();
850 dataDescriptor->Size = (uint)sizeof(long);
851 long* longptr = (long*)dataBuffer;
852 *longptr = dateTimeTicks;
853 dataDescriptor->Ptr = (ulong)longptr;
857 if (data is System.Enum)
859 Type underlyingType = Enum.GetUnderlyingType(data.GetType());
860 if (underlyingType == typeof(int))
863 data = ((IConvertible)data).ToInt32(null);
869 else if (underlyingType == typeof(long))
872 data = ((IConvertible)data).ToInt64(null);
880 // To our eyes, everything else is a just a string
884 sRet = data.ToString();
885 dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
888 totalEventSize += dataDescriptor->Size;
892 dataBuffer += s_basicTypeAllocationBufferSize;
894 return (object)sRet ?? (object)blobRet;
898 /// WriteEvent, method to write a parameters with event schema properties
900 /// <param name="eventDescriptor">
901 /// Event Descriptor for this event.
903 /// <param name="activityID">
904 /// A pointer to the activity ID GUID to log
906 /// <param name="childActivityID">
907 /// childActivityID is marked as 'related' to the current activity ID.
909 /// <param name="eventPayload">
910 /// Payload for the ETW event.
912 // <SecurityKernel Critical="True" Ring="0">
913 // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
914 // <UsesUnsafeCode Name="Local dataBuffer of type: Byte*" />
915 // <UsesUnsafeCode Name="Local pdata of type: Char*" />
916 // <UsesUnsafeCode Name="Local userData of type: EventData*" />
917 // <UsesUnsafeCode Name="Local userDataPtr of type: EventData*" />
918 // <UsesUnsafeCode Name="Local currentBuffer of type: Byte*" />
919 // <UsesUnsafeCode Name="Local v0 of type: Char*" />
920 // <UsesUnsafeCode Name="Local v1 of type: Char*" />
921 // <UsesUnsafeCode Name="Local v2 of type: Char*" />
922 // <UsesUnsafeCode Name="Local v3 of type: Char*" />
923 // <UsesUnsafeCode Name="Local v4 of type: Char*" />
924 // <UsesUnsafeCode Name="Local v5 of type: Char*" />
925 // <UsesUnsafeCode Name="Local v6 of type: Char*" />
926 // <UsesUnsafeCode Name="Local v7 of type: Char*" />
927 // <ReferencesCritical Name="Method: EncodeObject(Object&, EventData*, Byte*):String" Ring="1" />
929 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Performance-critical code")]
930 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
931 internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, params object[] eventPayload)
935 if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords))
940 argCount = eventPayload.Length;
942 if (argCount > s_etwMaxNumberArguments)
944 s_returnCode = WriteEventErrorCode.TooManyArgs;
948 uint totalEventSize = 0;
951 List<int> refObjPosition = new List<int>(s_etwAPIMaxRefObjCount);
952 List<object> dataRefObj = new List<object>(s_etwAPIMaxRefObjCount);
953 EventData* userData = stackalloc EventData[2 * argCount];
954 EventData* userDataPtr = (EventData*)userData;
955 byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize * 2 * argCount]; // Assume 16 chars for non-string argument
956 byte* currentBuffer = dataBuffer;
959 // The loop below goes through all the arguments and fills in the data
960 // descriptors. For strings save the location in the dataString array.
961 // Calculates the total size of the event by adding the data descriptor
962 // size value set in EncodeObject method.
964 bool hasNonStringRefArgs = false;
965 for (index = 0; index < eventPayload.Length; index++)
967 if (eventPayload[index] != null)
969 object supportedRefObj;
970 supportedRefObj = EncodeObject(ref eventPayload[index], ref userDataPtr, ref currentBuffer, ref totalEventSize);
972 if (supportedRefObj != null)
974 // EncodeObject advanced userDataPtr to the next empty slot
975 int idx = (int)(userDataPtr - userData - 1);
976 if (!(supportedRefObj is string))
978 if (eventPayload.Length + idx + 1 - index > s_etwMaxNumberArguments)
980 s_returnCode = WriteEventErrorCode.TooManyArgs;
983 hasNonStringRefArgs = true;
985 dataRefObj.Add(supportedRefObj);
986 refObjPosition.Add(idx);
992 s_returnCode = WriteEventErrorCode.NullInput;
997 // update argCount based on actual number of arguments written to 'userData'
998 argCount = (int)(userDataPtr - userData);
1000 if (totalEventSize > s_traceEventMaximumSize)
1002 s_returnCode = WriteEventErrorCode.EventTooBig;
1006 // the optimized path (using "fixed" instead of allocating pinned GCHandles
1007 if (!hasNonStringRefArgs && (refObjIndex < s_etwAPIMaxRefObjCount))
1009 // Fast path: at most 8 string arguments
1011 // ensure we have at least s_etwAPIMaxStringCount in dataString, so that
1012 // the "fixed" statement below works
1013 while (refObjIndex < s_etwAPIMaxRefObjCount)
1015 dataRefObj.Add(null);
1020 // now fix any string arguments and set the pointer on the data descriptor
1022 fixed (char* v0 = (string)dataRefObj[0], v1 = (string)dataRefObj[1], v2 = (string)dataRefObj[2], v3 = (string)dataRefObj[3],
1023 v4 = (string)dataRefObj[4], v5 = (string)dataRefObj[5], v6 = (string)dataRefObj[6], v7 = (string)dataRefObj[7])
1025 userDataPtr = (EventData*)userData;
1026 if (dataRefObj[0] != null)
1028 userDataPtr[refObjPosition[0]].Ptr = (ulong)v0;
1030 if (dataRefObj[1] != null)
1032 userDataPtr[refObjPosition[1]].Ptr = (ulong)v1;
1034 if (dataRefObj[2] != null)
1036 userDataPtr[refObjPosition[2]].Ptr = (ulong)v2;
1038 if (dataRefObj[3] != null)
1040 userDataPtr[refObjPosition[3]].Ptr = (ulong)v3;
1042 if (dataRefObj[4] != null)
1044 userDataPtr[refObjPosition[4]].Ptr = (ulong)v4;
1046 if (dataRefObj[5] != null)
1048 userDataPtr[refObjPosition[5]].Ptr = (ulong)v5;
1050 if (dataRefObj[6] != null)
1052 userDataPtr[refObjPosition[6]].Ptr = (ulong)v6;
1054 if (dataRefObj[7] != null)
1056 userDataPtr[refObjPosition[7]].Ptr = (ulong)v7;
1059 status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, argCount, userData);
1064 // Slow path: use pinned handles
1065 userDataPtr = (EventData*)userData;
1067 GCHandle[] rgGCHandle = new GCHandle[refObjIndex];
1068 for (int i = 0; i < refObjIndex; ++i)
1070 // below we still use "fixed" to avoid taking dependency on the offset of the first field
1071 // in the object (the way we would need to if we used GCHandle.AddrOfPinnedObject)
1072 rgGCHandle[i] = GCHandle.Alloc(dataRefObj[i], GCHandleType.Pinned);
1073 if (dataRefObj[i] is string)
1075 fixed (char* p = (string)dataRefObj[i])
1076 userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
1080 fixed (byte* p = (byte[])dataRefObj[i])
1081 userDataPtr[refObjPosition[i]].Ptr = (ulong)p;
1085 status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, argCount, userData);
1087 for (int i = 0; i < refObjIndex; ++i)
1089 rgGCHandle[i].Free();
1097 SetLastError((int)status);
1105 /// WriteEvent, method to be used by generated code on a derived class
1107 /// <param name="eventDescriptor">
1108 /// Event Descriptor for this event.
1110 /// <param name="activityID">
1111 /// A pointer to the activity ID to log
1113 /// <param name="childActivityID">
1114 /// If this event is generating a child activity (WriteEventTransfer related activity) this is child activity
1115 /// This can be null for events that do not generate a child activity.
1117 /// <param name="dataCount">
1118 /// number of event descriptors
1120 /// <param name="data">
1121 /// pointer do the event data
1123 // <SecurityKernel Critical="True" Ring="0">
1124 // <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventWrite(System.Int64,EventDescriptor&,System.UInt32,System.Void*):System.UInt32" />
1125 // </SecurityKernel>
1126 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
1127 internal unsafe protected bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, int dataCount, IntPtr data)
1129 if (childActivityID != null)
1131 // activity transfers are supported only for events that specify the Send or Receive opcode
1132 Debug.Assert((EventOpcode)eventDescriptor.Opcode == EventOpcode.Send ||
1133 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Receive ||
1134 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Start ||
1135 (EventOpcode)eventDescriptor.Opcode == EventOpcode.Stop);
1138 int status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, dataCount, (EventData*)data);
1142 SetLastError(status);
1148 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
1149 internal unsafe bool WriteEventRaw(
1150 ref EventDescriptor eventDescriptor,
1152 Guid* relatedActivityID,
1158 status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(
1160 ref eventDescriptor,
1168 SetLastError(status);
1175 // These are look-alikes to the Manifest based ETW OS APIs that have been shimmed to work
1176 // either with Manifest ETW or Classic ETW (if Manifest based ETW is not available).
1177 private unsafe uint EventRegister(ref Guid providerId, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback)
1179 m_providerId = providerId;
1180 m_etwCallback = enableCallback;
1181 return UnsafeNativeMethods.ManifestEtw.EventRegister(ref providerId, enableCallback, null, ref m_regHandle);
1184 private uint EventUnregister(long registrationHandle)
1186 return UnsafeNativeMethods.ManifestEtw.EventUnregister(registrationHandle);
1189 static int[] nibblebits = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
1190 private static int bitcount(uint n)
1193 for (; n != 0; n = n >> 4)
1194 count += nibblebits[n & 0x0f];
1197 private static int bitindex(uint n)
1199 Debug.Assert(bitcount(n) == 1);
1201 while ((n & (1 << idx)) == 0)