1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <windows.h>
7 
8 #include "BaseProfilerSharedLibraries.h"
9 
10 #include "mozilla/glue/WindowsUnicode.h"
11 #include "mozilla/NativeNt.h"
12 #include "mozilla/WindowsEnumProcessModules.h"
13 #include "mozilla/WindowsVersion.h"
14 
15 #include <cctype>
16 #include <string>
17 
18 static constexpr char digits[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
19                                     '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
20 
AppendHex(const unsigned char * aBegin,const unsigned char * aEnd,std::string & aOut)21 static void AppendHex(const unsigned char* aBegin, const unsigned char* aEnd,
22                       std::string& aOut) {
23   for (const unsigned char* p = aBegin; p < aEnd; ++p) {
24     unsigned char c = *p;
25     aOut += digits[c >> 4];
26     aOut += digits[c & 0xFu];
27   }
28 }
29 
30 static constexpr bool WITH_PADDING = true;
31 static constexpr bool WITHOUT_PADDING = false;
32 template <typename T>
AppendHex(T aValue,std::string & aOut,bool aWithPadding)33 static void AppendHex(T aValue, std::string& aOut, bool aWithPadding) {
34   for (int i = sizeof(T) * 2 - 1; i >= 0; --i) {
35     unsigned nibble = (aValue >> (i * 4)) & 0xFu;
36     // If no-padding requested, skip starting zeroes -- unless we're on the very
37     // last nibble (so we don't output a blank).
38     if (!aWithPadding && i != 0) {
39       if (nibble == 0) {
40         // Requested no padding, skip zeroes.
41         continue;
42       }
43       // Requested no padding, got first non-zero, pretend we now want padding
44       // so we don't skip zeroes anymore.
45       aWithPadding = true;
46     }
47     aOut += digits[nibble];
48   }
49 }
50 
IsModuleUnsafeToLoad(const std::string & aModuleName)51 static bool IsModuleUnsafeToLoad(const std::string& aModuleName) {
52   auto LowerCaseEqualsLiteral = [](char aModuleChar, char aDetouredChar) {
53     return std::tolower(aModuleChar) == aDetouredChar;
54   };
55 
56 #if defined(_M_AMD64) || defined(_M_IX86)
57   // Hackaround for Bug 1607574.  Nvidia's shim driver nvd3d9wrap[x].dll detours
58   // LoadLibraryExW and it causes AV when the following conditions are met.
59   //   1. LoadLibraryExW was called for "detoured.dll"
60   //   2. nvinit[x].dll was unloaded
61   //   3. OS version is older than 6.2
62 #  if defined(_M_AMD64)
63   LPCWSTR kNvidiaShimDriver = L"nvd3d9wrapx.dll";
64   LPCWSTR kNvidiaInitDriver = L"nvinitx.dll";
65 #  elif defined(_M_IX86)
66   LPCWSTR kNvidiaShimDriver = L"nvd3d9wrap.dll";
67   LPCWSTR kNvidiaInitDriver = L"nvinit.dll";
68 #  endif
69   constexpr std::string_view detoured_dll = "detoured.dll";
70   if (std::equal(aModuleName.cbegin(), aModuleName.cend(),
71                  detoured_dll.cbegin(), detoured_dll.cend(),
72                  LowerCaseEqualsLiteral) &&
73       !mozilla::IsWin8OrLater() && ::GetModuleHandleW(kNvidiaShimDriver) &&
74       !::GetModuleHandleW(kNvidiaInitDriver)) {
75     return true;
76   }
77 #endif  // defined(_M_AMD64) || defined(_M_IX86)
78 
79   // Hackaround for Bug 1723868.  There is no safe way to prevent the module
80   // Microsoft's VP9 Video Decoder from being unloaded because mfplat.dll may
81   // have posted more than one task to unload the module in the work queue
82   // without calling LoadLibrary.
83   constexpr std::string_view vp9_decoder_dll = "msvp9dec_store.dll";
84   if (std::equal(aModuleName.cbegin(), aModuleName.cend(),
85                  vp9_decoder_dll.cbegin(), vp9_decoder_dll.cend(),
86                  LowerCaseEqualsLiteral)) {
87     return true;
88   }
89 
90   return false;
91 }
92 
GetInfoForSelf()93 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() {
94   SharedLibraryInfo sharedLibraryInfo;
95 
96   auto addSharedLibraryFromModuleInfo =
97       [&sharedLibraryInfo](const wchar_t* aModulePath, HMODULE aModule) {
98         mozilla::UniquePtr<char[]> utf8ModulePath(
99             mozilla::glue::WideToUTF8(aModulePath));
100         if (!utf8ModulePath) {
101           return;
102         }
103 
104         std::string modulePathStr(utf8ModulePath.get());
105         size_t pos = modulePathStr.find_last_of("\\/");
106         std::string moduleNameStr = (pos != std::string::npos)
107                                         ? modulePathStr.substr(pos + 1)
108                                         : modulePathStr;
109 
110         // If the module is unsafe to call LoadLibraryEx for, we skip.
111         if (IsModuleUnsafeToLoad(moduleNameStr)) {
112           return;
113         }
114 
115         // Load the module again to make sure that its handle will remain
116         // valid as we attempt to read the PDB information from it.  We load the
117         // DLL as a datafile so that we don't end up running the newly loaded
118         // module's DllMain function.  If the original handle |aModule| is
119         // valid, LoadLibraryEx just increments its refcount.
120         // LOAD_LIBRARY_AS_IMAGE_RESOURCE is needed to read information from the
121         // sections (not PE headers) which should be relocated by the loader,
122         // otherwise GetPdbInfo() will cause a crash.
123         nsModuleHandle handleLock(::LoadLibraryExW(
124             aModulePath, NULL,
125             LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE));
126         if (!handleLock) {
127           return;
128         }
129 
130         mozilla::nt::PEHeaders headers(handleLock.get());
131         if (!headers) {
132           return;
133         }
134 
135         mozilla::Maybe<mozilla::Range<const uint8_t>> bounds =
136             headers.GetBounds();
137         if (!bounds) {
138           return;
139         }
140 
141         // Put the original |aModule| into SharedLibrary, but we get debug info
142         // from |handleLock| as |aModule| might be inaccessible.
143         const uintptr_t modStart = reinterpret_cast<uintptr_t>(aModule);
144         const uintptr_t modEnd = modStart + bounds->length();
145 
146         std::string breakpadId;
147         std::string pdbPathStr;
148         std::string pdbNameStr;
149         if (const auto* debugInfo = headers.GetPdbInfo()) {
150           MOZ_ASSERT(breakpadId.empty());
151           const GUID& pdbSig = debugInfo->pdbSignature;
152           AppendHex(pdbSig.Data1, breakpadId, WITH_PADDING);
153           AppendHex(pdbSig.Data2, breakpadId, WITH_PADDING);
154           AppendHex(pdbSig.Data3, breakpadId, WITH_PADDING);
155           AppendHex(reinterpret_cast<const unsigned char*>(&pdbSig.Data4),
156                     reinterpret_cast<const unsigned char*>(&pdbSig.Data4) +
157                         sizeof(pdbSig.Data4),
158                     breakpadId);
159           AppendHex(debugInfo->pdbAge, breakpadId, WITHOUT_PADDING);
160 
161           // The PDB file name could be different from module filename,
162           // so report both
163           // e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb
164           pdbPathStr = debugInfo->pdbFileName;
165           size_t pos = pdbPathStr.find_last_of("\\/");
166           pdbNameStr = (pos != std::string::npos) ? pdbPathStr.substr(pos + 1)
167                                                   : pdbPathStr;
168         }
169 
170         std::string versionStr;
171         uint64_t version;
172         if (headers.GetVersionInfo(version)) {
173           versionStr += std::to_string((version >> 48) & 0xFFFF);
174           versionStr += '.';
175           versionStr += std::to_string((version >> 32) & 0xFFFF);
176           versionStr += '.';
177           versionStr += std::to_string((version >> 16) & 0xFFFF);
178           versionStr += '.';
179           versionStr += std::to_string(version & 0xFFFF);
180         }
181 
182         SharedLibrary shlib(modStart, modEnd,
183                             0,  // DLLs are always mapped at offset 0 on Windows
184                             breakpadId, moduleNameStr, modulePathStr,
185                             pdbNameStr, pdbPathStr, versionStr, "");
186         sharedLibraryInfo.AddSharedLibrary(shlib);
187       };
188 
189   mozilla::EnumerateProcessModules(addSharedLibraryFromModuleInfo);
190   return sharedLibraryInfo;
191 }
192 
Initialize()193 void SharedLibraryInfo::Initialize() { /* do nothing */
194 }
195