diff --git a/mozglue/baseprofiler/core/shared-libraries-win32.cc b/mozglue/baseprofiler/core/shared-libraries-win32.cc index 5bf740819342..521754e1188f 100644 --- a/mozglue/baseprofiler/core/shared-libraries-win32.cc +++ b/mozglue/baseprofiler/core/shared-libraries-win32.cc @@ -12,6 +12,7 @@ #include "mozilla/glue/WindowsUnicode.h" #include "mozilla/Unused.h" +#include "mozilla/WindowsEnumProcessModules.h" #include "mozilla/WindowsVersion.h" #include @@ -114,7 +115,7 @@ static bool GetPdbInfo(uintptr_t aStart, std::string& aSignature, return true; } -static std::string GetVersion(wchar_t* dllPath) { +static std::string GetVersion(const wchar_t* dllPath) { DWORD infoSize = GetFileVersionInfoSizeW(dllPath, nullptr); if (infoSize == 0) { return {}; @@ -144,42 +145,19 @@ static std::string GetVersion(wchar_t* dllPath) { SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { SharedLibraryInfo sharedLibraryInfo; - HANDLE hProcess = GetCurrentProcess(); - mozilla::UniquePtr hMods; - size_t modulesNum = 0; - if (hProcess != NULL) { - DWORD modulesSize; - if (!EnumProcessModules(hProcess, nullptr, 0, &modulesSize)) { - return sharedLibraryInfo; - } - modulesNum = modulesSize / sizeof(HMODULE); - hMods = mozilla::MakeUnique(modulesNum); - if (!EnumProcessModules(hProcess, hMods.get(), modulesNum * sizeof(HMODULE), - &modulesSize)) { - return sharedLibraryInfo; - } - // The list may have shrunk between calls - if (modulesSize / sizeof(HMODULE) < modulesNum) { - modulesNum = modulesSize / sizeof(HMODULE); - } - } - - for (unsigned int i = 0; i < modulesNum; i++) { - wchar_t modulePath[MAX_PATH + 1]; - if (!GetModuleFileNameExW(hProcess, hMods[i], modulePath, - std::size(modulePath))) { - continue; - } + auto addSharedLibraryFromModuleInfo = [&sharedLibraryInfo]( + const wchar_t* aModulePath, + HMODULE aModule) { mozilla::UniquePtr utf8ModulePath( - mozilla::glue::WideToUTF8(modulePath)); + mozilla::glue::WideToUTF8(aModulePath)); if (!utf8ModulePath) { - continue; + return; } MODULEINFO module = {0}; - if (!GetModuleInformation(hProcess, hMods[i], &module, + if (!GetModuleInformation(mozilla::nt::kCurrentProcess, aModule, &module, sizeof(MODULEINFO))) { - continue; + return; } std::string modulePathStr(utf8ModulePath.get()); @@ -220,7 +198,7 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { "000000000000000000000000000000000", moduleNameStr, modulePathStr, pdbNameStr, pdbNameStr, "", ""); sharedLibraryInfo.AddSharedLibrary(shlib); - continue; + return; } #endif // !defined(_M_ARM64) @@ -238,7 +216,7 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { // can read the memory mapped at the base address before we can safely // proceed to actually access those pages. HMODULE handleLock = - LoadLibraryExW(modulePath, NULL, LOAD_LIBRARY_AS_DATAFILE); + LoadLibraryExW(aModulePath, NULL, LOAD_LIBRARY_AS_DATAFILE); MEMORY_BASIC_INFORMATION vmemInfo = {0}; std::string pdbSig; uint32_t pdbAge; @@ -264,12 +242,13 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { (uintptr_t)module.lpBaseOfDll + module.SizeOfImage, 0, // DLLs are always mapped at offset 0 on Windows breakpadId, moduleNameStr, modulePathStr, pdbNameStr, - pdbPathStr, GetVersion(modulePath), ""); + pdbPathStr, GetVersion(aModulePath), ""); sharedLibraryInfo.AddSharedLibrary(shlib); FreeLibrary(handleLock); // ok to free null handles - } + }; + mozilla::EnumerateProcessModules(addSharedLibraryFromModuleInfo); return sharedLibraryInfo; } diff --git a/mozglue/misc/WindowsEnumProcessModules.h b/mozglue/misc/WindowsEnumProcessModules.h new file mode 100644 index 000000000000..573b0dbdfacd --- /dev/null +++ b/mozglue/misc/WindowsEnumProcessModules.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_WindowsEnumProcessModules_h +#define mozilla_WindowsEnumProcessModules_h + +#include +#include + +#include "mozilla/FunctionRef.h" +#include "mozilla/NativeNt.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WinHeaderOnlyUtils.h" + +namespace mozilla { + +// Why don't we use CreateToolhelp32Snapshot instead of EnumProcessModules? +// CreateToolhelp32Snapshot gets the ANSI versions of module path strings +// via ntdll!RtlQueryProcessDebugInformation and stores them into a snapshot. +// Module32FirstW/Module32NextW re-converts ANSI into Unicode, but it cannot +// restore lost information. This means we still need GetModuleFileNameEx +// even when we use CreateToolhelp32Snapshot, but EnumProcessModules is faster. +inline bool EnumerateProcessModules( + const FunctionRef& aCallback) { + DWORD modulesSize; + if (!::EnumProcessModules(nt::kCurrentProcess, nullptr, 0, &modulesSize)) { + return false; + } + + DWORD modulesNum = modulesSize / sizeof(HMODULE); + UniquePtr modules = MakeUnique(modulesNum); + if (!::EnumProcessModules(nt::kCurrentProcess, modules.get(), + modulesNum * sizeof(HMODULE), &modulesSize)) { + return false; + } + + // The list may have shrunk between calls + if (modulesSize / sizeof(HMODULE) < modulesNum) { + modulesNum = modulesSize / sizeof(HMODULE); + } + + for (DWORD i = 0; i < modulesNum; ++i) { + UniquePtr modulePath = GetFullModulePath(modules[i]); + if (!modulePath) { + continue; + } + + // Please note that modules[i] could be invalid if the module + // was unloaded after GetFullModulePath succeeded. + aCallback(modulePath.get(), modules[i]); + } + + return true; +} + +} // namespace mozilla + +#endif // mozilla_WindowsEnumProcessModules_h diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build index c56ad717708b..45c2a7773ecf 100644 --- a/mozglue/misc/moz.build +++ b/mozglue/misc/moz.build @@ -59,6 +59,7 @@ if CONFIG["OS_ARCH"] == "WINNT": "DynamicallyLinkedFunctionPtr.h", "ImportDir.h", "NativeNt.h", + "WindowsEnumProcessModules.h", "WindowsMapRemoteView.h", "WindowsProcessMitigations.h", ] diff --git a/mozglue/tests/TestNativeNt.cpp b/mozglue/tests/TestNativeNt.cpp index 77cd3ad4a3e5..63e987877a21 100644 --- a/mozglue/tests/TestNativeNt.cpp +++ b/mozglue/tests/TestNativeNt.cpp @@ -8,9 +8,11 @@ #include "mozilla/NativeNt.h" #include "mozilla/ThreadLocal.h" #include "mozilla/UniquePtr.h" +#include "mozilla/WindowsEnumProcessModules.h" #include #include +#include const wchar_t kNormal[] = L"Foo.dll"; const wchar_t kHex12[] = L"Foo.ABCDEF012345.dll"; @@ -65,6 +67,127 @@ bool TestVirtualQuery(HANDLE aProcess, LPCVOID aAddress) { return true; } +// This class copies the self executable file to the %temp%\\ +// folder. The length of its path is longer than MAX_PATH. +class LongNameModule { + wchar_t mOuterDirBuffer[MAX_PATH]; + wchar_t mInnerDirBuffer[MAX_PATH * 2]; + wchar_t mTargetFileBuffer[MAX_PATH * 2]; + + const wchar_t* mOuterDir; + const wchar_t* mInnerDir; + const wchar_t* mTargetFile; + + public: + explicit LongNameModule(const wchar_t* aNewLeafNameAfterCopy) + : mOuterDir(nullptr), mInnerDir(nullptr), mTargetFile(nullptr) { + const wchar_t kFolderName160Chars[] = + L"0123456789ABCDEF0123456789ABCDEF" + L"0123456789ABCDEF0123456789ABCDEF" + L"0123456789ABCDEF0123456789ABCDEF" + L"0123456789ABCDEF0123456789ABCDEF" + L"0123456789ABCDEF0123456789ABCDEF"; + UniquePtr thisExe = GetFullBinaryPath(); + if (!thisExe) { + return; + } + + // If the buffer is too small, GetTempPathW returns the required + // length including a null character, while on a successful case + // it returns the number of copied characters which does not include + // a null character. This means len == MAX_PATH should never happen + // and len > MAX_PATH means GetTempPathW failed. + wchar_t tempDir[MAX_PATH]; + DWORD len = ::GetTempPathW(MAX_PATH, tempDir); + if (!len || len >= MAX_PATH) { + return; + } + + if (FAILED(::StringCbPrintfW(mOuterDirBuffer, sizeof(mOuterDirBuffer), + L"\\\\?\\%s%s", tempDir, + kFolderName160Chars)) || + !::CreateDirectoryW(mOuterDirBuffer, nullptr)) { + return; + } + mOuterDir = mOuterDirBuffer; + + if (FAILED(::StringCbPrintfW(mInnerDirBuffer, sizeof(mInnerDirBuffer), + L"\\\\?\\%s%s\\%s", tempDir, + kFolderName160Chars, kFolderName160Chars)) || + !::CreateDirectoryW(mInnerDirBuffer, nullptr)) { + return; + } + mInnerDir = mInnerDirBuffer; + + if (FAILED(::StringCbPrintfW(mTargetFileBuffer, sizeof(mTargetFileBuffer), + L"\\\\?\\%s%s\\%s\\%s", tempDir, + kFolderName160Chars, kFolderName160Chars, + aNewLeafNameAfterCopy)) || + !::CopyFileW(thisExe.get(), mTargetFileBuffer, + /*bFailIfExists*/ TRUE)) { + return; + } + mTargetFile = mTargetFileBuffer; + } + + ~LongNameModule() { + if (mTargetFile) { + ::DeleteFileW(mTargetFile); + } + if (mInnerDir) { + ::RemoveDirectoryW(mInnerDir); + } + if (mOuterDir) { + ::RemoveDirectoryW(mOuterDir); + } + } + + operator const wchar_t*() const { return mTargetFile; } +}; + +bool TestModuleInfo() { + UNICODE_STRING newLeafName; + ::RtlInitUnicodeString(&newLeafName, + L"\u672D\u5E4C\u5473\u564C.\u30E9\u30FC\u30E1\u30F3"); + + LongNameModule longNameModule(newLeafName.Buffer); + if (!longNameModule) { + printf( + "TEST-FAILED | NativeNt | " + "Failed to copy the executable to a long directory path\n"); + return 1; + } + + { + nsModuleHandle module(::LoadLibraryW(longNameModule)); + + bool detectedTarget = false; + auto moduleCallback = [&](const wchar_t* aModulePath, HMODULE aModule) { + UNICODE_STRING modulePath, moduleName; + ::RtlInitUnicodeString(&modulePath, aModulePath); + GetLeafName(&moduleName, &modulePath); + if (::RtlEqualUnicodeString(&moduleName, &newLeafName, + /*aCaseInsensitive*/ TRUE)) { + detectedTarget = true; + } + }; + + if (!mozilla::EnumerateProcessModules(moduleCallback)) { + printf("TEST-FAILED | NativeNt | EnumerateProcessModules failed\n"); + return false; + } + + if (!detectedTarget) { + printf( + "TEST-FAILED | NativeNt | " + "EnumerateProcessModules missed the target file\n"); + return false; + } + } + + return true; +} + LauncherResult GetModuleHandleFromLeafName(const wchar_t* aName) { UNICODE_STRING name; ::RtlInitUnicodeString(&name, aName); @@ -290,6 +413,10 @@ int wmain(int argc, wchar_t* argv[]) { return 1; } + if (!TestModuleInfo()) { + return 1; + } + printf("TEST-PASS | NativeNt | All tests ran successfully\n"); return 0; } diff --git a/tools/profiler/core/shared-libraries-win32.cc b/tools/profiler/core/shared-libraries-win32.cc index e209e4328fe1..a4289a66d4c3 100644 --- a/tools/profiler/core/shared-libraries-win32.cc +++ b/tools/profiler/core/shared-libraries-win32.cc @@ -12,6 +12,7 @@ #include "nsWindowsHelpers.h" #include "mozilla/UniquePtr.h" #include "mozilla/Unused.h" +#include "mozilla/WindowsEnumProcessModules.h" #include "mozilla/WindowsProcessMitigations.h" #include "mozilla/WindowsVersion.h" #include "nsNativeCharsetUtils.h" @@ -79,7 +80,7 @@ static bool GetPdbInfo(uintptr_t aStart, nsID& aSignature, uint32_t& aAge, return true; } -static nsCString GetVersion(WCHAR* dllPath) { +static nsCString GetVersion(const WCHAR* dllPath) { DWORD infoSize = GetFileVersionInfoSizeW(dllPath, nullptr); if (infoSize == 0) { return ""_ns; @@ -110,40 +111,16 @@ static nsCString GetVersion(WCHAR* dllPath) { SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { SharedLibraryInfo sharedLibraryInfo; - HANDLE hProcess = GetCurrentProcess(); - mozilla::UniquePtr hMods; - size_t modulesNum = 0; - if (hProcess != NULL) { - DWORD modulesSize; - if (!EnumProcessModules(hProcess, nullptr, 0, &modulesSize)) { - return sharedLibraryInfo; - } - modulesNum = modulesSize / sizeof(HMODULE); - hMods = mozilla::MakeUnique(modulesNum); - if (!EnumProcessModules(hProcess, hMods.get(), modulesNum * sizeof(HMODULE), - &modulesSize)) { - return sharedLibraryInfo; - } - // The list may have shrunk between calls - if (modulesSize / sizeof(HMODULE) < modulesNum) { - modulesNum = modulesSize / sizeof(HMODULE); - } - } - - for (unsigned int i = 0; i < modulesNum; i++) { - WCHAR modulePath[MAX_PATH + 1]; - if (!GetModuleFileNameEx(hProcess, hMods[i], modulePath, - sizeof(modulePath) / sizeof(WCHAR))) { - continue; - } - + auto addSharedLibraryFromModuleInfo = [&sharedLibraryInfo]( + const wchar_t* aModulePath, + HMODULE aModule) { MODULEINFO module = {0}; - if (!GetModuleInformation(hProcess, hMods[i], &module, + if (!GetModuleInformation(mozilla::nt::kCurrentProcess, aModule, &module, sizeof(MODULEINFO))) { - continue; + return; } - nsAutoString modulePathStr(modulePath); + nsAutoString modulePathStr(aModulePath); nsAutoString moduleNameStr = modulePathStr; int32_t pos = moduleNameStr.RFindCharInSet(u"\\/"); if (pos != kNotFound) { @@ -177,7 +154,7 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { "000000000000000000000000000000000"_ns, moduleNameStr, modulePathStr, pdbNameStr, pdbNameStr, ""_ns, ""); sharedLibraryInfo.AddSharedLibrary(shlib); - continue; + return; } #endif // !defined(_M_ARM64) @@ -199,7 +176,7 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { // can read the memory mapped at the base address before we can safely // proceed to actually access those pages. HMODULE handleLock = - LoadLibraryEx(modulePath, NULL, LOAD_LIBRARY_AS_DATAFILE); + LoadLibraryEx(aModulePath, NULL, LOAD_LIBRARY_AS_DATAFILE); MEMORY_BASIC_INFORMATION vmemInfo = {0}; nsID pdbSig; uint32_t pdbAge; @@ -233,12 +210,13 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() { (uintptr_t)module.lpBaseOfDll + module.SizeOfImage, 0, // DLLs are always mapped at offset 0 on Windows breakpadId, moduleNameStr, modulePathStr, pdbNameStr, - pdbPathStr, GetVersion(modulePath), ""); + pdbPathStr, GetVersion(aModulePath), ""); sharedLibraryInfo.AddSharedLibrary(shlib); FreeLibrary(handleLock); // ok to free null handles - } + }; + mozilla::EnumerateProcessModules(addSharedLibraryFromModuleInfo); return sharedLibraryInfo; }