summaryrefslogtreecommitdiffstats
path: root/mozglue/baseprofiler/core/shared-libraries-win32.cc
blob: eb6ca3572adef9b291ea45dd9861e5d00327bab6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <windows.h>

#include "BaseProfilerSharedLibraries.h"

#include "mozilla/glue/WindowsUnicode.h"
#include "mozilla/NativeNt.h"
#include "mozilla/WindowsEnumProcessModules.h"
#include "mozilla/WindowsVersion.h"

#include <cctype>
#include <string>

static constexpr char uppercaseDigits[16] = {'0', '1', '2', '3', '4', '5',
                                             '6', '7', '8', '9', 'A', 'B',
                                             'C', 'D', 'E', 'F'};
static constexpr char lowercaseDigits[16] = {'0', '1', '2', '3', '4', '5',
                                             '6', '7', '8', '9', 'a', 'b',
                                             'c', 'd', 'e', 'f'};

static void AppendHex(const unsigned char* aBegin, const unsigned char* aEnd,
                      std::string& aOut) {
  for (const unsigned char* p = aBegin; p < aEnd; ++p) {
    unsigned char c = *p;
    aOut += uppercaseDigits[c >> 4];
    aOut += uppercaseDigits[c & 0xFu];
  }
}

static constexpr bool WITH_PADDING = true;
static constexpr bool WITHOUT_PADDING = false;
static constexpr bool LOWERCASE = true;
static constexpr bool UPPERCASE = false;
template <typename T>
static void AppendHex(T aValue, std::string& aOut, bool aWithPadding,
                      bool aLowercase = UPPERCASE) {
  for (int i = sizeof(T) * 2 - 1; i >= 0; --i) {
    unsigned nibble = (aValue >> (i * 4)) & 0xFu;
    // If no-padding requested, skip starting zeroes -- unless we're on the very
    // last nibble (so we don't output a blank).
    if (!aWithPadding && i != 0) {
      if (nibble == 0) {
        // Requested no padding, skip zeroes.
        continue;
      }
      // Requested no padding, got first non-zero, pretend we now want padding
      // so we don't skip zeroes anymore.
      aWithPadding = true;
    }
    aOut += aLowercase ? lowercaseDigits[nibble] : uppercaseDigits[nibble];
  }
}

static bool IsModuleUnsafeToLoad(const std::string& aModuleName) {
  auto LowerCaseEqualsLiteral = [](char aModuleChar, char aDetouredChar) {
    return std::tolower(aModuleChar) == aDetouredChar;
  };

#if defined(_M_AMD64) || defined(_M_IX86)
  // Hackaround for Bug 1607574.  Nvidia's shim driver nvd3d9wrap[x].dll detours
  // LoadLibraryExW and it causes AV when the following conditions are met.
  //   1. LoadLibraryExW was called for "detoured.dll"
  //   2. nvinit[x].dll was unloaded
  //   3. OS version is older than 6.2
#  if defined(_M_AMD64)
  LPCWSTR kNvidiaShimDriver = L"nvd3d9wrapx.dll";
  LPCWSTR kNvidiaInitDriver = L"nvinitx.dll";
#  elif defined(_M_IX86)
  LPCWSTR kNvidiaShimDriver = L"nvd3d9wrap.dll";
  LPCWSTR kNvidiaInitDriver = L"nvinit.dll";
#  endif
  constexpr std::string_view detoured_dll = "detoured.dll";
  if (std::equal(aModuleName.cbegin(), aModuleName.cend(),
                 detoured_dll.cbegin(), detoured_dll.cend(),
                 LowerCaseEqualsLiteral) &&
      !mozilla::IsWin8OrLater() && ::GetModuleHandleW(kNvidiaShimDriver) &&
      !::GetModuleHandleW(kNvidiaInitDriver)) {
    return true;
  }
#endif  // defined(_M_AMD64) || defined(_M_IX86)

  // Hackaround for Bug 1723868.  There is no safe way to prevent the module
  // Microsoft's VP9 Video Decoder from being unloaded because mfplat.dll may
  // have posted more than one task to unload the module in the work queue
  // without calling LoadLibrary.
  constexpr std::string_view vp9_decoder_dll = "msvp9dec_store.dll";
  if (std::equal(aModuleName.cbegin(), aModuleName.cend(),
                 vp9_decoder_dll.cbegin(), vp9_decoder_dll.cend(),
                 LowerCaseEqualsLiteral)) {
    return true;
  }

  return false;
}

void SharedLibraryInfo::AddSharedLibraryFromModuleInfo(
    const wchar_t* aModulePath, mozilla::Maybe<HMODULE> aModule) {
  mozilla::UniquePtr<char[]> utf8ModulePath(
      mozilla::glue::WideToUTF8(aModulePath));
  if (!utf8ModulePath) {
    return;
  }

  std::string modulePathStr(utf8ModulePath.get());
  size_t pos = modulePathStr.find_last_of("\\/");
  std::string moduleNameStr = (pos != std::string::npos)
                                  ? modulePathStr.substr(pos + 1)
                                  : modulePathStr;

  // If the module is unsafe to call LoadLibraryEx for, we skip.
  if (IsModuleUnsafeToLoad(moduleNameStr)) {
    return;
  }

  // Load the module again - to make sure that its handle will remain valid as
  // we attempt to read the PDB information from it - or for the first time if
  // we only have a path. We want to load the DLL without running the newly
  // loaded module's DllMain function, but not as a data file because we want
  // to be able to do RVA computations easily. Hence, we use the flag
  // LOAD_LIBRARY_AS_IMAGE_RESOURCE which ensures that the sections (not PE
  // headers) will be relocated by the loader. Otherwise GetPdbInfo() and/or
  // GetVersionInfo() can cause a crash. If the original handle |aModule| is
  // valid, LoadLibraryEx just increments its refcount.
  nsModuleHandle handleLock(
      ::LoadLibraryExW(aModulePath, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE));
  if (!handleLock) {
    return;
  }

  mozilla::nt::PEHeaders headers(handleLock.get());
  if (!headers) {
    return;
  }

  mozilla::Maybe<mozilla::Range<const uint8_t>> bounds = headers.GetBounds();
  if (!bounds) {
    return;
  }

  // Put the original |aModule| into SharedLibrary, but we get debug info
  // from |handleLock| as |aModule| might be inaccessible.
  const uintptr_t modStart =
      aModule.isSome() ? reinterpret_cast<uintptr_t>(*aModule)
                       : reinterpret_cast<uintptr_t>(handleLock.get());
  const uintptr_t modEnd = modStart + bounds->length();

  std::string breakpadId;
  std::string pdbPathStr;
  std::string pdbNameStr;
  if (const auto* debugInfo = headers.GetPdbInfo()) {
    MOZ_ASSERT(breakpadId.empty());
    const GUID& pdbSig = debugInfo->pdbSignature;
    AppendHex(pdbSig.Data1, breakpadId, WITH_PADDING);
    AppendHex(pdbSig.Data2, breakpadId, WITH_PADDING);
    AppendHex(pdbSig.Data3, breakpadId, WITH_PADDING);
    AppendHex(reinterpret_cast<const unsigned char*>(&pdbSig.Data4),
              reinterpret_cast<const unsigned char*>(&pdbSig.Data4) +
                  sizeof(pdbSig.Data4),
              breakpadId);
    AppendHex(debugInfo->pdbAge, breakpadId, WITHOUT_PADDING);

    // The PDB file name could be different from module filename,
    // so report both
    // e.g. The PDB for C:\Windows\SysWOW64\ntdll.dll is wntdll.pdb
    pdbPathStr = debugInfo->pdbFileName;
    size_t pos = pdbPathStr.find_last_of("\\/");
    pdbNameStr =
        (pos != std::string::npos) ? pdbPathStr.substr(pos + 1) : pdbPathStr;
  }

  std::string codeId;
  DWORD timestamp;
  DWORD imageSize;
  if (headers.GetTimeStamp(timestamp) && headers.GetImageSize(imageSize)) {
    AppendHex(timestamp, codeId, WITH_PADDING);
    AppendHex(imageSize, codeId, WITHOUT_PADDING, LOWERCASE);
  }

  std::string versionStr;
  uint64_t version;
  if (headers.GetVersionInfo(version)) {
    versionStr += std::to_string((version >> 48) & 0xFFFF);
    versionStr += '.';
    versionStr += std::to_string((version >> 32) & 0xFFFF);
    versionStr += '.';
    versionStr += std::to_string((version >> 16) & 0xFFFF);
    versionStr += '.';
    versionStr += std::to_string(version & 0xFFFF);
  }

  SharedLibrary shlib(modStart, modEnd,
                      0,  // DLLs are always mapped at offset 0 on Windows
                      breakpadId, codeId, moduleNameStr, modulePathStr,
                      pdbNameStr, pdbPathStr, versionStr, "");
  AddSharedLibrary(shlib);
}

SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() {
  SharedLibraryInfo sharedLibraryInfo;

  auto addSharedLibraryFromModuleInfo =
      [&sharedLibraryInfo](const wchar_t* aModulePath, HMODULE aModule) {
        sharedLibraryInfo.AddSharedLibraryFromModuleInfo(
            aModulePath, mozilla::Some(aModule));
      };

  mozilla::EnumerateProcessModules(addSharedLibraryFromModuleInfo);
  return sharedLibraryInfo;
}

SharedLibraryInfo SharedLibraryInfo::GetInfoFromPath(const wchar_t* aPath) {
  SharedLibraryInfo sharedLibraryInfo;
  sharedLibraryInfo.AddSharedLibraryFromModuleInfo(aPath, mozilla::Nothing());
  return sharedLibraryInfo;
}

void SharedLibraryInfo::Initialize() { /* do nothing */
}