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 #include <dbghelp.h>
8 #include <sstream>
9 #include <psapi.h>
10
11 #include "BaseProfilerSharedLibraries.h"
12
13 #include "mozilla/UniquePtr.h"
14 #include "mozilla/Unused.h"
15 #include "mozilla/WindowsVersion.h"
16
17 #include <cctype>
18 #include <string>
19
20 #define CV_SIGNATURE 0x53445352 // 'SDSR'
21
22 struct CodeViewRecord70 {
23 uint32_t signature;
24 GUID pdbSignature;
25 uint32_t pdbAge;
26 // A UTF-8 string, according to
27 // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/locator.cpp#L785
28 char pdbFileName[1];
29 };
30
31 static constexpr char digits[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
32 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
33
AppendHex(const unsigned char * aBegin,const unsigned char * aEnd,std::string & aOut)34 static void AppendHex(const unsigned char* aBegin, const unsigned char* aEnd,
35 std::string& aOut) {
36 for (const unsigned char* p = aBegin; p < aEnd; ++p) {
37 unsigned char c = *p;
38 aOut += digits[c >> 4];
39 aOut += digits[c & 0xFu];
40 }
41 }
42
43 static constexpr bool WITH_PADDING = true;
44 static constexpr bool WITHOUT_PADDING = false;
45 template <typename T>
AppendHex(T aValue,std::string & aOut,bool aWithPadding)46 static void AppendHex(T aValue, std::string& aOut, bool aWithPadding) {
47 for (int i = sizeof(T) * 2 - 1; i >= 0; --i) {
48 unsigned nibble = (aValue >> (i * 4)) & 0xFu;
49 // If no-padding requested, skip starting zeroes -- unless we're on the very
50 // last nibble (so we don't output a blank).
51 if (!aWithPadding && i != 0) {
52 if (nibble == 0) {
53 // Requested no padding, skip zeroes.
54 continue;
55 }
56 // Requested no padding, got first non-zero, pretend we now want padding
57 // so we don't skip zeroes anymore.
58 aWithPadding = true;
59 }
60 aOut += digits[nibble];
61 }
62 }
63
GetPdbInfo(uintptr_t aStart,std::string & aSignature,uint32_t & aAge,char ** aPdbName)64 static bool GetPdbInfo(uintptr_t aStart, std::string& aSignature,
65 uint32_t& aAge, char** aPdbName) {
66 if (!aStart) {
67 return false;
68 }
69
70 PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(aStart);
71 if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
72 return false;
73 }
74
75 PIMAGE_NT_HEADERS ntHeaders =
76 reinterpret_cast<PIMAGE_NT_HEADERS>(aStart + dosHeader->e_lfanew);
77 if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
78 return false;
79 }
80
81 uint32_t relativeVirtualAddress =
82 ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]
83 .VirtualAddress;
84 if (!relativeVirtualAddress) {
85 return false;
86 }
87
88 PIMAGE_DEBUG_DIRECTORY debugDirectory =
89 reinterpret_cast<PIMAGE_DEBUG_DIRECTORY>(aStart + relativeVirtualAddress);
90 if (!debugDirectory || debugDirectory->Type != IMAGE_DEBUG_TYPE_CODEVIEW) {
91 return false;
92 }
93
94 CodeViewRecord70* debugInfo = reinterpret_cast<CodeViewRecord70*>(
95 aStart + debugDirectory->AddressOfRawData);
96 if (!debugInfo || debugInfo->signature != CV_SIGNATURE) {
97 return false;
98 }
99
100 aAge = debugInfo->pdbAge;
101 GUID& pdbSignature = debugInfo->pdbSignature;
102 AppendHex(pdbSignature.Data1, aSignature, WITH_PADDING);
103 AppendHex(pdbSignature.Data2, aSignature, WITH_PADDING);
104 AppendHex(pdbSignature.Data3, aSignature, WITH_PADDING);
105 AppendHex(reinterpret_cast<const unsigned char*>(&pdbSignature.Data4),
106 reinterpret_cast<const unsigned char*>(&pdbSignature.Data4) +
107 sizeof(pdbSignature.Data4),
108 aSignature);
109
110 // The PDB file name could be different from module filename, so report both
111 // e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb
112 *aPdbName = debugInfo->pdbFileName;
113
114 return true;
115 }
116
GetVersion(char * dllPath)117 static std::string GetVersion(char* dllPath) {
118 DWORD infoSize = GetFileVersionInfoSizeA(dllPath, nullptr);
119 if (infoSize == 0) {
120 return {};
121 }
122
123 mozilla::UniquePtr<unsigned char[]> infoData =
124 mozilla::MakeUnique<unsigned char[]>(infoSize);
125 if (!GetFileVersionInfoA(dllPath, 0, infoSize, infoData.get())) {
126 return {};
127 }
128
129 VS_FIXEDFILEINFO* vInfo;
130 UINT vInfoLen;
131 if (!VerQueryValueW(infoData.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen)) {
132 return {};
133 }
134 if (!vInfo) {
135 return {};
136 }
137
138 return std::to_string(vInfo->dwFileVersionMS >> 16) + '.' +
139 std::to_string(vInfo->dwFileVersionMS & 0xFFFF) + '.' +
140 std::to_string(vInfo->dwFileVersionLS >> 16) + '.' +
141 std::to_string(vInfo->dwFileVersionLS & 0xFFFF);
142 }
143
GetInfoForSelf()144 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() {
145 SharedLibraryInfo sharedLibraryInfo;
146
147 HANDLE hProcess = GetCurrentProcess();
148 mozilla::UniquePtr<HMODULE[]> hMods;
149 size_t modulesNum = 0;
150 if (hProcess != NULL) {
151 DWORD modulesSize;
152 if (!EnumProcessModules(hProcess, nullptr, 0, &modulesSize)) {
153 return sharedLibraryInfo;
154 }
155 modulesNum = modulesSize / sizeof(HMODULE);
156 hMods = mozilla::MakeUnique<HMODULE[]>(modulesNum);
157 if (!EnumProcessModules(hProcess, hMods.get(), modulesNum * sizeof(HMODULE),
158 &modulesSize)) {
159 return sharedLibraryInfo;
160 }
161 // The list may have shrunk between calls
162 if (modulesSize / sizeof(HMODULE) < modulesNum) {
163 modulesNum = modulesSize / sizeof(HMODULE);
164 }
165 }
166
167 for (unsigned int i = 0; i < modulesNum; i++) {
168 char modulePath[MAX_PATH + 1];
169 if (!GetModuleFileNameEx(hProcess, hMods[i], modulePath,
170 sizeof(modulePath) / sizeof(char))) {
171 continue;
172 }
173
174 MODULEINFO module = {0};
175 if (!GetModuleInformation(hProcess, hMods[i], &module,
176 sizeof(MODULEINFO))) {
177 continue;
178 }
179
180 std::string modulePathStr(modulePath);
181 size_t pos = modulePathStr.find_last_of("\\/");
182 std::string moduleNameStr = (pos != std::string::npos)
183 ? modulePathStr.substr(pos + 1)
184 : modulePathStr;
185
186 // Hackaround for Bug 1607574. Nvidia's shim driver nvd3d9wrap[x].dll
187 // detours LoadLibraryExW when it's loaded and the detour function causes
188 // AV when the code tries to access data pointing to an address within
189 // unloaded nvinit[x].dll.
190 // The crashing code is executed when a given parameter is "detoured.dll"
191 // and OS version is older than 6.2. We hit that crash at the following
192 // call to LoadLibraryEx even if we specify LOAD_LIBRARY_AS_DATAFILE.
193 // We work around it by skipping LoadLibraryEx, and add a library info with
194 // a dummy breakpad id instead.
195 #if !defined(_M_ARM64)
196 # if defined(_M_AMD64)
197 LPCSTR kNvidiaShimDriver = "nvd3d9wrapx.dll";
198 LPCSTR kNvidiaInitDriver = "nvinitx.dll";
199 # elif defined(_M_IX86)
200 LPCSTR kNvidiaShimDriver = "nvd3d9wrap.dll";
201 LPCSTR kNvidiaInitDriver = "nvinit.dll";
202 # endif
203 constexpr std::string_view detoured_dll = "detoured.dll";
204 if (std::equal(moduleNameStr.cbegin(), moduleNameStr.cend(),
205 detoured_dll.cbegin(), detoured_dll.cend(),
206 [](char aModuleChar, char aDetouredChar) {
207 return std::tolower(aModuleChar) == aDetouredChar;
208 }) &&
209 !mozilla::IsWin8OrLater() && ::GetModuleHandle(kNvidiaShimDriver) &&
210 !::GetModuleHandle(kNvidiaInitDriver)) {
211 const std::string pdbNameStr = "detoured.pdb";
212 SharedLibrary shlib((uintptr_t)module.lpBaseOfDll,
213 (uintptr_t)module.lpBaseOfDll + module.SizeOfImage,
214 0, // DLLs are always mapped at offset 0 on Windows
215 "000000000000000000000000000000000", moduleNameStr,
216 modulePathStr, pdbNameStr, pdbNameStr, "", "");
217 sharedLibraryInfo.AddSharedLibrary(shlib);
218 continue;
219 }
220 #endif // !defined(_M_ARM64)
221
222 std::string breakpadId;
223 // Load the module again to make sure that its handle will remain
224 // valid as we attempt to read the PDB information from it. We load the
225 // DLL as a datafile so that if the module actually gets unloaded between
226 // the call to EnumProcessModules and the following LoadLibraryEx, we
227 // don't end up running the now newly loaded module's DllMain function. If
228 // the module is already loaded, LoadLibraryEx just increments its
229 // refcount.
230 //
231 // Note that because of the race condition above, merely loading the DLL
232 // again is not safe enough, therefore we also need to make sure that we
233 // can read the memory mapped at the base address before we can safely
234 // proceed to actually access those pages.
235 HMODULE handleLock =
236 LoadLibraryEx(modulePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
237 MEMORY_BASIC_INFORMATION vmemInfo = {0};
238 std::string pdbSig;
239 uint32_t pdbAge;
240 std::string pdbPathStr;
241 std::string pdbNameStr;
242 char* pdbName = nullptr;
243 if (handleLock &&
244 sizeof(vmemInfo) ==
245 VirtualQuery(module.lpBaseOfDll, &vmemInfo, sizeof(vmemInfo)) &&
246 vmemInfo.State == MEM_COMMIT &&
247 GetPdbInfo((uintptr_t)module.lpBaseOfDll, pdbSig, pdbAge, &pdbName)) {
248 MOZ_ASSERT(breakpadId.empty());
249 breakpadId += pdbSig;
250 AppendHex(pdbAge, breakpadId, WITHOUT_PADDING);
251
252 pdbPathStr = pdbName;
253 size_t pos = pdbPathStr.find_last_of("\\/");
254 pdbNameStr =
255 (pos != std::string::npos) ? pdbPathStr.substr(pos + 1) : pdbPathStr;
256 }
257
258 SharedLibrary shlib((uintptr_t)module.lpBaseOfDll,
259 (uintptr_t)module.lpBaseOfDll + module.SizeOfImage,
260 0, // DLLs are always mapped at offset 0 on Windows
261 breakpadId, moduleNameStr, modulePathStr, pdbNameStr,
262 pdbPathStr, GetVersion(modulePath), "");
263 sharedLibraryInfo.AddSharedLibrary(shlib);
264
265 FreeLibrary(handleLock); // ok to free null handles
266 }
267
268 return sharedLibraryInfo;
269 }
270
Initialize()271 void SharedLibraryInfo::Initialize() { /* do nothing */
272 }
273