123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- // Copyright 2008 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include "Common/LdrWatcher.h"
- #include <Windows.h>
- #include <TlHelp32.h>
- #include <string>
- #include <winternl.h>
- typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA
- {
- ULONG Flags; // Reserved.
- PCUNICODE_STRING FullDllName; // The full path name of the DLL module.
- PCUNICODE_STRING BaseDllName; // The base file name of the DLL module.
- PVOID DllBase; // A pointer to the base address for the DLL in memory.
- ULONG SizeOfImage; // The size of the DLL image, in bytes.
- } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA;
- typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA
- {
- ULONG Flags; // Reserved.
- PCUNICODE_STRING FullDllName; // The full path name of the DLL module.
- PCUNICODE_STRING BaseDllName; // The base file name of the DLL module.
- PVOID DllBase; // A pointer to the base address for the DLL in memory.
- ULONG SizeOfImage; // The size of the DLL image, in bytes.
- } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA;
- typedef union _LDR_DLL_NOTIFICATION_DATA
- {
- LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
- LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
- } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA;
- typedef const LDR_DLL_NOTIFICATION_DATA* PCLDR_DLL_NOTIFICATION_DATA;
- #define LDR_DLL_NOTIFICATION_REASON_LOADED (1)
- #define LDR_DLL_NOTIFICATION_REASON_UNLOADED (2)
- typedef VOID NTAPI LDR_DLL_NOTIFICATION_FUNCTION(_In_ ULONG NotificationReason,
- _In_ PCLDR_DLL_NOTIFICATION_DATA NotificationData,
- _In_opt_ PVOID Context);
- typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION;
- typedef NTSTATUS(NTAPI* LdrRegisterDllNotification_t)(
- _In_ ULONG Flags, _In_ PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction,
- _In_opt_ PVOID Context, _Out_ PVOID* Cookie);
- typedef NTSTATUS(NTAPI* LdrUnregisterDllNotification_t)(_In_ PVOID Cookie);
- static void LdrObserverRun(const LdrObserver& observer, PCUNICODE_STRING module_name,
- uintptr_t base_address)
- {
- for (auto& needle : observer.module_names)
- {
- // Like RtlCompareUnicodeString, but saves dynamically resolving it.
- // NOTE: Does not compare null terminator.
- auto compare_length = module_name->Length / sizeof(wchar_t);
- if (!_wcsnicmp(needle.c_str(), module_name->Buffer, compare_length))
- observer.action({needle, base_address});
- }
- }
- static VOID DllNotificationCallback(ULONG NotificationReason,
- PCLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context)
- {
- if (NotificationReason != LDR_DLL_NOTIFICATION_REASON_LOADED)
- return;
- auto& data = NotificationData->Loaded;
- auto observer = static_cast<const LdrObserver*>(Context);
- LdrObserverRun(*observer, data.BaseDllName, reinterpret_cast<uintptr_t>(data.DllBase));
- }
- // This only works on Vista+. On lower platforms, it will be a no-op.
- class LdrDllNotifier
- {
- public:
- static LdrDllNotifier& GetInstance()
- {
- static LdrDllNotifier notifier;
- return notifier;
- }
- void Install(LdrObserver* observer);
- void Uninstall(LdrObserver* observer);
- private:
- LdrDllNotifier();
- bool Init();
- LdrRegisterDllNotification_t LdrRegisterDllNotification{};
- LdrUnregisterDllNotification_t LdrUnregisterDllNotification{};
- bool initialized{};
- };
- LdrDllNotifier::LdrDllNotifier()
- {
- initialized = Init();
- }
- bool LdrDllNotifier::Init()
- {
- auto ntdll = GetModuleHandleW(L"ntdll");
- if (!ntdll)
- return false;
- LdrRegisterDllNotification = reinterpret_cast<decltype(LdrRegisterDllNotification)>(
- GetProcAddress(ntdll, "LdrRegisterDllNotification"));
- if (!LdrRegisterDllNotification)
- return false;
- LdrUnregisterDllNotification = reinterpret_cast<decltype(LdrUnregisterDllNotification)>(
- GetProcAddress(ntdll, "LdrUnregisterDllNotification"));
- if (!LdrUnregisterDllNotification)
- return false;
- return true;
- }
- void LdrDllNotifier::Install(LdrObserver* observer)
- {
- if (!initialized)
- return;
- void* cookie{};
- if (!NT_SUCCESS(LdrRegisterDllNotification(0, DllNotificationCallback,
- static_cast<PVOID>(observer), &cookie)))
- cookie = {};
- observer->cookie = cookie;
- return;
- }
- void LdrDllNotifier::Uninstall(LdrObserver* observer)
- {
- if (!initialized)
- return;
- LdrUnregisterDllNotification(observer->cookie);
- observer->cookie = {};
- return;
- }
- LdrWatcher::~LdrWatcher()
- {
- UninstallAll();
- }
- // Needed for RtlInitUnicodeString
- #pragma comment(lib, "ntdll")
- bool LdrWatcher::InjectCurrentModules(const LdrObserver& observer)
- {
- // Use TlHelp32 instead of psapi functions to reduce dolphin's dependency on psapi
- // (revisit this when Win7 support is dropped).
- HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
- if (snapshot == INVALID_HANDLE_VALUE)
- return false;
- MODULEENTRY32 entry;
- entry.dwSize = sizeof(entry);
- for (BOOL rv = Module32First(snapshot, &entry); rv == TRUE; rv = Module32Next(snapshot, &entry))
- {
- UNICODE_STRING module_name;
- RtlInitUnicodeString(&module_name, entry.szModule);
- LdrObserverRun(observer, &module_name, reinterpret_cast<uintptr_t>(entry.modBaseAddr));
- }
- CloseHandle(snapshot);
- return true;
- }
- void LdrWatcher::Install(const LdrObserver& observer)
- {
- observers.emplace_back(observer);
- auto& new_observer = observers.back();
- // Register for notifications before looking at the list of current modules.
- // This ensures none are missed, but there is a tiny chance some will be seen twice.
- LdrDllNotifier::GetInstance().Install(&new_observer);
- InjectCurrentModules(new_observer);
- }
- void LdrWatcher::UninstallAll()
- {
- for (auto& observer : observers)
- LdrDllNotifier::GetInstance().Uninstall(&observer);
- observers.clear();
- }
|