From 19145dbd1f8c390e6d079abc33330826a638db0a Mon Sep 17 00:00:00 2001 From: Mitchell Hwang <16830051+mdh1418@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:58:13 -0500 Subject: [PATCH] [EventTrace] AddEventPipe filter data parsing (#109624) * [EventTrace] Add EventPipe FilterData parsing * Add NativeAOT counterpart * Address feedback Only keep implemented filter types in enum Add EventPipe parsing to nativeaot counterpart Add bounds check on value portion of buffer * Guard against non null-terminated buffers * Fix macro guard * Fix break condition --- src/coreclr/nativeaot/Runtime/eventtrace.cpp | 69 +++++++++++++++++-- src/coreclr/vm/eventtrace.cpp | 71 ++++++++++++++++++-- src/native/eventpipe/ep-provider.c | 2 +- 3 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/eventtrace.cpp b/src/coreclr/nativeaot/Runtime/eventtrace.cpp index 8b3d134f5c4f24..e5484417fd93bb 100644 --- a/src/coreclr/nativeaot/Runtime/eventtrace.cpp +++ b/src/coreclr/nativeaot/Runtime/eventtrace.cpp @@ -94,6 +94,67 @@ enum CallbackProviderIndex DotNETRuntimePrivate = 3 }; +#ifdef FEATURE_ETW +// EventFilterType identifies the filter type used by the PEVENT_FILTER_DESCRIPTOR +enum EventFilterType +{ + // data should be pairs of UTF8 null terminated strings all concatenated together. + // The first element of the pair is the key and the 2nd is the value. We expect one of the + // keys to be the string "GCSeqNumber" and the value to be a number encoded as text. + // This is the standard way EventPipe encodes filter values + StringKeyValueEncoding = 0, + // data should be an 8 byte binary LONGLONG value + // this is the historic encoding defined by .NET Framework for use with ETW + LongBinaryClientSequenceNumber = 1 +}; + +void ParseFilterDataClientSequenceNumber( + EVENT_FILTER_DESCRIPTOR * FilterData, + LONGLONG * pClientSequenceNumber) +{ + if (FilterData == NULL) + return; + + if (FilterData->Type == LongBinaryClientSequenceNumber && FilterData->Size == sizeof(LONGLONG)) + { + *pClientSequenceNumber = *(LONGLONG *) (FilterData->Ptr); + } + else if (FilterData->Type == StringKeyValueEncoding) + { + const char* buffer = reinterpret_cast(FilterData->Ptr); + const char* buffer_end = buffer + FilterData->Size; + + while (buffer < buffer_end) + { + const char* key = buffer; + size_t key_len = strnlen(key, buffer_end - buffer); + buffer += key_len + 1; + + if (buffer >= buffer_end) + break; + + const char* value = buffer; + size_t value_len = strnlen(value, buffer_end - buffer); + buffer += value_len + 1; + + if (buffer > buffer_end) + break; + + if (strcmp(key, "GCSeqNumber") != 0) + continue; + + char* endPtr = nullptr; + long parsedValue = strtol(value, &endPtr, 10); + if (endPtr != value && *endPtr == '\0') + { + *pClientSequenceNumber = static_cast(parsedValue); + break; + } + } + } +} +#endif // FEATURE_ETW + void EtwCallbackCommon( CallbackProviderIndex ProviderIndex, ULONG ControlCode, @@ -164,13 +225,7 @@ void EtwCallbackCommon( // Profilers may (optionally) specify extra data in the filter parameter // to log with the GCStart event. LONGLONG l64ClientSequenceNumber = 0; - EVENT_FILTER_DESCRIPTOR* filterDesc = (EVENT_FILTER_DESCRIPTOR*)pFilterData; - if ((filterDesc != NULL) && - (filterDesc->Type == 1) && - (filterDesc->Size == sizeof(l64ClientSequenceNumber))) - { - l64ClientSequenceNumber = *(LONGLONG *) (filterDesc->Ptr); - } + ParseFilterDataClientSequenceNumber((EVENT_FILTER_DESCRIPTOR*)pFilterData, &l64ClientSequenceNumber); ETW::GCLog::ForceGC(l64ClientSequenceNumber); } #endif diff --git a/src/coreclr/vm/eventtrace.cpp b/src/coreclr/vm/eventtrace.cpp index 535a44f96dffb7..f83b65a05e6145 100644 --- a/src/coreclr/vm/eventtrace.cpp +++ b/src/coreclr/vm/eventtrace.cpp @@ -2328,6 +2328,69 @@ enum CallbackProviderIndex DotNETRuntimePrivate = 3 }; +#if !defined(HOST_UNIX) +// EventFilterType identifies the filter type used by the PEVENT_FILTER_DESCRIPTOR +enum EventFilterType +{ + // data should be pairs of UTF8 null terminated strings all concatenated together. + // The first element of the pair is the key and the 2nd is the value. We expect one of the + // keys to be the string "GCSeqNumber" and the value to be a number encoded as text. + // This is the standard way EventPipe encodes filter values + StringKeyValueEncoding = 0, + // data should be an 8 byte binary LONGLONG value + // this is the historic encoding defined by .NET Framework for use with ETW + LongBinaryClientSequenceNumber = 1 +}; + +VOID ParseFilterDataClientSequenceNumber( + PEVENT_FILTER_DESCRIPTOR FilterData, + LONGLONG * pClientSequenceNumber) +{ + LIMITED_METHOD_CONTRACT; + + if (FilterData == NULL) + return; + + if (FilterData->Type == LongBinaryClientSequenceNumber && FilterData->Size == sizeof(LONGLONG)) + { + *pClientSequenceNumber = *(LONGLONG *) (FilterData->Ptr); + } + else if (FilterData->Type == StringKeyValueEncoding) + { + const char* buffer = reinterpret_cast(FilterData->Ptr); + const char* buffer_end = buffer + FilterData->Size; + + while (buffer < buffer_end) + { + const char* key = buffer; + size_t key_len = strnlen(key, buffer_end - buffer); + buffer += key_len + 1; + + if (buffer >= buffer_end) + break; + + const char* value = buffer; + size_t value_len = strnlen(value, buffer_end - buffer); + buffer += value_len + 1; + + if (buffer > buffer_end) + break; + + if (strcmp(key, "GCSeqNumber") != 0) + continue; + + char* endPtr = nullptr; + long parsedValue = strtol(value, &endPtr, 10); + if (endPtr != value && *endPtr == '\0') + { + *pClientSequenceNumber = static_cast(parsedValue); + break; + } + } + } +} +#endif // !defined(HOST_UNIX) + // 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. @@ -2412,13 +2475,7 @@ VOID EtwCallbackCommon( // to log with the GCStart event. LONGLONG l64ClientSequenceNumber = 0; #if !defined(HOST_UNIX) - PEVENT_FILTER_DESCRIPTOR FilterData = (PEVENT_FILTER_DESCRIPTOR)pFilterData; - if ((FilterData != NULL) && - (FilterData->Type == 1) && - (FilterData->Size == sizeof(l64ClientSequenceNumber))) - { - l64ClientSequenceNumber = *(LONGLONG *) (FilterData->Ptr); - } + ParseFilterDataClientSequenceNumber((PEVENT_FILTER_DESCRIPTOR)pFilterData, &l64ClientSequenceNumber); #endif // !defined(HOST_UNIX) ETW::GCLog::ForceGC(l64ClientSequenceNumber); } diff --git a/src/native/eventpipe/ep-provider.c b/src/native/eventpipe/ep-provider.c index cf7d9eb51d3a2a..7670f23453ac46 100644 --- a/src/native/eventpipe/ep-provider.c +++ b/src/native/eventpipe/ep-provider.c @@ -421,7 +421,7 @@ provider_invoke_callback (EventPipeProviderCallbackData *provider_callback_data) if (j < filter_data_len) buffer_size = j + 1; - ep_event_filter_desc_init (&event_filter_desc, (uint64_t)buffer, buffer_size, 0); + ep_event_filter_desc_init (&event_filter_desc, (uint64_t)buffer, buffer_size, /* EventFilterType.StringKeyValueEncoding */ 0); is_event_filter_desc_init = true; }