From a68459bd41565a5ddc2c7c5ec1de64a02e460e26 Mon Sep 17 00:00:00 2001 From: Michael Winterberg Date: Fri, 3 Mar 2017 16:01:39 -0800 Subject: [PATCH] Added support for TraceLogging providers Only strictly necessary for pre-Windows 8 operating systems as the xperf from the Windows 10 Anniversary Edition version (August 2016) SDK supports the proper translation "out of the box". The algorithm to translate from TraceLogging provider name to GUID is based on the C# code posted by Doug Cook to https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ --- README | 6 + UIforETW/Settings.cpp | 1 + UIforETW/TraceLoggingSupport.cpp | 231 ++++++++++++++++++++++++++++++ UIforETW/TraceLoggingSupport.h | 22 +++ UIforETW/UIforETW.vcxproj | 2 + UIforETW/UIforETW.vcxproj.filters | 6 + UIforETW/UIforETWDlg.cpp | 56 +++++++- 7 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 UIforETW/TraceLoggingSupport.cpp create mode 100644 UIforETW/TraceLoggingSupport.h diff --git a/README b/README index 20afec8b..8471872b 100644 --- a/README +++ b/README @@ -24,6 +24,12 @@ associated with each one. UIforETW has some features that are specific to Chrome developers - but it is fully functional for non-Chrome developers as well. +When adding TraceLogging and EventSource providers, UIforETW supports +the same *Name naming convention as other ETW tools. See +https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ +to ensure that your provider name and GUID conform to the convention. +EventSource providers should automatically match. + If you want to use UIforETW then you should download the latest etwpackage.zip from the releases page at: https://github.com/google/UIforETW/releases diff --git a/UIforETW/Settings.cpp b/UIforETW/Settings.cpp index 3bc23863..83cfe8e1 100644 --- a/UIforETW/Settings.cpp +++ b/UIforETW/Settings.cpp @@ -173,6 +173,7 @@ BOOL CSettings::OnInitDialog() toolTip_.AddTool(&btExtraUserProviders_, L"Extra user providers, separated by '+', such as " L"\n\"Microsoft-Windows-Audio+Microsoft-Windows-HttpLog\". See \"xperf -providers\" " L"for the full list. " + L"TraceLogging and EventSource providers must be prefixed by '*'. " L"Note that incorrect user providers will cause tracing to fail to start."); toolTip_.AddTool(&btPerfCounters_, L"Arbitrary performance counters to be logged occasionally."); toolTip_.AddTool(&btWSMonitoredProcesses_, L"Names of processes whose working sets will be " diff --git a/UIforETW/TraceLoggingSupport.cpp b/UIforETW/TraceLoggingSupport.cpp new file mode 100644 index 00000000..1b7802a4 --- /dev/null +++ b/UIforETW/TraceLoggingSupport.cpp @@ -0,0 +1,231 @@ +/* +Copyright 2017 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "stdafx.h" +#include "TraceLoggingSupport.h" +#include "Utility.h" + +#include + +#include +#include + +#ifdef _DEBUG +#define new DEBUG_NEW +#endif + +#pragma comment(lib, "Bcrypt.lib") + +static std::system_error MakeSystemErrorFromWin32Error(DWORD errorCode, const char* context) +{ + std::error_code code(errorCode, std::system_category()); + return std::system_error(code, context); +} +namespace +{ +class SHA1HashProvider +{ + struct secret_init + { + }; + // this is just to force the destructor to be called + // after this constructor finishes, regardless of whether or not + // the calling constructor throws an exception + SHA1HashProvider(secret_init) + : algHandle_(nullptr) + , hashHandle_(nullptr) + { + } +public: + SHA1HashProvider(const SHA1HashProvider&) = delete; + SHA1HashProvider& operator=(const SHA1HashProvider&) = delete; + + SHA1HashProvider() + : SHA1HashProvider(secret_init()) + { + NTSTATUS error = BCryptOpenAlgorithmProvider(&algHandle_, BCRYPT_SHA1_ALGORITHM, nullptr, 0); + if (error != 0) + { + algHandle_ = nullptr; + throw MakeSystemErrorFromWin32Error(error, "BCryptOpenAlgorithmProvider"); + } + + DWORD objectLength; + DWORD cbData; + + error = BCryptGetProperty(algHandle_, BCRYPT_OBJECT_LENGTH, reinterpret_cast(&objectLength), sizeof(objectLength), &cbData, 0); + if (error != 0) + { + hashHandle_ = nullptr; + throw MakeSystemErrorFromWin32Error(error, "BCryptGetProperty(BCRYPT_OBJECT_LENGTH)"); + } + + hashObject_.resize(objectLength); + error = BCryptCreateHash(algHandle_, &hashHandle_, hashObject_.data(), objectLength, nullptr, 0, 0); + if (error != 0) + { + hashHandle_ = nullptr; + throw MakeSystemErrorFromWin32Error(error, "BCryptCreateHash"); + } + } + ~SHA1HashProvider() + { + if (hashHandle_ != nullptr) + { + ATLVERIFY(BCryptDestroyHash(hashHandle_) == 0); + } + + if (nullptr != algHandle_) + { + ATLVERIFY(BCryptCloseAlgorithmProvider(algHandle_, 0) == 0); + } + } + + void AddBytesForHash(const unsigned char* blob, unsigned long bytes) + { + // ugh. how did an API written for an OS released in 2006 not consider constness? + // if this cast isn't safe, we'll crash as the namespace bytes should be in + // .rdata. + NTSTATUS error = BCryptHashData(hashHandle_, const_cast(blob), bytes, 0); + if (error != 0) + { + throw MakeSystemErrorFromWin32Error(error, "CryptHashData"); + } + } + std::array FinishHash() const + { + std::array hashData; + NTSTATUS error = BCryptFinishHash(hashHandle_, hashData.data(), static_cast(hashData.size()), 0); + if (error != 0) + { + throw MakeSystemErrorFromWin32Error(error, "CryptGetHashParam"); + } + return hashData; + } + +private: + BCRYPT_ALG_HANDLE algHandle_; + BCRYPT_HASH_HANDLE hashHandle_; + std::vector hashObject_; +}; +} + + +// Intended to match .NET's ToUpperInvariant. Presumably +// "LOCALE_NAME_INVARIANT" in Win32 is a close enough proxy. +static std::wstring ToUpperInvariant(const std::wstring& mixed) +{ + std::wstring uppered; + int destLength = static_cast(mixed.size()); + if (destLength == 0) + return mixed; + + // destLength doesn't include the terminating NUL, so add it. + destLength++; + { + uppered.resize(destLength); + wchar_t* upperedbuf = &uppered[0]; + const int result = ::LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, mixed.c_str(), + -1, upperedbuf, destLength, nullptr, nullptr, 0); + const DWORD lastError = ::GetLastError(); + if (result > 0) + { + uppered.resize(result - 1); + return uppered; + } + uppered.resize(0); + if (lastError != ERROR_INSUFFICIENT_BUFFER) + throw MakeSystemErrorFromWin32Error(lastError, "LCMapStringEx"); + } + + destLength = ::LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, mixed.c_str(), -1, nullptr, + 0, nullptr, nullptr, 0); + if (destLength == 0) + { + const DWORD lastError = ::GetLastError(); + throw MakeSystemErrorFromWin32Error(lastError, "LCMapStringEx"); + } + + { + uppered.resize(destLength); + wchar_t* upperedbuf = &uppered[0]; + const int result = ::LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, mixed.c_str(), + -1, upperedbuf, destLength, nullptr, nullptr, 0); + const DWORD lastError = ::GetLastError(); + if (result > 0) + { + uppered.resize(result - 1); + return uppered; + } + throw MakeSystemErrorFromWin32Error(lastError, "LCMapStringEx"); + } +} + + +static std::vector GetBigEndianBytes(const std::wstring& str) +{ + std::vector bytes(str.size() * 2); + + for (size_t i = 0; i < str.size(); i++) + { + wchar_t u = str[i]; + bytes[i * 2] = u >> 8; + bytes[i * 2 + 1] = u & 0xFF; + } + + return bytes; +} + + +// Adapted from the C# here, +// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ +// Which just has +// /* +// This sample code is public - domain and may be used for any purpose. +// It is provided without warrantee, express or implied. +// */ +// for licensing information. +std::wstring TraceLoggingProviderNameToGUID(const std::wstring& provider) +{ + const std::vector providerNameBytes = GetBigEndianBytes(ToUpperInvariant(provider)); + + static constexpr std::array namespaceBytes = + { + 0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, + 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB, + }; + + SHA1HashProvider hasher; + hasher.AddBytesForHash(namespaceBytes.data(), static_cast(namespaceBytes.size())); + hasher.AddBytesForHash(providerNameBytes.data(), static_cast(providerNameBytes.size())); + + auto hashBytes = hasher.FinishHash(); + // Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122 + hashBytes[7] = ((hashBytes[7] & 0x0F) | 0x50); + GUID guidBytes; + memcpy(&guidBytes, hashBytes.data(), sizeof(guidBytes)); + + return stringPrintf(L"%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guidBytes.Data1, guidBytes.Data2, guidBytes.Data3, + guidBytes.Data4[0], guidBytes.Data4[1], guidBytes.Data4[2], + guidBytes.Data4[3], guidBytes.Data4[4], guidBytes.Data4[5], + guidBytes.Data4[6], guidBytes.Data4[7]); + +} + + + + diff --git a/UIforETW/TraceLoggingSupport.h b/UIforETW/TraceLoggingSupport.h new file mode 100644 index 00000000..bc528ece --- /dev/null +++ b/UIforETW/TraceLoggingSupport.h @@ -0,0 +1,22 @@ +/* +Copyright 2017 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#include + +extern std::wstring TraceLoggingProviderNameToGUID(const std::wstring& provider); + diff --git a/UIforETW/UIforETW.vcxproj b/UIforETW/UIforETW.vcxproj index d157d657..a4302562 100644 --- a/UIforETW/UIforETW.vcxproj +++ b/UIforETW/UIforETW.vcxproj @@ -267,6 +267,7 @@ + @@ -289,6 +290,7 @@ Create + diff --git a/UIforETW/UIforETW.vcxproj.filters b/UIforETW/UIforETW.vcxproj.filters index 56d89b95..e90502c8 100644 --- a/UIforETW/UIforETW.vcxproj.filters +++ b/UIforETW/UIforETW.vcxproj.filters @@ -63,6 +63,9 @@ Header Files + + Header Files + @@ -107,6 +110,9 @@ Source Files + + Source Files + diff --git a/UIforETW/UIforETWDlg.cpp b/UIforETW/UIforETWDlg.cpp index d233cdc1..9b9d1c1f 100644 --- a/UIforETW/UIforETWDlg.cpp +++ b/UIforETW/UIforETWDlg.cpp @@ -24,6 +24,7 @@ limitations under the License. #include "Utility.h" #include "WorkingSet.h" #include "Version.h" +#include "TraceLoggingSupport.h" #include #include @@ -83,6 +84,48 @@ void CUIforETWDlg::vprintf(const wchar_t* pFormat, va_list args) } +static std::wstring TranslateTraceLoggingProvider(const std::wstring& provider) +{ + std::wstring providerOptions; + std::wstring justProviderName(provider); + auto endOfProvider = justProviderName.find(L':'); + if (endOfProvider != std::wstring::npos) + { + providerOptions = justProviderName.substr(endOfProvider); + justProviderName.resize(endOfProvider); + } + + std::wstring providerGUID = TraceLoggingProviderNameToGUID(justProviderName); + + providerGUID += providerOptions; + return providerGUID; +} + +static std::wstring TranslateUserModeProviders(const std::wstring& providers) +{ + std::wstring translatedProviders; + translatedProviders.reserve(providers.size()); + for (const auto& provider : split(providers, '+')) + { + if (provider.empty()) + { + continue; + } + translatedProviders += '+'; + if (provider.front() != '*') + { + translatedProviders += provider; + continue; + } + // if the provider name begins with a *, it follows the EventSource / TraceLogging + // convention and must be translated to a GUID. + // remove the leading '*' before calling the function + translatedProviders += TranslateTraceLoggingProvider(provider.substr(1)); + } + + return translatedProviders; +} + CUIforETWDlg::CUIforETWDlg(CWnd* pParent /*=NULL*/) : CDialog(CUIforETWDlg::IDD, pParent) , monitorThread_(this) @@ -963,7 +1006,18 @@ void CUIforETWDlg::OnBnClickedStarttracing() userProviders += L"+Microsoft-Windows-Kernel-Memory:0xE0"; if (!extraUserProviders_.empty()) - userProviders += L"+" + extraUserProviders_; + { + try + { + userProviders += TranslateUserModeProviders(extraUserProviders_); + } + catch (const std::exception& e) + { + outputPrintf(L"Check the extra user providers; failed to translate them from the TraceLogging name to a GUID.\n%hs\n", e.what()); + StopEventThreads(); + return; + } + } // DWM providers can be helpful also. Uncomment to enable. //userProviders += L"+Microsoft-Windows-Dwm-Dwm";