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";