Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(339)

Side by Side Diff: chrome/common/conflicts/module_watcher_win.cc

Issue 2473783005: [Win] Create ModuleWatcher. (Closed)
Patch Set: Address grt's nits. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 <string>
12
13 #include "base/lazy_instance.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/synchronization/lock.h"
17 #include "base/win/scoped_handle.h"
18
19 // These structures and functions are documented in MSDN, see
20 // http://msdn.microsoft.com/en-us/library/gg547638(v=vs.85).aspx
21 // there are however no headers or import libraries available in the
22 // Platform SDK. They are declared outside of the anonymous namespace to
23 // allow them to be forward declared in the header file.
24 enum {
25 // The DLL was loaded. The NotificationData parameter points to a
26 // LDR_DLL_LOADED_NOTIFICATION_DATA structure.
27 LDR_DLL_NOTIFICATION_REASON_LOADED = 1,
28 // The DLL was unloaded. The NotificationData parameter points to a
29 // LDR_DLL_UNLOADED_NOTIFICATION_DATA structure.
30 LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2,
31 };
32
33 // Structure that is used for module load notifications.
34 struct LDR_DLL_LOADED_NOTIFICATION_DATA {
35 // Reserved.
36 ULONG Flags;
37 // The full path name of the DLL module.
38 PCUNICODE_STRING FullDllName;
39 // The base file name of the DLL module.
40 PCUNICODE_STRING BaseDllName;
41 // A pointer to the base address for the DLL in memory.
42 PVOID DllBase;
43 // The size of the DLL image, in bytes.
44 ULONG SizeOfImage;
45 };
46 using PLDR_DLL_LOADED_NOTIFICATION_DATA = LDR_DLL_LOADED_NOTIFICATION_DATA*;
47
48 // Structure that is used for module unload notifications.
49 struct LDR_DLL_UNLOADED_NOTIFICATION_DATA {
50 // Reserved.
51 ULONG Flags;
52 // The full path name of the DLL module.
53 PCUNICODE_STRING FullDllName;
54 // The base file name of the DLL module.
55 PCUNICODE_STRING BaseDllName;
56 // A pointer to the base address for the DLL in memory.
57 PVOID DllBase;
58 // The size of the DLL image, in bytes.
59 ULONG SizeOfImage;
60 };
61 using PLDR_DLL_UNLOADED_NOTIFICATION_DATA = LDR_DLL_UNLOADED_NOTIFICATION_DATA*;
62
63 // Union that is used for notifications.
64 union LDR_DLL_NOTIFICATION_DATA {
65 LDR_DLL_LOADED_NOTIFICATION_DATA Loaded;
66 LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded;
67 };
68 using PLDR_DLL_NOTIFICATION_DATA = LDR_DLL_NOTIFICATION_DATA*;
69
70 // Signature of the notification callback function.
71 using PLDR_DLL_NOTIFICATION_FUNCTION =
72 VOID(CALLBACK*)(ULONG notification_reason,
73 const LDR_DLL_NOTIFICATION_DATA* notification_data,
74 PVOID context);
75
76 // Signatures of the functions used for registering DLL notification callbacks.
77 using LdrRegisterDllNotificationFunc =
78 NTSTATUS(NTAPI*)(ULONG flags,
79 PLDR_DLL_NOTIFICATION_FUNCTION notification_function,
80 PVOID context,
81 PVOID* cookie);
82 using LdrUnregisterDllNotificationFunc = NTSTATUS(NTAPI*)(PVOID cookie);
83
84 namespace {
85
86 // Global lock for ensuring synchronization of destruction and notifications.
87 base::LazyInstance<base::Lock>::Leaky g_module_watcher_lock =
sky 2016/11/14 22:14:16 These aren't global, so you shouldn't use "g_".
grt (UTC plus 2) 2016/11/15 10:35:34 Hi Scott. I asked Chris to put the g_ there, so I'
sky 2016/11/15 14:15:37 This has come up in the past on a number of review
88 LAZY_INSTANCE_INITIALIZER;
89 // Global pointer to the singleton ModuleWatcher, if one exists. Under
90 // |module_watcher_lock|.
91 ModuleWatcher* g_module_watcher_instance = nullptr;
92
93 // Names of the DLL notification registration functions. These are exported by
94 // ntdll.
95 constexpr wchar_t kNtDll[] = L"ntdll.dll";
96 constexpr char kLdrRegisterDllNotification[] = "LdrRegisterDllNotification";
97 constexpr char kLdrUnregisterDllNotification[] = "LdrUnregisterDllNotification";
98
99 // Helper function for converting a UNICODE_STRING to a UTF8 std::string.
100 std::string ToString(const UNICODE_STRING* str) {
101 std::string s;
102 base::WideToUTF8(str->Buffer, str->Length / sizeof(wchar_t), &s);
103 return s;
104 }
105
106 template <typename NotificationDataType>
107 void OnModuleEvent(mojom::ModuleEventType event_type,
108 const NotificationDataType& notification_data,
109 const ModuleWatcher::OnModuleEventCallback& callback) {
110 mojom::ModuleEvent event;
111 event.event_type = event_type;
112 event.module_path = ToString(notification_data.FullDllName);
113 event.load_address = reinterpret_cast<uintptr_t>(notification_data.DllBase);
114 event.size = notification_data.SizeOfImage;
115 callback.Run(event);
116 }
117
118 } // namespace
119
120 // static
121 std::unique_ptr<ModuleWatcher> ModuleWatcher::Create(
122 const OnModuleEventCallback& callback) {
123 // If a ModuleWatcher already exists then bail out.
124 base::AutoLock lock(g_module_watcher_lock.Get());
125 if (g_module_watcher_instance)
126 return nullptr;
127
128 // This thread acquired the right to create a ModuleWatcher, so do so.
129 g_module_watcher_instance = new ModuleWatcher(callback);
130 return base::WrapUnique(g_module_watcher_instance);
131 }
132
133 ModuleWatcher::~ModuleWatcher() {
134 // As soon as |g_module_watcher_instance| is null any dispatched callbacks
135 // will be silently absorbed by LoaderNotificationCallback.
136 base::AutoLock lock(g_module_watcher_lock.Get());
137 DCHECK_EQ(g_module_watcher_instance, this);
138 g_module_watcher_instance = nullptr;
139 UnregisterDllNotificationCallback();
140 }
141
142 void ModuleWatcher::RegisterDllNotificationCallback() {
143 LdrRegisterDllNotificationFunc reg_fn =
144 reinterpret_cast<LdrRegisterDllNotificationFunc>(::GetProcAddress(
145 ::GetModuleHandle(kNtDll), kLdrRegisterDllNotification));
146 if (reg_fn)
147 reg_fn(0, &LoaderNotificationCallback, this, &dll_notification_cookie_);
148 }
149
150 void ModuleWatcher::UnregisterDllNotificationCallback() {
151 LdrUnregisterDllNotificationFunc unreg_fn =
152 reinterpret_cast<LdrUnregisterDllNotificationFunc>(::GetProcAddress(
153 ::GetModuleHandle(kNtDll), kLdrUnregisterDllNotification));
154 if (unreg_fn)
155 unreg_fn(dll_notification_cookie_);
156 }
157
158 void ModuleWatcher::EnumerateAlreadyLoadedModules() {
159 // Get all modules in the current process. According to MSDN,
160 // CreateToolhelp32Snapshot should be retried as long as its returning
161 // ERROR_BAD_LENGTH. To avoid locking up here a retry limit is enforced.
162 base::win::ScopedHandle snap;
163 DWORD process_id = ::GetCurrentProcessId();
164 for (size_t i = 0; i < 5; ++i) {
165 snap.Set(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32,
166 process_id));
167 if (snap.IsValid())
168 break;
169 if (::GetLastError() != ERROR_BAD_LENGTH)
170 return;
171 }
172 if (!snap.IsValid())
173 return;
174
175 // Walk the module list.
176 MODULEENTRY32 module = {sizeof(module)};
Will Harris 2016/11/17 18:12:36 module.dwSize = sizeof(module_entry) before callin
chrisha 2016/11/17 21:40:31 The initializer does exactly that (dwSize is the f
Will Harris 2016/11/18 02:46:03 Acknowledged.
177 std::string path;
178 for (BOOL result = ::Module32First(snap.Get(), &module); result != FALSE;
179 result = ::Module32Next(snap.Get(), &module)) {
180 base::WideToUTF8(module.szExePath, ::wcslen(module.szExePath), &path);
181 mojom::ModuleEvent event;
182 event.event_type = mojom::ModuleEventType::MODULE_ALREADY_LOADED;
183 event.module_path = path;
184 event.load_address = reinterpret_cast<uintptr_t>(module.modBaseAddr);
185 event.size = module.modBaseSize;
186 callback_.Run(event);
187 }
188
189 return;
190 }
191
192 // static
193 ModuleWatcher::OnModuleEventCallback ModuleWatcher::GetCallbackForContext(
194 void* context) {
195 base::AutoLock lock(g_module_watcher_lock.Get());
196 if (context != g_module_watcher_instance)
Will Harris 2016/11/17 18:12:36 when does this ever happen?
chrisha 2016/11/17 21:40:31 If you tore down the watcher while a ldr notificat
Will Harris 2016/11/18 02:46:03 Acknowledged.
197 return OnModuleEventCallback();
198 return g_module_watcher_instance->callback_;
199 }
200
201 // static
202 void __stdcall ModuleWatcher::LoaderNotificationCallback(
203 unsigned long notification_reason,
204 const LDR_DLL_NOTIFICATION_DATA* notification_data,
205 void* context) {
206 auto callback = GetCallbackForContext(context);
207 if (callback.is_null())
Will Harris 2016/11/17 18:12:36 when will this ever be null?
chrisha 2016/11/17 21:40:31 Same story as above. If a pending ldr notification
Will Harris 2016/11/18 02:46:03 I see "if (callback)" everywhere else this is done
grt (UTC plus 2) 2016/11/18 11:45:54 Awesome! I hadn't noticed the bool operator before
chrisha 2016/11/21 16:34:47 Switched to using the bool operator.
208 return;
209
210 switch (notification_reason) {
211 case LDR_DLL_NOTIFICATION_REASON_LOADED:
212 OnModuleEvent(mojom::ModuleEventType::MODULE_LOADED,
213 notification_data->Loaded, callback);
214 break;
215
216 case LDR_DLL_NOTIFICATION_REASON_UNLOADED:
217 OnModuleEvent(mojom::ModuleEventType::MODULE_UNLOADED,
218 notification_data->Unloaded, callback);
219 break;
220
221 default:
222 // This is unexpected, but not a reason to crash.
223 NOTREACHED() << "Unknown LDR_DLL_NOTIFICATION_REASON: "
224 << notification_reason;
225 }
226 }
227
228 ModuleWatcher::ModuleWatcher(const OnModuleEventCallback& callback)
229 : callback_(callback) {
230 RegisterDllNotificationCallback();
231 EnumerateAlreadyLoadedModules();
232 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698