summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Gillespie <segilles@microsoft.com>2018-01-23 18:53:30 -0800
committerGitHub <noreply@github.com>2018-01-23 18:53:30 -0800
commitfacdc8b97f73973fb416ed13e4b9dd9a255864bf (patch)
treec6be1ef07de8d324dcb121da524c13458d7f0f63
parent0bafdbc71e5a3efe6b6df0cbcf5aee5081a3e4c8 (diff)
downloadcoreclr-facdc8b97f73973fb416ed13e4b9dd9a255864bf.tar.gz
coreclr-facdc8b97f73973fb416ed13e4b9dd9a255864bf.tar.bz2
coreclr-facdc8b97f73973fb416ed13e4b9dd9a255864bf.zip
[Local GC] FEATURE_EVENT_TRACE 1/n: Tracking Event State (#15873)
* [Local GC] FEATURE_EVENT_TRACE 1/n: Add infrastructure for keeping event state within the GC and plumbing to communicate event state changes * Code review feedback: use a load without a barrier in IsEnabled and put debug-only code under TRACE_GC_EVENT_STATE * Address code review feedback: add EventPipe callback and comments * Fix the non-FEATURE_PAL build * Fix an issue where the GC fails to react to ETW callbacks to occur before the GC is initialized (e.g. on startup when an ETW session is already active) * Simplify callback locking scheme * Add a separate callback for each EventPipe provider and funnel them all through a common handler * Fix non-FEATURE_PAL build
-rw-r--r--src/gc/CMakeLists.txt1
-rw-r--r--src/gc/env/gcenv.h4
-rw-r--r--src/gc/env/gcenv.object.h5
-rw-r--r--src/gc/env/gcenv.sync.h4
-rw-r--r--src/gc/gcee.cpp9
-rw-r--r--src/gc/gceesvr.cpp1
-rw-r--r--src/gc/gceewks.cpp1
-rw-r--r--src/gc/gceventstatus.cpp9
-rw-r--r--src/gc/gceventstatus.h190
-rw-r--r--src/gc/gcimpl.h4
-rw-r--r--src/gc/gcinterface.h50
-rw-r--r--src/gc/sample/CMakeLists.txt1
-rw-r--r--src/gc/sample/gcenv.h4
-rw-r--r--src/inc/eventtracebase.h36
-rw-r--r--src/scripts/genEventPipe.py5
-rw-r--r--src/vm/CMakeLists.txt3
-rw-r--r--src/vm/eventtrace.cpp135
-rw-r--r--src/vm/gcheaputilities.cpp98
-rw-r--r--src/vm/gcheaputilities.h4
19 files changed, 542 insertions, 22 deletions
diff --git a/src/gc/CMakeLists.txt b/src/gc/CMakeLists.txt
index 3240074b9b..e7aacdb831 100644
--- a/src/gc/CMakeLists.txt
+++ b/src/gc/CMakeLists.txt
@@ -18,6 +18,7 @@ remove_definitions(-DWRITE_BARRIER_CHECK)
add_definitions(-DFEATURE_REDHAWK)
set( GC_SOURCES
+ gceventstatus.cpp
gcconfig.cpp
gccommon.cpp
gcscan.cpp
diff --git a/src/gc/env/gcenv.h b/src/gc/env/gcenv.h
index 3de756021d..a3071a1397 100644
--- a/src/gc/env/gcenv.h
+++ b/src/gc/env/gcenv.h
@@ -1,6 +1,8 @@
// 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.
+#ifndef __GCENV_H__
+#define __GCENV_H__
#if defined(_DEBUG)
#ifndef _DEBUG_IMPL
@@ -71,3 +73,5 @@
#include "etmdummy.h"
#define ETW_EVENT_ENABLED(e,f) false
+
+#endif // __GCENV_H__
diff --git a/src/gc/env/gcenv.object.h b/src/gc/env/gcenv.object.h
index 4d611e562d..dd152f2f3c 100644
--- a/src/gc/env/gcenv.object.h
+++ b/src/gc/env/gcenv.object.h
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#ifndef __GCENV_OBJECT_H__
+#define __GCENV_OBJECT_H__
+
//-------------------------------------------------------------------------------------------------
//
// Low-level types describing GC object layouts.
@@ -168,3 +171,5 @@ public:
return offsetof(ArrayBase, m_dwLength);
}
};
+
+#endif // __GCENV_OBJECT_H__
diff --git a/src/gc/env/gcenv.sync.h b/src/gc/env/gcenv.sync.h
index d6bee05a19..5b7b77ddd4 100644
--- a/src/gc/env/gcenv.sync.h
+++ b/src/gc/env/gcenv.sync.h
@@ -1,6 +1,8 @@
// 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.
+#ifndef __GCENV_SYNC_H__
+#define __GCENV_SYNC_H__
// -----------------------------------------------------------------------------------------------------------
//
@@ -143,3 +145,5 @@ private:
HANDLE m_hEvent;
bool m_fInitialized;
};
+
+#endif // __GCENV_SYNC_H__
diff --git a/src/gc/gcee.cpp b/src/gc/gcee.cpp
index 90939b3d97..c5cc88b373 100644
--- a/src/gc/gcee.cpp
+++ b/src/gc/gcee.cpp
@@ -687,6 +687,15 @@ bool GCHeap::RuntimeStructuresValid()
return GCScan::GetGcRuntimeStructuresValid();
}
+void GCHeap::ControlEvents(GCEventKeyword keyword, GCEventLevel level)
+{
+ GCEventStatus::Set(GCEventProvider_Default, keyword, level);
+}
+
+void GCHeap::ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level)
+{
+ GCEventStatus::Set(GCEventProvider_Private, keyword, level);
+}
#endif // !DACCESS_COMPILE
diff --git a/src/gc/gceesvr.cpp b/src/gc/gceesvr.cpp
index e216834f8e..cfcbe5869a 100644
--- a/src/gc/gceesvr.cpp
+++ b/src/gc/gceesvr.cpp
@@ -13,6 +13,7 @@
#include "gc.h"
#include "gcscan.h"
#include "gchandletableimpl.h"
+#include "gceventstatus.h"
#define SERVER_GC 1
diff --git a/src/gc/gceewks.cpp b/src/gc/gceewks.cpp
index f23038f012..9a4038cdd9 100644
--- a/src/gc/gceewks.cpp
+++ b/src/gc/gceewks.cpp
@@ -11,6 +11,7 @@
#include "gc.h"
#include "gcscan.h"
#include "gchandletableimpl.h"
+#include "gceventstatus.h"
#ifdef SERVER_GC
#undef SERVER_GC
diff --git a/src/gc/gceventstatus.cpp b/src/gc/gceventstatus.cpp
new file mode 100644
index 0000000000..9c4f35bfde
--- /dev/null
+++ b/src/gc/gceventstatus.cpp
@@ -0,0 +1,9 @@
+// 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.
+
+#include "common.h"
+#include "gceventstatus.h"
+
+Volatile<GCEventLevel> GCEventStatus::enabledLevels[2] = {GCEventLevel_None, GCEventLevel_None};
+Volatile<GCEventKeyword> GCEventStatus::enabledKeywords[2] = {GCEventKeyword_None, GCEventKeyword_None};
diff --git a/src/gc/gceventstatus.h b/src/gc/gceventstatus.h
new file mode 100644
index 0000000000..4b7310b8a7
--- /dev/null
+++ b/src/gc/gceventstatus.h
@@ -0,0 +1,190 @@
+// 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.
+
+#ifndef __GCEVENTSTATUS_H__
+#define __GCEVENTSTATUS_H__
+
+
+/*
+ * gceventstatus.h - Eventing status for a standalone GC
+ *
+ * In order for a local GC to determine what events are enabled
+ * in an efficient manner, the GC maintains some local state about
+ * keywords and levels that are enabled for each eventing provider.
+ *
+ * The GC fires events from two providers: the "main" provider
+ * and the "private" provider. This file tracks keyword and level
+ * information for each provider separately.
+ *
+ * It is the responsibility of the EE to inform the GC of changes
+ * to eventing state. This is accomplished by invoking the
+ * `IGCHeap::ControlEvents` and `IGCHeap::ControlPrivateEvents` callbacks
+ * on the EE's heap instance, which ultimately will enable and disable keywords
+ * and levels within this file.
+ */
+
+#include "common.h"
+#include "gcenv.h"
+#include "gc.h"
+
+// Uncomment this define to print out event state changes to standard error.
+// #define TRACE_GC_EVENT_STATE 1
+
+/*
+ * GCEventProvider represents one of the two providers that the GC can
+ * fire events from: the default and private providers.
+ */
+enum GCEventProvider
+{
+ GCEventProvider_Default = 0,
+ GCEventProvider_Private = 1
+};
+
+/*
+ * GCEventStatus maintains all eventing state for the GC. It consists
+ * of a keyword bitmask and level for each provider that the GC can use
+ * to fire events.
+ *
+ * A level and event pair are considered to be "enabled" on a given provider
+ * if the given level is less than or equal to the current enabled level
+ * and if the keyword is present in the enabled keyword bitmask for that
+ * provider.
+ */
+class GCEventStatus
+{
+private:
+ /*
+ * The enabled level for each provider.
+ */
+ static Volatile<GCEventLevel> enabledLevels[2];
+
+ /*
+ * The bitmap of enabled keywords for each provider.
+ */
+ static Volatile<GCEventKeyword> enabledKeywords[2];
+
+public:
+ /*
+ * IsEnabled queries whether or not the given level and keyword are
+ * enabled on the given provider, returning true if they are.
+ */
+ __forceinline static bool IsEnabled(GCEventProvider provider, GCEventKeyword keyword, GCEventLevel level)
+ {
+ assert(level >= GCEventLevel_None && level < GCEventLevel_Max);
+
+ size_t index = static_cast<size_t>(provider);
+ return (enabledLevels[index].LoadWithoutBarrier() >= level)
+ && (enabledKeywords[index].LoadWithoutBarrier() & keyword);
+ }
+
+ /*
+ * Set sets the eventing state (level and keyword bitmap) for a given
+ * provider to the provided values.
+ */
+ static void Set(GCEventProvider provider, GCEventKeyword keywords, GCEventLevel level)
+ {
+ assert(level >= GCEventLevel_None && level < GCEventLevel_Max);
+
+ size_t index = static_cast<size_t>(provider);
+
+ enabledLevels[index] = level;
+ enabledKeywords[index] = keywords;
+
+#if TRACE_GC_EVENT_STATE
+ fprintf(stderr, "event state change:\n");
+ DebugDumpState(provider);
+#endif // TRACE_GC_EVENT_STATE
+ }
+
+#if TRACE_GC_EVENT_STATE
+private:
+ static void DebugDumpState(GCEventProvider provider)
+ {
+ size_t index = static_cast<size_t>(provider);
+ GCEventLevel level = enabledLevels[index];
+ GCEventKeyword keyword = enabledKeywords[index];
+ if (provider == GCEventProvider_Default)
+ {
+ fprintf(stderr, "provider: default\n");
+ }
+ else
+ {
+ fprintf(stderr, "provider: private\n");
+ }
+
+ switch (level)
+ {
+ case GCEventLevel_None:
+ fprintf(stderr, " level: None\n");
+ break;
+ case GCEventLevel_Fatal:
+ fprintf(stderr, " level: Fatal\n");
+ break;
+ case GCEventLevel_Error:
+ fprintf(stderr, " level: Error\n");
+ break;
+ case GCEventLevel_Warning:
+ fprintf(stderr, " level: Warning\n");
+ break;
+ case GCEventLevel_Information:
+ fprintf(stderr, " level: Information\n");
+ break;
+ case GCEventLevel_Verbose:
+ fprintf(stderr, " level: Verbose\n");
+ break;
+ default:
+ fprintf(stderr, " level: %d?\n", level);
+ break;
+ }
+
+ fprintf(stderr, " keywords: ");
+ if (keyword & GCEventKeyword_GC)
+ {
+ fprintf(stderr, "GC ");
+ }
+
+ if (keyword & GCEventKeyword_GCHandle)
+ {
+ fprintf(stderr, "GCHandle ");
+ }
+
+ if (keyword & GCEventKeyword_GCHeapDump)
+ {
+ fprintf(stderr, "GCHeapDump ");
+ }
+
+ if (keyword & GCEventKeyword_GCSampledObjectAllocationHigh)
+ {
+ fprintf(stderr, "GCSampledObjectAllocationHigh ");
+ }
+
+ if (keyword & GCEventKeyword_GCHeapSurvivalAndMovement)
+ {
+ fprintf(stderr, "GCHeapSurvivalAndMovement ");
+ }
+
+ if (keyword & GCEventKeyword_GCHeapCollect)
+ {
+ fprintf(stderr, "GCHeapCollect ");
+ }
+
+ if (keyword & GCEventKeyword_GCHeapAndTypeNames)
+ {
+ fprintf(stderr, "GCHeapAndTypeNames ");
+ }
+
+ if (keyword & GCEventKeyword_GCSampledObjectAllocationLow)
+ {
+ fprintf(stderr, "GCSampledObjectAllocationLow ");
+ }
+
+ fprintf(stderr, "\n");
+ }
+#endif // TRACE_GC_EVENT_STATUS
+
+ // This class is a singleton and can't be instantiated.
+ GCEventStatus() = delete;
+};
+
+#endif // __GCEVENTSTATUS_H__
diff --git a/src/gc/gcimpl.h b/src/gc/gcimpl.h
index c0efa69531..7210b9bcf0 100644
--- a/src/gc/gcimpl.h
+++ b/src/gc/gcimpl.h
@@ -230,6 +230,10 @@ public: // FIX
virtual segment_handle RegisterFrozenSegment(segment_info *pseginfo);
virtual void UnregisterFrozenSegment(segment_handle seg);
+ // Event control functions
+ void ControlEvents(GCEventKeyword keyword, GCEventLevel level);
+ void ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level);
+
void WaitUntilConcurrentGCComplete (); // Use in managd threads
#ifndef DACCESS_COMPILE
HRESULT WaitUntilConcurrentGCCompleteAsync(int millisecondsTimeout); // Use in native threads. TRUE if succeed. FALSE if failed or timeout
diff --git a/src/gc/gcinterface.h b/src/gc/gcinterface.h
index afd3db3fe4..26e7c096d2 100644
--- a/src/gc/gcinterface.h
+++ b/src/gc/gcinterface.h
@@ -205,6 +205,44 @@ extern uint8_t* g_shadow_lowest_address;
// For low memory notification from host
extern int32_t g_bLowMemoryFromHost;
+// Event levels corresponding to events that can be fired by the GC.
+enum GCEventLevel
+{
+ GCEventLevel_None = 0,
+ GCEventLevel_Fatal = 1,
+ GCEventLevel_Error = 2,
+ GCEventLevel_Warning = 3,
+ GCEventLevel_Information = 4,
+ GCEventLevel_Verbose = 5,
+ GCEventLevel_Max = 6
+};
+
+// Event keywords corresponding to events that can be fired by the GC. These
+// numbers come from the ETW manifest itself - please make changes to this enum
+// if you add, remove, or change keyword sets that are used by the GC!
+enum GCEventKeyword
+{
+ GCEventKeyword_None = 0x0,
+ GCEventKeyword_GC = 0x1,
+ GCEventKeyword_GCHandle = 0x2,
+ GCEventKeyword_GCHeapDump = 0x100000,
+ GCEventKeyword_GCSampledObjectAllocationHigh = 0x200000,
+ GCEventKeyword_GCHeapSurvivalAndMovement = 0x400000,
+ GCEventKeyword_GCHeapCollect = 0x800000,
+ GCEventKeyword_GCHeapAndTypeNames = 0x1000000,
+ GCEventKeyword_GCSampledObjectAllocationLow = 0x2000000,
+ GCEventKeyword_All = GCEventKeyword_GC
+ | GCEventKeyword_GCHandle
+ | GCEventKeyword_GCHeapDump
+ | GCEventKeyword_GCSampledObjectAllocationHigh
+ | GCEventKeyword_GCHeapDump
+ | GCEventKeyword_GCSampledObjectAllocationHigh
+ | GCEventKeyword_GCHeapSurvivalAndMovement
+ | GCEventKeyword_GCHeapCollect
+ | GCEventKeyword_GCHeapAndTypeNames
+ | GCEventKeyword_GCSampledObjectAllocationLow
+};
+
// !!!!!!!!!!!!!!!!!!!!!!!
// make sure you change the def in bcl\system\gc.cs
// if you change this!
@@ -809,6 +847,18 @@ public:
// Unregisters a frozen segment.
virtual void UnregisterFrozenSegment(segment_handle seg) = 0;
+ /*
+ ===========================================================================
+ Routines for informing the GC about which events are enabled.
+ ===========================================================================
+ */
+
+ // Enables or disables the given keyword or level on the default event provider.
+ virtual void ControlEvents(GCEventKeyword keyword, GCEventLevel level) = 0;
+
+ // Enables or disables the given keyword or level on the private event provider.
+ virtual void ControlPrivateEvents(GCEventKeyword keyword, GCEventLevel level) = 0;
+
IGCHeap() {}
virtual ~IGCHeap() {}
};
diff --git a/src/gc/sample/CMakeLists.txt b/src/gc/sample/CMakeLists.txt
index 6f8aa615d7..953d0498e0 100644
--- a/src/gc/sample/CMakeLists.txt
+++ b/src/gc/sample/CMakeLists.txt
@@ -10,6 +10,7 @@ add_definitions(-DFEATURE_REDHAWK)
set(SOURCES
GCSample.cpp
gcenv.ee.cpp
+ ../gceventstatus.cpp
../gcconfig.cpp
../gccommon.cpp
../gceewks.cpp
diff --git a/src/gc/sample/gcenv.h b/src/gc/sample/gcenv.h
index a86cf535d5..54b596e141 100644
--- a/src/gc/sample/gcenv.h
+++ b/src/gc/sample/gcenv.h
@@ -1,6 +1,8 @@
// 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.
+#ifndef __GCENV_H__
+#define __GCENV_H__
// The sample is to be kept simple, so building the sample
// in tandem with a standalone GC is currently not supported.
@@ -200,3 +202,5 @@ extern EEConfig * g_pConfig;
#include "etmdummy.h"
#define ETW_EVENT_ENABLED(e,f) false
+
+#endif // __GCENV_H__
diff --git a/src/inc/eventtracebase.h b/src/inc/eventtracebase.h
index d19e3e97dc..344296f016 100644
--- a/src/inc/eventtracebase.h
+++ b/src/inc/eventtracebase.h
@@ -251,6 +251,42 @@ public:
#if defined(FEATURE_EVENT_TRACE)
+VOID EventPipeEtwCallbackDotNETRuntimeStress(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext);
+
+VOID EventPipeEtwCallbackDotNETRuntime(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext);
+
+VOID EventPipeEtwCallbackDotNETRuntimeRundown(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext);
+
+VOID EventPipeEtwCallbackDotNETRuntimePrivate(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext);
+
#ifndef FEATURE_PAL
// Callback and stack support
#if !defined(DONOT_DEFINE_ETW_CALLBACK) && !defined(DACCESS_COMPILE)
diff --git a/src/scripts/genEventPipe.py b/src/scripts/genEventPipe.py
index 96755ea459..fc4570bf14 100644
--- a/src/scripts/genEventPipe.py
+++ b/src/scripts/genEventPipe.py
@@ -130,6 +130,7 @@ def generateClrEventPipeWriteEventsImpl(
WriteEventImpl.append("\n return ERROR_SUCCESS;\n}\n\n")
# EventPipeProvider and EventPipeEvent initialization
+ callbackName = 'EventPipeEtwCallback' + providerPrettyName
if extern: WriteEventImpl.append('extern "C" ')
WriteEventImpl.append(
"void Init" +
@@ -140,7 +141,7 @@ def generateClrEventPipeWriteEventsImpl(
providerPrettyName +
" = EventPipe::CreateProvider(SL(" +
providerPrettyName +
- "Name));\n")
+ "Name), " + callbackName + ");\n")
for eventNode in eventNodes:
eventName = eventNode.getAttribute('symbol')
templateName = eventNode.getAttribute('template')
@@ -522,4 +523,4 @@ def main(argv):
if __name__ == '__main__':
return_code = main(sys.argv[1:])
- sys.exit(return_code) \ No newline at end of file
+ sys.exit(return_code)
diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt
index 97ab656f81..4e2b8d6838 100644
--- a/src/vm/CMakeLists.txt
+++ b/src/vm/CMakeLists.txt
@@ -259,6 +259,7 @@ set(VM_SOURCES_WKS
set(GC_SOURCES_WKS
${GC_SOURCES_DAC_AND_WKS_COMMON}
+ ../gc/gceventstatus.cpp
../gc/gcconfig.cpp
../gc/gccommon.cpp
../gc/gcscan.cpp
@@ -521,4 +522,4 @@ add_subdirectory(wks)
if(FEATURE_PERFTRACING)
add_subdirectory(${GENERATED_EVENTING_DIR}/eventpipe ${CMAKE_CURRENT_BINARY_DIR}/eventpipe)
-endif(FEATURE_PERFTRACING) \ No newline at end of file
+endif(FEATURE_PERFTRACING)
diff --git a/src/vm/eventtrace.cpp b/src/vm/eventtrace.cpp
index f66c92e40e..533a3bc7fa 100644
--- a/src/vm/eventtrace.cpp
+++ b/src/vm/eventtrace.cpp
@@ -4313,6 +4313,110 @@ void InitializeEventTracing()
ETW::TypeSystemLog::PostRegistrationInit();
}
+// Plumbing to funnel event pipe callbacks and ETW callbacks together into a single common
+// handler, for the purposes of informing the GC of changes to the event state.
+//
+// There is one callback for every EventPipe provider and one for all of ETW. The reason
+// for this is that ETW passes the registration handle of the provider that was enabled
+// as a field on the "CallbackContext" field of the callback, while EventPipe passes null
+// unless another token is given to it when the provider is constructed. In the absence of
+// a suitable token, this implementation has a different callback for every EventPipe provider
+// that ultimately funnels them all into a common handler.
+
+// CallbackProviderIndex provides a quick identification of which provider triggered the
+// ETW callback.
+enum CallbackProviderIndex
+{
+ DotNETRuntime = 0,
+ DotNETRuntimeRundown = 1,
+ DotNETRuntimeStress = 2,
+ DotNETRuntimePrivate = 3
+};
+
+// Common handler for all ETW or EventPipe event notifications. Based on the provider that
+// was enabled/disabled, this implementation forwards the event state change onto GCHeapUtilities
+// which will inform the GC to update its local state about what events are enabled.
+VOID EtwCallbackCommon(
+ CallbackProviderIndex ProviderIndex,
+ ULONG ControlCode,
+ UCHAR Level,
+ ULONGLONG MatchAnyKeyword)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ bool bIsPublicTraceHandle = ProviderIndex == DotNETRuntime;
+#if !defined(FEATURE_PAL)
+ static_assert(GCEventLevel_None == TRACE_LEVEL_NONE, "GCEventLevel_None value mismatch");
+ static_assert(GCEventLevel_Fatal == TRACE_LEVEL_FATAL, "GCEventLevel_Fatal value mismatch");
+ static_assert(GCEventLevel_Error == TRACE_LEVEL_ERROR, "GCEventLevel_Error value mismatch");
+ static_assert(GCEventLevel_Warning == TRACE_LEVEL_WARNING, "GCEventLevel_Warning mismatch");
+ static_assert(GCEventLevel_Information == TRACE_LEVEL_INFORMATION, "GCEventLevel_Information mismatch");
+ static_assert(GCEventLevel_Verbose == TRACE_LEVEL_VERBOSE, "GCEventLevel_Verbose mismatch");
+#endif // !defined(FEATURE_PAL)
+ GCEventKeyword keywords = static_cast<GCEventKeyword>(MatchAnyKeyword);
+ GCEventLevel level = static_cast<GCEventLevel>(Level);
+ GCHeapUtilities::RecordEventStateChange(bIsPublicTraceHandle, keywords, level);
+}
+
+// Individual callbacks for each EventPipe provider.
+
+VOID EventPipeEtwCallbackDotNETRuntimeStress(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ EtwCallbackCommon(DotNETRuntimeStress, ControlCode, Level, MatchAnyKeyword);
+}
+
+VOID EventPipeEtwCallbackDotNETRuntime(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ EtwCallbackCommon(DotNETRuntime, ControlCode, Level, MatchAnyKeyword);
+}
+
+VOID EventPipeEtwCallbackDotNETRuntimeRundown(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ EtwCallbackCommon(DotNETRuntimeRundown, ControlCode, Level, MatchAnyKeyword);
+}
+
+VOID EventPipeEtwCallbackDotNETRuntimePrivate(
+ _In_ LPCGUID SourceId,
+ _In_ ULONG ControlCode,
+ _In_ UCHAR Level,
+ _In_ ULONGLONG MatchAnyKeyword,
+ _In_ ULONGLONG MatchAllKeyword,
+ _In_opt_ PVOID FilterData,
+ _Inout_opt_ PVOID CallbackContext)
+{
+ WRAPPER_NO_CONTRACT;
+
+ EtwCallbackCommon(DotNETRuntimePrivate, ControlCode, Level, MatchAnyKeyword);
+}
+
+
#if !defined(FEATURE_PAL)
HRESULT ETW::CEtwTracer::Register()
{
@@ -4397,7 +4501,6 @@ extern "C"
extern "C"
{
-
// #EtwCallback:
// During the build, MC generates the code to register our provider, and to register
// our ETW callback. (This is buried under Intermediates, in a path like
@@ -4446,12 +4549,30 @@ extern "C"
BOOLEAN bIsRundownTraceHandle = (context->RegistrationHandle==Microsoft_Windows_DotNETRuntimeRundownHandle);
- // TypeSystemLog needs a notification when certain keywords are modified, so
- // give it a hook here.
- if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle)
- {
- ETW::TypeSystemLog::OnKeywordsChanged();
- }
+ // EventPipeEtwCallback contains some GC eventing functionality shared between EventPipe and ETW.
+ // Eventually, we'll want to merge these two codepaths whenever we can.
+ CallbackProviderIndex providerIndex = DotNETRuntime;
+ if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimeHandle) {
+ providerIndex = DotNETRuntime;
+ } else if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimeRundownHandle) {
+ providerIndex = DotNETRuntimeRundown;
+ } else if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimeStressHandle) {
+ providerIndex = DotNETRuntimeStress;
+ } else if (context->RegistrationHandle == Microsoft_Windows_DotNETRuntimePrivateHandle) {
+ providerIndex = DotNETRuntimePrivate;
+ } else {
+ assert(!"unknown registration handle");
+ return;
+ }
+
+ EtwCallbackCommon(providerIndex, ControlCode, Level, MatchAnyKeyword);
+
+ // TypeSystemLog needs a notification when certain keywords are modified, so
+ // give it a hook here.
+ if (g_fEEStarted && !g_fEEShutDown && bIsPublicTraceHandle)
+ {
+ ETW::TypeSystemLog::OnKeywordsChanged();
+ }
// A manifest based provider can be enabled to multiple event tracing sessions
// As long as there is atleast 1 enabled session, IsEnabled will be TRUE
diff --git a/src/vm/gcheaputilities.cpp b/src/vm/gcheaputilities.cpp
index af2971f1ab..3ffb3de479 100644
--- a/src/vm/gcheaputilities.cpp
+++ b/src/vm/gcheaputilities.cpp
@@ -80,6 +80,78 @@ HMODULE GCHeapUtilities::GetGCModule()
namespace
{
+// This block of code contains all of the state necessary to handle incoming
+// EtwCallbacks before the GC has been initialized. This is a tricky problem
+// because EtwCallbacks can appear at any time, even when we are just about
+// finished initializing the GC.
+//
+// The below lock is taken by the "main" thread (the thread in EEStartup) and
+// the "ETW" thread, the one calling EtwCallback. EtwCallback may or may not
+// be called on the main thread.
+DangerousNonHostedSpinLock g_eventStashLock;
+
+GCEventLevel g_stashedLevel = GCEventLevel_None;
+GCEventKeyword g_stashedKeyword = GCEventKeyword_None;
+GCEventLevel g_stashedPrivateLevel = GCEventLevel_None;
+GCEventKeyword g_stashedPrivateKeyword = GCEventKeyword_None;
+
+BOOL g_gcEventTracingInitialized = FALSE;
+
+// FinalizeLoad is called by the main thread to complete initialization of the GC.
+// At this point, the GC has provided us with an IGCHeap instance and we are preparing
+// to "publish" it by assigning it to g_pGCHeap.
+//
+// This function can proceed concurrently with StashKeywordAndLevel below.
+void FinalizeLoad(IGCHeap* gcHeap, IGCHandleManager* handleMgr, HMODULE gcModule)
+{
+ g_pGCHeap = gcHeap;
+
+ {
+ DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock);
+
+ // Ultimately, g_eventStashLock ensures that no two threads call ControlEvents at any
+ // point in time.
+ g_pGCHeap->ControlEvents(g_stashedKeyword, g_stashedLevel);
+ g_pGCHeap->ControlPrivateEvents(g_stashedPrivateKeyword, g_stashedPrivateLevel);
+ g_gcEventTracingInitialized = TRUE;
+ }
+
+ g_pGCHandleManager = handleMgr;
+ g_gcDacGlobals = &g_gc_dac_vars;
+ g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE;
+ g_gc_module = gcModule;
+ LOG((LF_GC, LL_INFO100, "GC load successful\n"));
+}
+
+void StashKeywordAndLevel(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level)
+{
+ DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock);
+ if (!g_gcEventTracingInitialized)
+ {
+ if (isPublicProvider)
+ {
+ g_stashedKeyword = keywords;
+ g_stashedLevel = level;
+ }
+ else
+ {
+ g_stashedPrivateKeyword = keywords;
+ g_stashedPrivateLevel = level;
+ }
+ }
+ else
+ {
+ if (isPublicProvider)
+ {
+ g_pGCHeap->ControlEvents(keywords, level);
+ }
+ else
+ {
+ g_pGCHeap->ControlPrivateEvents(keywords, level);
+ }
+ }
+}
+
// Loads and initializes a standalone GC, given the path to the GC
// that we should load. Returns S_OK on success and the failed HRESULT
// on failure.
@@ -153,12 +225,7 @@ HRESULT LoadAndInitializeGC(LPWSTR standaloneGcLocation)
HRESULT initResult = initFunc(gcToClr, &heap, &manager, &g_gc_dac_vars);
if (initResult == S_OK)
{
- g_pGCHeap = heap;
- g_pGCHandleManager = manager;
- g_gcDacGlobals = &g_gc_dac_vars;
- g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE;
- g_gc_module = hMod;
- LOG((LF_GC, LL_INFO100, "GC load successful\n"));
+ FinalizeLoad(heap, manager, hMod);
}
else
{
@@ -198,12 +265,7 @@ HRESULT InitializeDefaultGC()
HRESULT initResult = GC_Initialize(nullptr, &heap, &manager, &g_gc_dac_vars);
if (initResult == S_OK)
{
- g_pGCHeap = heap;
- g_pGCHandleManager = manager;
- g_gcDacGlobals = &g_gc_dac_vars;
- g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE;
- g_gc_module = GetModuleInst();
- LOG((LF_GC, LL_INFO100, "GC load successful\n"));
+ FinalizeLoad(heap, manager, GetModuleInst());
}
else
{
@@ -244,4 +306,16 @@ HRESULT GCHeapUtilities::LoadAndInitialize()
}
}
+void GCHeapUtilities::RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level)
+{
+ CONTRACTL {
+ MODE_ANY;
+ NOTHROW;
+ GC_NOTRIGGER;
+ CAN_TAKE_LOCK;
+ } CONTRACTL_END;
+
+ StashKeywordAndLevel(isPublicProvider, keywords, level);
+}
+
#endif // DACCESS_COMPILE
diff --git a/src/vm/gcheaputilities.h b/src/vm/gcheaputilities.h
index 7dae8c23f1..2208d4421d 100644
--- a/src/vm/gcheaputilities.h
+++ b/src/vm/gcheaputilities.h
@@ -202,6 +202,10 @@ public:
// Loads (if using a standalone GC) and initializes the GC.
static HRESULT LoadAndInitialize();
+
+ // Records a change in eventing state. This ultimately will inform the GC that it needs to be aware
+ // of new events being enabled.
+ static void RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level);
#endif // DACCESS_COMPILE
private: