Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/common/conflicts/module_watcher_win.h" | |
| 6 | |
| 7 #include <windows.h> | |
| 8 #include <tlhelp32.h> | |
| 9 #include <winternl.h> // For UNICODE_STRING. | |
| 10 | |
| 11 #include "base/strings/utf_string_conversions.h" | |
| 12 #include "base/win/scoped_handle.h" | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // Global lock for ensuring synchronization of destruction and notifications. | |
| 17 base::Lock module_watcher_lock; | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
you need to use a leaky lazy instance here to avoi
grt (UTC plus 2)
2016/11/10 08:52:12
nit: g_foo here and below
chrisha
2016/11/14 16:06:44
Done.
chrisha
2016/11/14 16:06:45
Done.
| |
| 18 // Global pointer to the singleton ModuleWatcher, if one exists. Under | |
| 19 // |module_watcher_lock|. | |
| 20 ModuleWatcher* module_watcher_instance = nullptr; | |
| 21 | |
| 22 // These structures and functions are documented in MSDN, see | |
| 23 // http://msdn.microsoft.com/en-us/library/gg547638(v=vs.85).aspx | |
| 24 // there are however no headers or import libraries available in the | |
| 25 // Platform SDK. | |
| 26 enum { | |
| 27 // The DLL was loaded. The NotificationData parameter points to a | |
| 28 // LDR_DLL_LOADED_NOTIFICATION_DATA structure. | |
| 29 LDR_DLL_NOTIFICATION_REASON_LOADED = 1, | |
| 30 // The DLL was unloaded. The NotificationData parameter points to a | |
| 31 // LDR_DLL_UNLOADED_NOTIFICATION_DATA structure. | |
| 32 LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2, | |
| 33 }; | |
| 34 | |
| 35 // Structure that is used for module load notifications. | |
| 36 typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { | |
| 37 // Reserved. | |
| 38 ULONG Flags; | |
| 39 // The full path name of the DLL module. | |
| 40 PCUNICODE_STRING FullDllName; | |
| 41 // The base file name of the DLL module. | |
| 42 PCUNICODE_STRING BaseDllName; | |
| 43 // A pointer to the base address for the DLL in memory. | |
| 44 PVOID DllBase; | |
| 45 // The size of the DLL image, in bytes. | |
| 46 ULONG SizeOfImage; | |
| 47 } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; | |
| 48 | |
| 49 // Structure that is used for module unload notifications. | |
| 50 typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { | |
| 51 // Reserved. | |
| 52 ULONG Flags; | |
| 53 // The full path name of the DLL module. | |
| 54 PCUNICODE_STRING FullDllName; | |
| 55 // The base file name of the DLL module. | |
| 56 PCUNICODE_STRING BaseDllName; | |
| 57 // A pointer to the base address for the DLL in memory. | |
| 58 PVOID DllBase; | |
| 59 // The size of the DLL image, in bytes. | |
| 60 ULONG SizeOfImage; | |
| 61 } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; | |
| 62 | |
| 63 // Union that is used for notifications. | |
| 64 typedef union _LDR_DLL_NOTIFICATION_DATA { | |
| 65 LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; | |
| 66 LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; | |
| 67 } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; | |
| 68 | |
| 69 // Signature of the notification callback function. | |
| 70 using PLDR_DLL_NOTIFICATION_FUNCTION = | |
| 71 VOID(CALLBACK*)(ULONG notification_reason, | |
| 72 const LDR_DLL_NOTIFICATION_DATA* notification_data, | |
| 73 PVOID context); | |
| 74 | |
| 75 // Signatures of the functions used for registering DLL notification callbacks. | |
| 76 using LdrRegisterDllNotificationFunc = | |
| 77 NTSTATUS(NTAPI*)(ULONG flags, | |
| 78 PLDR_DLL_NOTIFICATION_FUNCTION notification_function, | |
| 79 PVOID context, | |
| 80 PVOID* cookie); | |
| 81 using LdrUnregisterDllNotificationFunc = NTSTATUS(NTAPI*)(PVOID cookie); | |
| 82 | |
| 83 // Names of the DLL notification registration functions. These are exported by | |
| 84 // ntdll. | |
| 85 constexpr wchar_t kNtDll[] = L"ntdll.dll"; | |
| 86 constexpr char kLdrRegisterDllNotification[] = "LdrRegisterDllNotification"; | |
| 87 constexpr char kLdrUnregisterDllNotification[] = "LdrUnregisterDllNotification"; | |
| 88 | |
| 89 // Helper function for converting a UNICODE_STRING to a UTF8 std::string. | |
| 90 std::string ToString(const UNICODE_STRING* str) { | |
| 91 std::string s; | |
| 92 base::WideToUTF8(str->Buffer, str->Length / sizeof(wchar_t), &s); | |
| 93 return s; | |
| 94 } | |
| 95 | |
| 96 // static | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
remove
chrisha
2016/11/14 16:06:45
Done.
| |
| 97 template <typename NotificationDataType> | |
| 98 void OnModuleEvent(mojom::ModuleEventType event_type, | |
| 99 const NotificationDataType& notification_data, | |
| 100 const ModuleWatcher::OnModuleEventCallback& callback) { | |
| 101 mojom::ModuleEvent event; | |
| 102 event.event_type = event_type; | |
| 103 event.module_path = ToString(notification_data.FullDllName); | |
| 104 event.load_address = reinterpret_cast<uintptr_t>(notification_data.DllBase); | |
| 105 event.size = notification_data.SizeOfImage; | |
| 106 callback.Run(event); | |
| 107 } | |
| 108 | |
| 109 } // namespace | |
| 110 | |
| 111 // static | |
| 112 std::unique_ptr<ModuleWatcher> ModuleWatcher::Create( | |
| 113 const OnModuleEventCallback& callback) { | |
| 114 // If a ModuleWatcher already exists then bail out. | |
| 115 base::AutoLock lock(module_watcher_lock); | |
| 116 if (module_watcher_instance) | |
| 117 return nullptr; | |
| 118 | |
| 119 // This thread acquired the right to create a ModuleWatcher, so do so. | |
| 120 module_watcher_instance = new ModuleWatcher(callback); | |
| 121 return std::unique_ptr<ModuleWatcher>(module_watcher_instance); | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
nit:
return base::WrapUnique<ModuleWatcher>(modu
chrisha
2016/11/14 16:06:45
Done.
| |
| 122 } | |
| 123 | |
| 124 ModuleWatcher::ModuleWatcher(const OnModuleEventCallback& callback) | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
nit: move down below LoaderNotificationCallback to
chrisha
2016/11/14 16:06:45
Done.
| |
| 125 : callback_(callback) { | |
| 126 RegisterDllNotificationCallback(); | |
| 127 EnumerateAlreadyLoadedModules(); | |
| 128 } | |
| 129 | |
| 130 ModuleWatcher::~ModuleWatcher() { | |
| 131 // As soon as |module_watcher_instance| is null any dispatched callbacks | |
| 132 // will be silently absorbed by LoaderNotificationCallback. | |
| 133 base::AutoLock lock(module_watcher_lock); | |
| 134 DCHECK_EQ(module_watcher_instance, this); | |
| 135 module_watcher_instance = nullptr; | |
| 136 UnregisterDllNotificationCallback(); | |
| 137 } | |
| 138 | |
| 139 bool ModuleWatcher::RegisterDllNotificationCallback() { | |
| 140 LdrRegisterDllNotificationFunc reg_fn = | |
| 141 reinterpret_cast<LdrRegisterDllNotificationFunc>(::GetProcAddress( | |
| 142 ::GetModuleHandle(kNtDll), kLdrRegisterDllNotification)); | |
| 143 | |
| 144 if (!reg_fn) | |
| 145 return false; | |
| 146 | |
| 147 NTSTATUS status = reg_fn(0, reinterpret_cast<PLDR_DLL_NOTIFICATION_FUNCTION>( | |
| 148 &ModuleWatcher::LoaderNotificationCallback), | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
nit: omit "ModuleWatcher::"
chrisha
2016/11/14 16:06:45
Done.
| |
| 149 this, &dll_notification_cookie_); | |
| 150 return status == 0; | |
| 151 } | |
| 152 | |
| 153 bool ModuleWatcher::UnregisterDllNotificationCallback() { | |
| 154 LdrUnregisterDllNotificationFunc unreg_fn = | |
| 155 reinterpret_cast<LdrUnregisterDllNotificationFunc>(::GetProcAddress( | |
| 156 ::GetModuleHandle(kNtDll), kLdrUnregisterDllNotification)); | |
| 157 | |
| 158 if (!unreg_fn) | |
| 159 return false; | |
| 160 | |
| 161 NTSTATUS status = unreg_fn(dll_notification_cookie_); | |
| 162 return status == 0; | |
| 163 } | |
| 164 | |
| 165 bool ModuleWatcher::EnumerateAlreadyLoadedModules() { | |
| 166 // Get all modules in the current process. According to MSDN, | |
| 167 // CreateToolhelp32Snapshot should be retried as long as its returning | |
| 168 // ERROR_BAD_LENGTH. To avoid locking up here a retry limit is enforced. | |
| 169 base::win::ScopedHandle snap; | |
| 170 for (size_t i = 0; i < 5; ++i) { | |
| 171 snap.Set(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, | |
| 172 ::GetCurrentProcessId())); | |
|
grt (UTC plus 2)
2016/11/09 08:37:52
is GetCurrentProcessId free? if no, move outside o
chrisha
2016/11/14 16:06:45
Done.
| |
| 173 if (snap.IsValid()) | |
| 174 break; | |
| 175 if (::GetLastError() == ERROR_BAD_LENGTH) | |
|
grt (UTC plus 2)
2016/11/09 08:37:52
personal style: i'd invert the logic here and do a
chrisha
2016/11/14 16:06:44
Done.
| |
| 176 continue; | |
| 177 return false; | |
| 178 } | |
| 179 if (!snap.IsValid()) | |
| 180 return false; | |
| 181 | |
| 182 // Walk the module list. | |
| 183 MODULEENTRY32 module = {sizeof(module)}; | |
| 184 if (!::Module32First(snap.Get(), &module)) | |
|
grt (UTC plus 2)
2016/11/09 08:37:52
both functions are documented as handing success/f
chrisha
2016/11/14 16:06:44
Yeah, I initially was CHECKing on these, but obvio
| |
| 185 return false; | |
| 186 | |
| 187 do { | |
| 188 std::string s; | |
| 189 base::WideToUTF8(module.szExePath, ::wcslen(module.szExePath), &s); | |
| 190 | |
| 191 mojom::ModuleEvent event; | |
| 192 event.event_type = mojom::ModuleEventType::MODULE_ALREADY_LOADED; | |
| 193 event.module_path = s; | |
| 194 event.load_address = reinterpret_cast<uintptr_t>(module.modBaseAddr); | |
| 195 event.size = module.modBaseSize; | |
| 196 | |
| 197 callback_.Run(event); | |
| 198 } while (::Module32Next(snap.Get(), &module)); | |
| 199 | |
| 200 return true; | |
| 201 } | |
| 202 | |
| 203 // static | |
| 204 void __stdcall ModuleWatcher::LoaderNotificationCallback( | |
| 205 uint32_t notification_reason, | |
| 206 const void* notification_data, | |
| 207 void* context) { | |
| 208 auto* data = | |
| 209 reinterpret_cast<const LDR_DLL_NOTIFICATION_DATA*>(notification_data); | |
| 210 | |
| 211 // Get a copy of the callback to invoke. | |
| 212 ModuleWatcher* module_watcher = reinterpret_cast<ModuleWatcher*>(context); | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
|module_watcher| shouldn't actually be used as one
chrisha
2016/11/14 16:06:45
Great idea! Done.
| |
| 213 ModuleWatcher::OnModuleEventCallback callback; | |
|
grt (UTC plus 2)
2016/11/10 08:52:12
nit: omit "ModuleWatcher::"
chrisha
2016/11/14 16:06:45
Done.
| |
| 214 { | |
| 215 base::AutoLock lock(module_watcher_lock); | |
| 216 if (module_watcher_instance != module_watcher) | |
| 217 return; | |
| 218 callback = module_watcher->callback_; | |
| 219 } | |
| 220 | |
| 221 switch (notification_reason) { | |
| 222 case LDR_DLL_NOTIFICATION_REASON_LOADED: | |
| 223 OnModuleEvent(mojom::ModuleEventType::MODULE_LOADED, data->Loaded, | |
| 224 callback); | |
| 225 break; | |
| 226 | |
| 227 case LDR_DLL_NOTIFICATION_REASON_UNLOADED: | |
| 228 OnModuleEvent(mojom::ModuleEventType::MODULE_UNLOADED, data->Unloaded, | |
| 229 callback); | |
| 230 break; | |
| 231 | |
| 232 default: | |
| 233 // This is unexpected, but not a reason to crash. | |
| 234 DCHECK(false) << "Unknown LDR_DLL_NOTIFICATION_REASON: " | |
|
grt (UTC plus 2)
2016/11/09 08:37:52
NOTREACHED()
chrisha
2016/11/09 15:38:07
Oops, I thought NOTREACHED was a fatal thing. Didn
| |
| 235 << notification_reason; | |
| 236 } | |
| 237 } | |
| OLD | NEW |