[Local GC] FEATURE_EVENT_TRACE 1/n: Tracking Event State (#15873)
authorSean Gillespie <segilles@microsoft.com>
Wed, 24 Jan 2018 02:53:30 +0000 (18:53 -0800)
committerGitHub <noreply@github.com>
Wed, 24 Jan 2018 02:53:30 +0000 (18:53 -0800)
* [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

19 files changed:
src/gc/CMakeLists.txt
src/gc/env/gcenv.h
src/gc/env/gcenv.object.h
src/gc/env/gcenv.sync.h
src/gc/gcee.cpp
src/gc/gceesvr.cpp
src/gc/gceewks.cpp
src/gc/gceventstatus.cpp [new file with mode: 0644]
src/gc/gceventstatus.h [new file with mode: 0644]
src/gc/gcimpl.h
src/gc/gcinterface.h
src/gc/sample/CMakeLists.txt
src/gc/sample/gcenv.h
src/inc/eventtracebase.h
src/scripts/genEventPipe.py
src/vm/CMakeLists.txt
src/vm/eventtrace.cpp
src/vm/gcheaputilities.cpp
src/vm/gcheaputilities.h

index 3240074..e7aacdb 100644 (file)
@@ -18,6 +18,7 @@ remove_definitions(-DWRITE_BARRIER_CHECK)
 add_definitions(-DFEATURE_REDHAWK)
 
 set( GC_SOURCES
+  gceventstatus.cpp
   gcconfig.cpp
   gccommon.cpp
   gcscan.cpp
index 3de7560..a3071a1 100644 (file)
@@ -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__
index 4d611e5..dd152f2 100644 (file)
@@ -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__
index d6bee05..5b7b77d 100644 (file)
@@ -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__
index 90939b3..c5cc88b 100644 (file)
@@ -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
 
index e216834..cfcbe58 100644 (file)
@@ -13,6 +13,7 @@
 #include "gc.h"
 #include "gcscan.h"
 #include "gchandletableimpl.h"
+#include "gceventstatus.h"
 
 #define SERVER_GC 1
 
index f23038f..9a4038c 100644 (file)
@@ -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 (file)
index 0000000..9c4f35b
--- /dev/null
@@ -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 (file)
index 0000000..4b7310b
--- /dev/null
@@ -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__
index c0efa69..7210b9b 100644 (file)
@@ -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
index afd3db3..26e7c09 100644 (file)
@@ -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() {}
 };
index 6f8aa61..953d049 100644 (file)
@@ -10,6 +10,7 @@ add_definitions(-DFEATURE_REDHAWK)
 set(SOURCES
     GCSample.cpp
     gcenv.ee.cpp
+    ../gceventstatus.cpp
     ../gcconfig.cpp
     ../gccommon.cpp
     ../gceewks.cpp
index a86cf53..54b596e 100644 (file)
@@ -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__
index d19e3e9..344296f 100644 (file)
@@ -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)
index 96755ea..fc4570b 100644 (file)
@@ -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)
index 97ab656..4e2b8d6 100644 (file)
@@ -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)
index f66c92e..533a3bc 100644 (file)
@@ -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
index af2971f..3ffb3de 100644 (file)
@@ -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
index 7dae8c2..2208d44 100644 (file)
@@ -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: