Skip to content

Commit ae40145

Browse files
authored
Fix AV in NativeRuntimeEventSource QCalls (#48414)
* Fix AV in NativeRuntimeEventSource QCalls * Add test * Use WriteEventCore APIs to emit events on Mono * Separate out Mono/CoreCLR implementations to different files * Fix wasm build
1 parent 481628f commit ae40145

File tree

8 files changed

+425
-24
lines changed

8 files changed

+425
-24
lines changed

src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Debugger.cs" />
127127
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\EditAndContinueHelper.cs" />
128128
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipe.CoreCLR.cs" />
129+
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\NativeRuntimeEventSource.PortableThreadPool.CoreCLR.cs" />
129130
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\ICustomDebuggerNotification.cs" />
130131
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\StackFrame.CoreCLR.cs" />
131132
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\StackFrameHelper.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
4+
using System.Threading;
5+
using System.Diagnostics.Tracing;
6+
using System.Runtime.CompilerServices;
7+
using Internal.Runtime.CompilerServices;
8+
9+
namespace System.Diagnostics.Tracing
10+
{
11+
// This is part of the NativeRuntimeEventsource, which is the managed version of the Microsoft-Windows-DotNETRuntime provider.
12+
// It contains the handwritten implementation of the ThreadPool events.
13+
// The events here do not call into the typical WriteEvent* APIs unlike most EventSources because that results in the
14+
// events to be forwarded to EventListeners twice, once directly from the managed WriteEvent API, and another time
15+
// from the mechanism in NativeRuntimeEventSource.ProcessEvents that forwards native runtime events to EventListeners.
16+
// To prevent this, these events call directly into QCalls provided by the runtime (refer to NativeRuntimeEventSource.cs) which call
17+
// FireEtw* methods auto-generated from ClrEtwAll.man. This ensures that corresponding event sinks are being used
18+
// for the native platform. Refer to src/coreclr/vm/nativeruntimesource.cpp.
19+
// For Mono implementation of these events, refer to NativeRuntimeEventSource.PortableThreadPool.cs.
20+
internal sealed partial class NativeRuntimeEventSource : EventSource
21+
{
22+
// This value does not seem to be used, leaving it as zero for now. It may be useful for a scenario that may involve
23+
// multiple instances of the runtime within the same process, but then it seems unlikely that both instances' thread
24+
// pools would be in moderate use.
25+
private const ushort DefaultClrInstanceId = 0;
26+
27+
private static class Messages
28+
{
29+
public const string WorkerThread = "ActiveWorkerThreadCount={0};\nRetiredWorkerThreadCount={1};\nClrInstanceID={2}";
30+
public const string WorkerThreadAdjustmentSample = "Throughput={0};\nClrInstanceID={1}";
31+
public const string WorkerThreadAdjustmentAdjustment = "AverageThroughput={0};\nNewWorkerThreadCount={1};\nReason={2};\nClrInstanceID={3}";
32+
public const string WorkerThreadAdjustmentStats = "Duration={0};\nThroughput={1};\nThreadWave={2};\nThroughputWave={3};\nThroughputErrorEstimate={4};\nAverageThroughputErrorEstimate={5};\nThroughputRatio={6};\nConfidence={7};\nNewControlSetting={8};\nNewThreadWaveMagnitude={9};\nClrInstanceID={10}";
33+
public const string IOEnqueue = "NativeOverlapped={0};\nOverlapped={1};\nMultiDequeues={2};\nClrInstanceID={3}";
34+
public const string IO = "NativeOverlapped={0};\nOverlapped={1};\nClrInstanceID={2}";
35+
public const string WorkingThreadCount = "Count={0};\nClrInstanceID={1}";
36+
}
37+
38+
// The task definitions for the ETW manifest
39+
public static class Tasks // this name and visibility is important for EventSource
40+
{
41+
public const EventTask ThreadPoolWorkerThread = (EventTask)16;
42+
public const EventTask ThreadPoolWorkerThreadAdjustment = (EventTask)18;
43+
public const EventTask ThreadPool = (EventTask)23;
44+
public const EventTask ThreadPoolWorkingThreadCount = (EventTask)22;
45+
}
46+
47+
public static class Opcodes // this name and visibility is important for EventSource
48+
{
49+
public const EventOpcode IOEnqueue = (EventOpcode)13;
50+
public const EventOpcode IODequeue = (EventOpcode)14;
51+
public const EventOpcode Wait = (EventOpcode)90;
52+
public const EventOpcode Sample = (EventOpcode)100;
53+
public const EventOpcode Adjustment = (EventOpcode)101;
54+
public const EventOpcode Stats = (EventOpcode)102;
55+
}
56+
57+
public enum ThreadAdjustmentReasonMap : uint
58+
{
59+
Warmup,
60+
Initializing,
61+
RandomMove,
62+
ClimbingMove,
63+
ChangePoint,
64+
Stabilizing,
65+
Starvation,
66+
ThreadTimedOut
67+
}
68+
69+
[Event(50, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)]
70+
public unsafe void ThreadPoolWorkerThreadStart(
71+
uint ActiveWorkerThreadCount,
72+
uint RetiredWorkerThreadCount = 0,
73+
ushort ClrInstanceID = DefaultClrInstanceId)
74+
{
75+
if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword))
76+
{
77+
LogThreadPoolWorkerThreadStart(ActiveWorkerThreadCount, RetiredWorkerThreadCount, ClrInstanceID);
78+
}
79+
}
80+
81+
[Event(51, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = EventOpcode.Stop, Version = 0, Keywords = Keywords.ThreadingKeyword)]
82+
public void ThreadPoolWorkerThreadStop(
83+
uint ActiveWorkerThreadCount,
84+
uint RetiredWorkerThreadCount = 0,
85+
ushort ClrInstanceID = DefaultClrInstanceId)
86+
{
87+
if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword))
88+
{
89+
LogThreadPoolWorkerThreadStop(ActiveWorkerThreadCount, RetiredWorkerThreadCount, ClrInstanceID);
90+
}
91+
}
92+
93+
[Event(57, Level = EventLevel.Informational, Message = Messages.WorkerThread, Task = Tasks.ThreadPoolWorkerThread, Opcode = Opcodes.Wait, Version = 0, Keywords = Keywords.ThreadingKeyword)]
94+
[MethodImpl(MethodImplOptions.NoInlining)]
95+
public void ThreadPoolWorkerThreadWait(
96+
uint ActiveWorkerThreadCount,
97+
uint RetiredWorkerThreadCount = 0,
98+
ushort ClrInstanceID = DefaultClrInstanceId)
99+
{
100+
if (IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword))
101+
{
102+
LogThreadPoolWorkerThreadWait(ActiveWorkerThreadCount, RetiredWorkerThreadCount, ClrInstanceID);
103+
}
104+
}
105+
106+
[Event(54, Level = EventLevel.Informational, Message = Messages.WorkerThreadAdjustmentSample, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Sample, Version = 0, Keywords = Keywords.ThreadingKeyword)]
107+
public unsafe void ThreadPoolWorkerThreadAdjustmentSample(
108+
double Throughput,
109+
ushort ClrInstanceID = DefaultClrInstanceId)
110+
{
111+
if (!IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword))
112+
{
113+
return;
114+
}
115+
LogThreadPoolWorkerThreadAdjustmentSample(Throughput, ClrInstanceID);
116+
}
117+
118+
[Event(55, Level = EventLevel.Informational, Message = Messages.WorkerThreadAdjustmentAdjustment, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Adjustment, Version = 0, Keywords = Keywords.ThreadingKeyword)]
119+
public unsafe void ThreadPoolWorkerThreadAdjustmentAdjustment(
120+
double AverageThroughput,
121+
uint NewWorkerThreadCount,
122+
ThreadAdjustmentReasonMap Reason,
123+
ushort ClrInstanceID = DefaultClrInstanceId)
124+
{
125+
if (!IsEnabled(EventLevel.Informational, Keywords.ThreadingKeyword))
126+
{
127+
return;
128+
}
129+
LogThreadPoolWorkerThreadAdjustmentAdjustment(AverageThroughput, NewWorkerThreadCount, Reason, ClrInstanceID);
130+
}
131+
132+
[Event(56, Level = EventLevel.Verbose, Message = Messages.WorkerThreadAdjustmentStats, Task = Tasks.ThreadPoolWorkerThreadAdjustment, Opcode = Opcodes.Stats, Version = 0, Keywords = Keywords.ThreadingKeyword)]
133+
public unsafe void ThreadPoolWorkerThreadAdjustmentStats(
134+
double Duration,
135+
double Throughput,
136+
double ThreadWave,
137+
double ThroughputWave,
138+
double ThroughputErrorEstimate,
139+
double AverageThroughputErrorEstimate,
140+
double ThroughputRatio,
141+
double Confidence,
142+
double NewControlSetting,
143+
ushort NewThreadWaveMagnitude,
144+
ushort ClrInstanceID = DefaultClrInstanceId)
145+
{
146+
if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword))
147+
{
148+
return;
149+
}
150+
LogThreadPoolWorkerThreadAdjustmentStats(Duration, Throughput, ThreadWave, ThroughputWave, ThroughputErrorEstimate, AverageThroughputErrorEstimate, ThroughputRatio, Confidence, NewControlSetting, NewThreadWaveMagnitude, ClrInstanceID);
151+
}
152+
153+
[Event(63, Level = EventLevel.Verbose, Message = Messages.IOEnqueue, Task = Tasks.ThreadPool, Opcode = Opcodes.IOEnqueue, Version = 0, Keywords = Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)]
154+
private unsafe void ThreadPoolIOEnqueue(
155+
IntPtr NativeOverlapped,
156+
IntPtr Overlapped,
157+
bool MultiDequeues,
158+
ushort ClrInstanceID = DefaultClrInstanceId)
159+
{
160+
int multiDequeuesInt = Convert.ToInt32(MultiDequeues); // bool maps to "win:Boolean", a 4-byte boolean
161+
LogThreadPoolIOEnqueue(NativeOverlapped, Overlapped, MultiDequeues, ClrInstanceID);
162+
}
163+
164+
// TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use
165+
// FrameworkEventSource's thread transfer send/receive events instead at callers.
166+
[NonEvent]
167+
[MethodImpl(MethodImplOptions.NoInlining)]
168+
public void ThreadPoolIOEnqueue(RegisteredWaitHandle registeredWaitHandle)
169+
{
170+
if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword))
171+
{
172+
ThreadPoolIOEnqueue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero, registeredWaitHandle.Repeating);
173+
}
174+
}
175+
176+
[Event(64, Level = EventLevel.Verbose, Message = Messages.IO, Task = Tasks.ThreadPool, Opcode = Opcodes.IODequeue, Version = 0, Keywords = Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword)]
177+
private unsafe void ThreadPoolIODequeue(
178+
IntPtr NativeOverlapped,
179+
IntPtr Overlapped,
180+
ushort ClrInstanceID = DefaultClrInstanceId)
181+
{
182+
LogThreadPoolIODequeue(NativeOverlapped, Overlapped, ClrInstanceID);
183+
}
184+
185+
// TODO: This event is fired for minor compat with CoreCLR in this case. Consider removing this method and use
186+
// FrameworkEventSource's thread transfer send/receive events instead at callers.
187+
[NonEvent]
188+
[MethodImpl(MethodImplOptions.NoInlining)]
189+
public void ThreadPoolIODequeue(RegisteredWaitHandle registeredWaitHandle)
190+
{
191+
if (IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword | Keywords.ThreadTransferKeyword))
192+
{
193+
ThreadPoolIODequeue((IntPtr)registeredWaitHandle.GetHashCode(), IntPtr.Zero);
194+
}
195+
}
196+
197+
[Event(60, Level = EventLevel.Verbose, Message = Messages.WorkingThreadCount, Task = Tasks.ThreadPoolWorkingThreadCount, Opcode = EventOpcode.Start, Version = 0, Keywords = Keywords.ThreadingKeyword)]
198+
public unsafe void ThreadPoolWorkingThreadCount(uint Count, ushort ClrInstanceID = DefaultClrInstanceId)
199+
{
200+
if (!IsEnabled(EventLevel.Verbose, Keywords.ThreadingKeyword))
201+
{
202+
return;
203+
}
204+
LogThreadPoolWorkingThreadCount(Count, ClrInstanceID);
205+
}
206+
}
207+
}

src/coreclr/vm/ecalllist.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -989,13 +989,15 @@ FCFuncStart(gStreamFuncs)
989989
FCFuncEnd()
990990

991991

992-
#if defined(FEATURE_EVENTSOURCE_XPLAT) || defined(FEATURE_PERFTRACING)
993-
FCFuncStart(gEventLogger)
994992
#if defined(FEATURE_EVENTSOURCE_XPLAT)
993+
FCFuncStart(gEventLogger)
995994
QCFuncElement("IsEventSourceLoggingEnabled", XplatEventSourceLogger::IsEventSourceLoggingEnabled)
996995
QCFuncElement("LogEventSource", XplatEventSourceLogger::LogEventSource)
996+
FCFuncEnd()
997997
#endif // defined(FEATURE_EVENTSOURCE_XPLAT)
998+
998999
#if defined(FEATURE_PERFTRACING)
1000+
FCFuncStart(gNativeEventLogger)
9991001
QCFuncElement("LogThreadPoolWorkerThreadStart", NativeEventLogger::LogThreadPoolWorkerThreadStart)
10001002
QCFuncElement("LogThreadPoolWorkerThreadStop", NativeEventLogger::LogThreadPoolWorkerThreadStop)
10011003
QCFuncElement("LogThreadPoolWorkerThreadWait", NativeEventLogger::LogThreadPoolWorkerThreadWait)
@@ -1005,9 +1007,8 @@ FCFuncStart(gEventLogger)
10051007
QCFuncElement("LogThreadPoolIOEnqueue", NativeEventLogger::LogThreadPoolIOEnqueue)
10061008
QCFuncElement("LogThreadPoolIODequeue", NativeEventLogger::LogThreadPoolIODequeue)
10071009
QCFuncElement("LogThreadPoolWorkingThreadCount", NativeEventLogger::LogThreadPoolWorkingThreadCount)
1008-
#endif // defined(FEATURE_PERFTRACING)
10091010
FCFuncEnd()
1010-
#endif // defined(FEATURE_EVENTSOURCE_XPLAT) || defined(FEATURE_PERFTRACING)
1011+
#endif // defined(FEATURE_PERFTRACING)
10111012

10121013
#ifdef FEATURE_PERFTRACING
10131014
FCFuncStart(gEventPipeInternalFuncs)
@@ -1165,6 +1166,9 @@ FCClassElement("ModuleBuilder", "System.Reflection.Emit", gCOMModuleBuilderFuncs
11651166
FCClassElement("ModuleHandle", "System", gCOMModuleHandleFuncs)
11661167
FCClassElement("Monitor", "System.Threading", gMonitorFuncs)
11671168
FCClassElement("NativeLibrary", "System.Runtime.InteropServices", gInteropNativeLibraryFuncs)
1169+
#if defined(FEATURE_PERFTRACING)
1170+
FCClassElement("NativeRuntimeEventSource", "System.Diagnostics.Tracing", gNativeEventLogger)
1171+
#endif //defined(FEATURE_PERFTRACING)
11681172
#ifdef FEATURE_COMINTEROP
11691173
FCClassElement("OAVariantLib", "Microsoft.Win32", gOAVariantFuncs)
11701174
#endif
@@ -1213,7 +1217,7 @@ FCClassElement("WeakReference`1", "System", gWeakReferenceOfTFuncs)
12131217
FCClassElement("X86Base", "System.Runtime.Intrinsics.X86", gX86BaseFuncs)
12141218
#endif // defined(TARGET_X86) || defined(TARGET_AMD64)
12151219

1216-
#if defined(FEATURE_EVENTSOURCE_XPLAT) || defined(FEATURE_PERFTRACING)
1220+
#if defined(FEATURE_EVENTSOURCE_XPLAT)
12171221
FCClassElement("XplatEventLogger", "System.Diagnostics.Tracing", gEventLogger)
12181222
#endif //defined(FEATURE_EVENTSOURCE_XPLAT)
12191223

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1960,7 +1960,7 @@
19601960
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPool.Portable.cs" Condition="'$(FeatureCoreCLR)' != 'true'" />
19611961
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\ThreadPoolBoundHandle.PlatformNotSupported.cs" Condition="'$(FeatureCoreCLR)' != 'true'" />
19621962
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.cs" />
1963-
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NativeRuntimeEventSource.PortableThreadPool.cs" />
1963+
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\NativeRuntimeEventSource.PortableThreadPool.cs" Condition="'$(FeatureCoreCLR)' != 'true'" />
19641964
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.GateThread.cs" />
19651965
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.HillClimbing.cs" />
19661966
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\PortableThreadPool.HillClimbing.Complex.cs" />

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,15 @@ protected unsafe void WriteEventWithRelatedActivityIdCore(int eventId, Guid* rel
12831283
#endif // FEATURE_MANAGED_ETW
12841284

12851285
if (m_Dispatchers != null && m_eventData[eventId].EnabledForAnyListener)
1286-
WriteToAllListeners(eventId, pActivityId, relatedActivityId, eventDataCount, data);
1286+
{
1287+
#if MONO && !TARGET_BROWSER
1288+
// On Mono, managed events from NativeRuntimeEventSource are written using WriteEventCore which can be
1289+
// written doubly because EventPipe tries to pump it back up to EventListener via NativeRuntimeEventSource.ProcessEvents.
1290+
// So we need to prevent this from getting written directly to the Listeners.
1291+
if (this.GetType() != typeof(NativeRuntimeEventSource))
1292+
#endif // MONO && !TARGET_BROWSER
1293+
WriteToAllListeners(eventId, pActivityId, relatedActivityId, eventDataCount, data);
1294+
}
12871295
}
12881296
catch (Exception ex)
12891297
{

0 commit comments

Comments
 (0)