// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Win32;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security;
#if !CORECLR && !ES_BUILD_PN
using System.Security.Permissions;
#endif // !CORECLR && !ES_BUILD_PN
using System.Threading;
using System;
#if !ES_BUILD_AGAINST_DOTNET_V35
using Contract = System.Diagnostics.Contracts.Contract;
#else
using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
#endif
#if ES_BUILD_AGAINST_DOTNET_V35
using Microsoft.Internal; // for Tuple (can't define alias for open generic types so we "use" the whole namespace)
#endif
#if ES_BUILD_STANDALONE
namespace Microsoft.Diagnostics.Tracing
#else
namespace System.Diagnostics.Tracing
#endif
{
// New in CLR4.0
internal enum ControllerCommand
{
// Strictly Positive numbers are for provider-specific commands, negative number are for 'shared' commands. 256
// The first 256 negative numbers are reserved for the framework.
Update = 0, // Not used by EventPrividerBase.
SendManifest = -1,
Enable = -2,
Disable = -3,
};
///
/// Only here because System.Diagnostics.EventProvider needs one more extensibility hook (when it gets a
/// controller callback)
///
#if !CORECLR && !ES_BUILD_PN
[System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
#endif // !CORECLR && !ES_BUILD_PN
internal partial class EventProvider : IDisposable
{
// This is the windows EVENT_DATA_DESCRIPTOR structure. We expose it because this is what
// subclasses of EventProvider use when creating efficient (but unsafe) version of
// EventWrite. We do make it a nested type because we really don't expect anyone to use
// it except subclasses (and then only rarely).
public struct EventData
{
internal unsafe ulong Ptr;
internal uint Size;
internal uint Reserved;
}
///
/// A struct characterizing ETW sessions (identified by the etwSessionId) as
/// activity-tracing-aware or legacy. A session that's activity-tracing-aware
/// has specified one non-zero bit in the reserved range 44-47 in the
/// 'allKeywords' value it passed in for a specific EventProvider.
///
public struct SessionInfo
{
internal int sessionIdBit; // the index of the bit used for tracing in the "reserved" field of AllKeywords
internal int etwSessionId; // the machine-wide ETW session ID
internal SessionInfo(int sessionIdBit_, int etwSessionId_)
{ sessionIdBit = sessionIdBit_; etwSessionId = etwSessionId_; }
}
private static bool m_setInformationMissing;
private IEventProvider m_eventProvider; // The interface that implements the specific logging mechanism functions.
UnsafeNativeMethods.ManifestEtw.EtwEnableCallback m_etwCallback; // Trace Callback function
private long m_regHandle; // Trace Registration Handle
private byte m_level; // Tracing Level
private long m_anyKeywordMask; // Trace Enable Flags
private long m_allKeywordMask; // Match all keyword
private List m_liveSessions; // current live sessions (Tuple)
private bool m_enabled; // Enabled flag from Trace callback
private Guid m_providerId; // Control Guid
internal bool m_disposed; // when true provider has unregistered
[ThreadStatic]
private static WriteEventErrorCode s_returnCode; // The last return code
private const int s_basicTypeAllocationBufferSize = 16;
private const int s_etwMaxNumberArguments = 128;
private const int s_etwAPIMaxRefObjCount = 8;
private const int s_maxEventDataDescriptors = 128;
private const int s_traceEventMaximumSize = 65482;
private const int s_traceEventMaximumStringSize = 32724;
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
public enum WriteEventErrorCode : int
{
//check mapping to runtime codes
NoError = 0,
NoFreeBuffers = 1,
EventTooBig = 2,
NullInput = 3,
TooManyArgs = 4,
Other = 5,
};
// Because callbacks happen on registration, and we need the callbacks for those setup
// we can't call Register in the constructor.
//
// Note that EventProvider should ONLY be used by EventSource. In particular because
// it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise
// you may get native callbacks during shutdown when we have destroyed the delegate.
// EventSource has special logic to do this, no one else should be calling EventProvider.
internal EventProvider()
{
#if PLATFORM_WINDOWS
m_eventProvider = new EtwEventProvider();
#elif FEATURE_PERFTRACING
m_eventProvider = new EventPipeEventProvider();
#endif
}
///
/// This method registers the controlGuid of this class with ETW. We need to be running on
/// Vista or above. If not a PlatformNotSupported exception will be thrown. If for some
/// reason the ETW Register call failed a NotSupported exception will be thrown.
///
//
//
//
//
//
internal unsafe void Register(Guid providerGuid)
{
m_providerId = providerGuid;
uint status;
m_etwCallback = new UnsafeNativeMethods.ManifestEtw.EtwEnableCallback(EtwEnableCallBack);
status = EventRegister(ref m_providerId, m_etwCallback);
if (status != 0)
{
throw new ArgumentException(Win32Native.GetMessage(unchecked((int)status)));
}
}
//
// implement Dispose Pattern to early deregister from ETW insted of waiting for
// the finalizer to call deregistration.
// Once the user is done with the provider it needs to call Close() or Dispose()
// If neither are called the finalizer will unregister the provider anyway
//
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
//
//
//
protected virtual void Dispose(bool disposing)
{
//
// explicit cleanup is done by calling Dispose with true from
// Dispose() or Close(). The disposing arguement is ignored because there
// are no unmanaged resources.
// The finalizer calls Dispose with false.
//
//
// check if the object has been allready disposed
//
if (m_disposed) return;
// Disable the provider.
m_enabled = false;
// Do most of the work under a lock to avoid shutdown race.
long registrationHandle = 0;
lock (EventListener.EventListenersLock)
{
// Double check
if (m_disposed)
return;
registrationHandle = m_regHandle;
m_regHandle = 0;
m_disposed = true;
}
// We do the Unregistration outside the EventListenerLock because there is a lock
// inside the ETW routines. This lock is taken before ETW issues commands
// Thus the ETW lock gets taken first and then our EventListenersLock gets taken
// in SendCommand(), and also here. If we called EventUnregister after taking
// the EventListenersLock then the take-lock order is reversed and we can have
// deadlocks in race conditions (dispose racing with an ETW command).
//
// We solve by Unregistering after releasing the EventListenerLock.
if (registrationHandle != 0)
EventUnregister(registrationHandle);
}
///
/// This method deregisters the controlGuid of this class with ETW.
///
///
public virtual void Close()
{
Dispose();
}
~EventProvider()
{
Dispose(false);
}
//
//
//
//
unsafe void EtwEnableCallBack(
[In] ref System.Guid sourceId,
[In] int controlCode,
[In] byte setLevel,
[In] long anyKeyword,
[In] long allKeyword,
[In] UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData,
[In] void* callbackContext
)
{
// This is an optional callback API. We will therefore ignore any failures that happen as a
// result of turning on this provider as to not crash the app.
// EventSource has code to validate whether initialization it expected to occur actually occurred
try
{
ControllerCommand command = ControllerCommand.Update;
IDictionary args = null;
bool skipFinalOnControllerCommand = false;
if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER)
{
m_enabled = true;
m_level = setLevel;
m_anyKeywordMask = anyKeyword;
m_allKeywordMask = allKeyword;
// ES_SESSION_INFO is a marker for additional places we #ifdeffed out to remove
// references to EnumerateTraceGuidsEx. This symbol is actually not used because
// today we use FEATURE_ACTIVITYSAMPLING to determine if this code is there or not.
// However we put it in the #if so that we don't lose the fact that this feature
// switch is at least partially independent of FEATURE_ACTIVITYSAMPLING
List> sessionsChanged = GetSessions();
foreach (var session in sessionsChanged)
{
int sessionChanged = session.Item1.sessionIdBit;
int etwSessionId = session.Item1.etwSessionId;
bool bEnabling = session.Item2;
skipFinalOnControllerCommand = true;
args = null; // reinitialize args for every session...
// if we get more than one session changed we have no way
// of knowing which one "filterData" belongs to
if (sessionsChanged.Count > 1)
filterData = null;
// read filter data only when a session is being *added*
byte[] data;
int keyIndex;
if (bEnabling &&
GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex))
{
args = new Dictionary(4);
while (keyIndex < data.Length)
{
int keyEnd = FindNull(data, keyIndex);
int valueIdx = keyEnd + 1;
int valueEnd = FindNull(data, valueIdx);
if (valueEnd < data.Length)
{
string key = System.Text.Encoding.UTF8.GetString(data, keyIndex, keyEnd - keyIndex);
string value = System.Text.Encoding.UTF8.GetString(data, valueIdx, valueEnd - valueIdx);
args[key] = value;
}
keyIndex = valueEnd + 1;
}
}
// execute OnControllerCommand once for every session that has changed.
OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId);
}
}
else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_DISABLE_PROVIDER)
{
m_enabled = false;
m_level = 0;
m_anyKeywordMask = 0;
m_allKeywordMask = 0;
m_liveSessions = null;
}
else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_CAPTURE_STATE)
{
command = ControllerCommand.SendManifest;
}
else
return; // per spec you ignore commands you don't recognize.
if (!skipFinalOnControllerCommand)
OnControllerCommand(command, args, 0, 0);
}
catch (Exception)
{
// We want to ignore any failures that happen as a result of turning on this provider as to
// not crash the app.
}
}
// New in CLR4.0
protected virtual void OnControllerCommand(ControllerCommand command, IDictionary arguments, int sessionId, int etwSessionId) { }
protected EventLevel Level { get { return (EventLevel)m_level; } set { m_level = (byte)value; } }
protected EventKeywords MatchAnyKeyword { get { return (EventKeywords)m_anyKeywordMask; } set { m_anyKeywordMask = unchecked((long)value); } }
protected EventKeywords MatchAllKeyword { get { return (EventKeywords)m_allKeywordMask; } set { m_allKeywordMask = unchecked((long)value); } }
static private int FindNull(byte[] buffer, int idx)
{
while (idx < buffer.Length && buffer[idx] != 0)
idx++;
return idx;
}
///
/// Determines the ETW sessions that have been added and/or removed to the set of
/// sessions interested in the current provider. It does so by (1) enumerating over all
/// ETW sessions that enabled 'this.m_Guid' for the current process ID, and (2)
/// comparing the current list with a list it cached on the previous invocation.
///
/// The return value is a list of tuples, where the SessionInfo specifies the
/// ETW session that was added or remove, and the bool specifies whether the
/// session was added or whether it was removed from the set.
///
private List> GetSessions()
{
List liveSessionList = null;
GetSessionInfo(
(int etwSessionId, long matchAllKeywords, ref List sessionList) =>
GetSessionInfoCallback(etwSessionId, matchAllKeywords, ref sessionList),
ref liveSessionList);
List> changedSessionList = new List>();
// first look for sessions that have gone away (or have changed)
// (present in the m_liveSessions but not in the new liveSessionList)
if (m_liveSessions != null)
{
foreach (SessionInfo s in m_liveSessions)
{
int idx;
if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 ||
(liveSessionList[idx].sessionIdBit != s.sessionIdBit))
changedSessionList.Add(Tuple.Create(s, false));
}
}
// next look for sessions that were created since the last callback (or have changed)
// (present in the new liveSessionList but not in m_liveSessions)
if (liveSessionList != null)
{
foreach (SessionInfo s in liveSessionList)
{
int idx;
if ((idx = IndexOfSessionInList(m_liveSessions, s.etwSessionId)) < 0 ||
(m_liveSessions[idx].sessionIdBit != s.sessionIdBit))
changedSessionList.Add(Tuple.Create(s, true));
}
}
m_liveSessions = liveSessionList;
return changedSessionList;
}
///
/// This method is the callback used by GetSessions() when it calls into GetSessionInfo().
/// It updates a List{SessionInfo} based on the etwSessionId and matchAllKeywords that
/// GetSessionInfo() passes in.
///
private static void GetSessionInfoCallback(int etwSessionId, long matchAllKeywords,
ref List sessionList)
{
uint sessionIdBitMask = (uint)SessionMask.FromEventKeywords(unchecked((ulong)matchAllKeywords));
// an ETW controller that specifies more than the mandated bit for our EventSource
// will be ignored...
if (bitcount(sessionIdBitMask) > 1)
return;
if (sessionList == null)
sessionList = new List(8);
if (bitcount(sessionIdBitMask) == 1)
{
// activity-tracing-aware etw session
sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask) + 1, etwSessionId));
}
else
{
// legacy etw session
sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All) + 1, etwSessionId));
}
}
private delegate void SessionInfoCallback(int etwSessionId, long matchAllKeywords, ref List sessionList);
///
/// This method enumerates over all active ETW sessions that have enabled 'this.m_Guid'
/// for the current process ID, calling 'action' for each session, and passing it the
/// ETW session and the 'AllKeywords' the session enabled for the current provider.
///
private unsafe void GetSessionInfo(SessionInfoCallback action, ref List sessionList)
{
// We wish the EventSource package to be legal for Windows Store applications.
// Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here
// and use the information in the registry instead. This means that ETW controllers
// that do not publish their intent to the registry (basically all controllers EXCEPT
// TraceEventSesion) will not work properly
// However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus
// does not have this issue.
#if (PLATFORM_WINDOWS && (ES_SESSION_INFO || !ES_BUILD_STANDALONE))
int buffSize = 256; // An initial guess that probably works most of the time.
byte* buffer;
for (; ; )
{
var space = stackalloc byte[buffSize];
buffer = space;
var hr = 0;
fixed (Guid* provider = &m_providerId)
{
hr = UnsafeNativeMethods.ManifestEtw.EnumerateTraceGuidsEx(UnsafeNativeMethods.ManifestEtw.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo,
provider, sizeof(Guid), buffer, buffSize, ref buffSize);
}
if (hr == 0)
break;
if (hr != 122 /* ERROR_INSUFFICIENT_BUFFER */)
return;
}
var providerInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_GUID_INFO*)buffer;
var providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1];
int processId = unchecked((int)Win32Native.GetCurrentProcessId());
// iterate over the instances of the EventProvider in all processes
for (int i = 0; i < providerInfos->InstanceCount; i++)
{
if (providerInstance->Pid == processId)
{
var enabledInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_ENABLE_INFO*)&providerInstance[1];
// iterate over the list of active ETW sessions "listening" to the current provider
for (int j = 0; j < providerInstance->EnableCount; j++)
action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList);
}
if (providerInstance->NextOffset == 0)
break;
Debug.Assert(0 <= providerInstance->NextOffset && providerInstance->NextOffset < buffSize);
var structBase = (byte*)providerInstance;
providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset];
}
#else
#if !ES_BUILD_PCL && !FEATURE_PAL // TODO command arguments don't work on PCL builds...
// This code is only used in the Nuget Package Version of EventSource. because
// the code above is using APIs baned from UWP apps.
//
// TODO: In addition to only working when TraceEventSession enables the provider, this code
// also has a problem because TraceEvent does not clean up if the registry is stale
// It is unclear if it is worth keeping, but for now we leave it as it does work
// at least some of the time.
// Determine our session from what is in the registry.
string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
regKey = @"Software" + @"\Wow6432Node" + regKey;
else
regKey = @"Software" + regKey;
var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKey);
if (key != null)
{
foreach (string valueName in key.GetValueNames())
{
if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal))
{
string strId = valueName.Substring(23); // strip of the ControllerData_Session_
int etwSessionId;
if (int.TryParse(strId, out etwSessionId))
{
// we need to assert this permission for partial trust scenarios
(new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
var data = key.GetValue(valueName) as byte[];
if (data != null)
{
var dataAsString = System.Text.Encoding.UTF8.GetString(data);
int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal);
if (0 <= keywordIdx)
{
int startIdx = keywordIdx + 18;
int endIdx = dataAsString.IndexOf('\0', startIdx);
string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx);
int keywordBit;
if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit))
action(etwSessionId, 1L << keywordBit, ref sessionList);
}
}
}
}
}
}
#endif
#endif
}
///
/// Returns the index of the SesisonInfo from 'sessions' that has the specified 'etwSessionId'
/// or -1 if the value is not present.
///
private static int IndexOfSessionInList(List sessions, int etwSessionId)
{
if (sessions == null)
return -1;
// for non-coreclr code we could use List.FindIndex(Predicate), but we need this to compile
// on coreclr as well
for (int i = 0; i < sessions.Count; ++i)
if (sessions[i].etwSessionId == etwSessionId)
return i;
return -1;
}
///
/// Gets any data to be passed from the controller to the provider. It starts with what is passed
/// into the callback, but unfortunately this data is only present for when the provider is active
/// at the time the controller issues the command. To allow for providers to activate after the
/// controller issued a command, we also check the registry and use that to get the data. The function
/// returns an array of bytes representing the data, the index into that byte array where the data
/// starts, and the command being issued associated with that data.
///
private unsafe bool GetDataFromController(int etwSessionId,
UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart)
{
data = null;
dataStart = 0;
if (filterData == null)
{
#if (!ES_BUILD_PCL && !ES_BUILD_PN && !FEATURE_PAL)
string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
regKey = @"HKEY_LOCAL_MACHINE\Software" + @"\Wow6432Node" + regKey;
else
regKey = @"HKEY_LOCAL_MACHINE\Software" + regKey;
string valueName = "ControllerData_Session_" + etwSessionId.ToString(CultureInfo.InvariantCulture);
// we need to assert this permission for partial trust scenarios
#if !CORECLR
(new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
#endif
data = Microsoft.Win32.Registry.GetValue(regKey, valueName, null) as byte[];
if (data != null)
{
// We only used the persisted data from the registry for updates.
command = ControllerCommand.Update;
return true;
}
#endif
}
else
{
if (filterData->Ptr != 0 && 0 < filterData->Size && filterData->Size <= 1024)
{
data = new byte[filterData->Size];
Marshal.Copy((IntPtr)filterData->Ptr, data, 0, data.Length);
}
command = (ControllerCommand)filterData->Type;
return true;
}
command = ControllerCommand.Update;
return false;
}
///
/// IsEnabled, method used to test if provider is enabled
///
public bool IsEnabled()
{
return m_enabled;
}
///
/// IsEnabled, method used to test if event is enabled
///
///
/// Level to test
///
///
/// Keyword to test
///
public bool IsEnabled(byte level, long keywords)
{
//
// If not enabled at all, return false.
//
if (!m_enabled)
{
return false;
}
// This also covers the case of Level == 0.
if ((level <= m_level) ||
(m_level == 0))
{
//
// Check if Keyword is enabled
//
if ((keywords == 0) ||
(((keywords & m_anyKeywordMask) != 0) &&
((keywords & m_allKeywordMask) == m_allKeywordMask)))
{
return true;
}
}
return false;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public static WriteEventErrorCode GetLastWriteEventError()
{
return s_returnCode;
}
//
// Helper function to set the last error on the thread
//
private static void SetLastError(int error)
{
switch (error)
{
case UnsafeNativeMethods.ManifestEtw.ERROR_ARITHMETIC_OVERFLOW:
case UnsafeNativeMethods.ManifestEtw.ERROR_MORE_DATA:
s_returnCode = WriteEventErrorCode.EventTooBig;
break;
case UnsafeNativeMethods.ManifestEtw.ERROR_NOT_ENOUGH_MEMORY:
s_returnCode = WriteEventErrorCode.NoFreeBuffers;
break;
}
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
private static unsafe object EncodeObject(ref object data, ref EventData* dataDescriptor, ref byte* dataBuffer, ref uint totalEventSize)
/*++
Routine Description:
This routine is used by WriteEvent to unbox the object type and
to fill the passed in ETW data descriptor.
Arguments:
data - argument to be decoded
dataDescriptor - pointer to the descriptor to be filled (updated to point to the next empty entry)
dataBuffer - storage buffer for storing user data, needed because cant get the address of the object
(updated to point to the next empty entry)
Return Value:
null if the object is a basic type other than string or byte[]. String otherwise
--*/
{
Again:
dataDescriptor->Reserved = 0;
string sRet = data as string;
byte[] blobRet = null;
if (sRet != null)
{
dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
}
else if ((blobRet = data as byte[]) != null)
{
// first store array length
*(int*)dataBuffer = blobRet.Length;
dataDescriptor->Ptr = (ulong)dataBuffer;
dataDescriptor->Size = 4;
totalEventSize += dataDescriptor->Size;
// then the array parameters
dataDescriptor++;
dataBuffer += s_basicTypeAllocationBufferSize;
dataDescriptor->Size = (uint)blobRet.Length;
}
else if (data is IntPtr)
{
dataDescriptor->Size = (uint)sizeof(IntPtr);
IntPtr* intptrPtr = (IntPtr*)dataBuffer;
*intptrPtr = (IntPtr)data;
dataDescriptor->Ptr = (ulong)intptrPtr;
}
else if (data is int)
{
dataDescriptor->Size = (uint)sizeof(int);
int* intptr = (int*)dataBuffer;
*intptr = (int)data;
dataDescriptor->Ptr = (ulong)intptr;
}
else if (data is long)
{
dataDescriptor->Size = (uint)sizeof(long);
long* longptr = (long*)dataBuffer;
*longptr = (long)data;
dataDescriptor->Ptr = (ulong)longptr;
}
else if (data is uint)
{
dataDescriptor->Size = (uint)sizeof(uint);
uint* uintptr = (uint*)dataBuffer;
*uintptr = (uint)data;
dataDescriptor->Ptr = (ulong)uintptr;
}
else if (data is UInt64)
{
dataDescriptor->Size = (uint)sizeof(ulong);
UInt64* ulongptr = (ulong*)dataBuffer;
*ulongptr = (ulong)data;
dataDescriptor->Ptr = (ulong)ulongptr;
}
else if (data is char)
{
dataDescriptor->Size = (uint)sizeof(char);
char* charptr = (char*)dataBuffer;
*charptr = (char)data;
dataDescriptor->Ptr = (ulong)charptr;
}
else if (data is byte)
{
dataDescriptor->Size = (uint)sizeof(byte);
byte* byteptr = (byte*)dataBuffer;
*byteptr = (byte)data;
dataDescriptor->Ptr = (ulong)byteptr;
}
else if (data is short)
{
dataDescriptor->Size = (uint)sizeof(short);
short* shortptr = (short*)dataBuffer;
*shortptr = (short)data;
dataDescriptor->Ptr = (ulong)shortptr;
}
else if (data is sbyte)
{
dataDescriptor->Size = (uint)sizeof(sbyte);
sbyte* sbyteptr = (sbyte*)dataBuffer;
*sbyteptr = (sbyte)data;
dataDescriptor->Ptr = (ulong)sbyteptr;
}
else if (data is ushort)
{
dataDescriptor->Size = (uint)sizeof(ushort);
ushort* ushortptr = (ushort*)dataBuffer;
*ushortptr = (ushort)data;
dataDescriptor->Ptr = (ulong)ushortptr;
}
else if (data is float)
{
dataDescriptor->Size = (uint)sizeof(float);
float* floatptr = (float*)dataBuffer;
*floatptr = (float)data;
dataDescriptor->Ptr = (ulong)floatptr;
}
else if (data is double)
{
dataDescriptor->Size = (uint)sizeof(double);
double* doubleptr = (double*)dataBuffer;
*doubleptr = (double)data;
dataDescriptor->Ptr = (ulong)doubleptr;
}
else if (data is bool)
{
// WIN32 Bool is 4 bytes
dataDescriptor->Size = 4;
int* intptr = (int*)dataBuffer;
if (((bool)data))
{
*intptr = 1;
}
else
{
*intptr = 0;
}
dataDescriptor->Ptr = (ulong)intptr;
}
else if (data is Guid)
{
dataDescriptor->Size = (uint)sizeof(Guid);
Guid* guidptr = (Guid*)dataBuffer;
*guidptr = (Guid)data;
dataDescriptor->Ptr = (ulong)guidptr;
}
else if (data is decimal)
{
dataDescriptor->Size = (uint)sizeof(decimal);
decimal* decimalptr = (decimal*)dataBuffer;
*decimalptr = (decimal)data;
dataDescriptor->Ptr = (ulong)decimalptr;
}
else if (data is DateTime)
{
const long UTCMinTicks = 504911232000000000;
long dateTimeTicks = 0;
// We cannot translate dates sooner than 1/1/1601 in UTC.
// To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks
if (((DateTime)data).Ticks > UTCMinTicks)
dateTimeTicks = ((DateTime)data).ToFileTimeUtc();
dataDescriptor->Size = (uint)sizeof(long);
long* longptr = (long*)dataBuffer;
*longptr = dateTimeTicks;
dataDescriptor->Ptr = (ulong)longptr;
}
else
{
if (data is System.Enum)
{
Type underlyingType = Enum.GetUnderlyingType(data.GetType());
if (underlyingType == typeof(int))
{
#if !ES_BUILD_PCL
data = ((IConvertible)data).ToInt32(null);
#else
data = (int)data;
#endif
goto Again;
}
else if (underlyingType == typeof(long))
{
#if !ES_BUILD_PCL
data = ((IConvertible)data).ToInt64(null);
#else
data = (long)data;
#endif
goto Again;
}
}
// To our eyes, everything else is a just a string
if (data == null)
sRet = "";
else
sRet = data.ToString();
dataDescriptor->Size = ((uint)sRet.Length + 1) * 2;
}
totalEventSize += dataDescriptor->Size;
// advance buffers
dataDescriptor++;
dataBuffer += s_basicTypeAllocationBufferSize;
return (object)sRet ?? (object)blobRet;
}
///
/// WriteEvent, method to write a parameters with event schema properties
///
///
/// Event Descriptor for this event.
///
///
/// A pointer to the activity ID GUID to log
///
///
/// childActivityID is marked as 'related' to the current activity ID.
///
///
/// Payload for the ETW event.
///
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Performance-critical code")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, params object[] eventPayload)
{
int status = 0;
if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords))
{
int argCount = 0;
unsafe
{
argCount = eventPayload.Length;
if (argCount > s_etwMaxNumberArguments)
{
s_returnCode = WriteEventErrorCode.TooManyArgs;
return false;
}
uint totalEventSize = 0;
int index;
int refObjIndex = 0;
List refObjPosition = new List(s_etwAPIMaxRefObjCount);
List