Bug 1702086 - Part1: Introduce EnumerateProcessModules. r=gerald

This patch introduces `EnumerateProcessModules` to enumerate all loaded modules
in the local process so that Gecko profiler and baseprofiler can use it.

Differential Revision: https://phabricator.services.mozilla.com/D115252
This commit is contained in:
Toshihito Kikuchi 2021-05-19 18:01:47 +00:00
parent 0da3a4882d
commit d64610fed5
5 changed files with 216 additions and 70 deletions

View File

@ -12,6 +12,7 @@
#include "mozilla/glue/WindowsUnicode.h"
#include "mozilla/Unused.h"
#include "mozilla/WindowsEnumProcessModules.h"
#include "mozilla/WindowsVersion.h"
#include <cctype>
@ -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<HMODULE[]> 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<HMODULE[]>(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<char[]> 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;
}

View File

@ -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 <windows.h>
#include <psapi.h>
#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<void(const wchar_t*, HMODULE)>& aCallback) {
DWORD modulesSize;
if (!::EnumProcessModules(nt::kCurrentProcess, nullptr, 0, &modulesSize)) {
return false;
}
DWORD modulesNum = modulesSize / sizeof(HMODULE);
UniquePtr<HMODULE[]> modules = MakeUnique<HMODULE[]>(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<wchar_t[]> 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

View File

@ -59,6 +59,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
"DynamicallyLinkedFunctionPtr.h",
"ImportDir.h",
"NativeNt.h",
"WindowsEnumProcessModules.h",
"WindowsMapRemoteView.h",
"WindowsProcessMitigations.h",
]

View File

@ -8,9 +8,11 @@
#include "mozilla/NativeNt.h"
#include "mozilla/ThreadLocal.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WindowsEnumProcessModules.h"
#include <stdio.h>
#include <windows.h>
#include <strsafe.h>
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%\<outer>\<inner>
// 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<wchar_t[]> 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<HMODULE> 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;
}

View File

@ -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<HMODULE[]> 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<HMODULE[]>(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;
}