Revert 247151 "Cleanup of windows launch code"
[chromium-blink-merge.git] / tools / memory_watcher / memory_hook.cc
blob64df93d204150978ae7046a6f71821893266c3f3
1 // Copyright (c) 2006-2008 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.
5 // Static class for hooking Win32 API routines.
7 // Some notes about how to hook Memory Allocation Routines in Windows.
8 //
9 // For our purposes we do not hook the libc routines. There are two
10 // reasons for this. First, the libc routines all go through HeapAlloc
11 // anyway. So, it's redundant to log both HeapAlloc and malloc.
12 // Second, it can be tricky to hook in both static and dynamic linkages
13 // of libc.
15 #include <windows.h>
17 #include "memory_hook.h"
18 #include "memory_watcher.h"
19 #include "preamble_patcher.h"
21 // Calls GetProcAddress, but casts to the correct type.
22 #define GET_PROC_ADDRESS(hmodule, name) \
23 ( (Type_##name)(::GetProcAddress(hmodule, #name)) )
25 // Macro to declare Patch functions.
26 #define DECLARE_PATCH(name) Patch<Type_##name> patch_##name
28 // Macro to install Patch functions.
29 #define INSTALL_PATCH(name) do { \
30 patch_##name.set_original(GET_PROC_ADDRESS(hkernel32, ##name)); \
31 patch_##name.Install(&Perftools_##name); \
32 } while (0)
34 // Macro to install Patch functions.
35 #define INSTALL_NTDLLPATCH(name) do { \
36 patch_##name.set_original(GET_PROC_ADDRESS(hntdll, ##name)); \
37 patch_##name.Install(&Perftools_##name); \
38 } while (0)
40 // Macro to uninstall Patch functions.
41 #define UNINSTALL_PATCH(name) patch_##name.Uninstall();
45 // Windows APIs to be hooked
47 // HeapAlloc routines
48 typedef HANDLE (WINAPI *Type_HeapCreate)(DWORD flOptions,
49 SIZE_T dwInitialSize,
50 SIZE_T dwMaximumSize);
51 typedef BOOL (WINAPI *Type_HeapDestroy)(HANDLE hHeap);
52 typedef LPVOID (WINAPI *Type_HeapAlloc)(HANDLE hHeap, DWORD dwFlags,
53 DWORD_PTR dwBytes);
54 typedef LPVOID (WINAPI *Type_HeapReAlloc)(HANDLE hHeap, DWORD dwFlags,
55 LPVOID lpMem, SIZE_T dwBytes);
56 typedef BOOL (WINAPI *Type_HeapFree)(HANDLE hHeap, DWORD dwFlags,
57 LPVOID lpMem);
59 // GlobalAlloc routines
60 typedef HGLOBAL (WINAPI *Type_GlobalAlloc)(UINT uFlags, SIZE_T dwBytes);
61 typedef HGLOBAL (WINAPI *Type_GlobalReAlloc)(HGLOBAL hMem, SIZE_T dwBytes,
62 UINT uFlags);
63 typedef HGLOBAL (WINAPI *Type_GlobalFree)(HGLOBAL hMem);
65 // LocalAlloc routines
66 typedef HLOCAL (WINAPI *Type_LocalAlloc)(UINT uFlags, SIZE_T uBytes);
67 typedef HLOCAL (WINAPI *Type_LocalReAlloc)(HLOCAL hMem, SIZE_T uBytes,
68 UINT uFlags);
69 typedef HLOCAL (WINAPI *Type_LocalFree)(HLOCAL hMem);
71 // A Windows-API equivalent of mmap and munmap, for "anonymous regions"
72 typedef LPVOID (WINAPI *Type_VirtualAllocEx)(HANDLE process, LPVOID address,
73 SIZE_T size, DWORD type,
74 DWORD protect);
75 typedef BOOL (WINAPI *Type_VirtualFreeEx)(HANDLE process, LPVOID address,
76 SIZE_T size, DWORD type);
78 // A Windows-API equivalent of mmap and munmap, for actual files
79 typedef LPVOID (WINAPI *Type_MapViewOfFile)(HANDLE hFileMappingObject,
80 DWORD dwDesiredAccess,
81 DWORD dwFileOffsetHigh,
82 DWORD dwFileOffsetLow,
83 SIZE_T dwNumberOfBytesToMap);
84 typedef LPVOID (WINAPI *Type_MapViewOfFileEx)(HANDLE hFileMappingObject,
85 DWORD dwDesiredAccess,
86 DWORD dwFileOffsetHigh,
87 DWORD dwFileOffsetLow,
88 SIZE_T dwNumberOfBytesToMap,
89 LPVOID lpBaseAddress);
90 typedef BOOL (WINAPI *Type_UnmapViewOfFile)(LPVOID lpBaseAddress);
92 typedef DWORD (WINAPI *Type_NtUnmapViewOfSection)(HANDLE process,
93 LPVOID lpBaseAddress);
96 // Patch is a template for keeping the pointer to the original
97 // hooked routine, the function to call when hooked, and the
98 // stub routine which is patched.
99 template<class T>
100 class Patch {
101 public:
102 // Constructor. Does not hook the function yet.
103 Patch<T>()
104 : original_function_(NULL),
105 patch_function_(NULL),
106 stub_function_(NULL) {
109 // Destructor. Unhooks the function if it has been hooked.
110 ~Patch<T>() {
111 Uninstall();
114 // Patches original function with func.
115 // Must have called set_original to set the original function.
116 void Install(T func) {
117 patch_function_ = func;
118 CHECK(patch_function_ != NULL);
119 CHECK(original_function_ != NULL);
120 CHECK(stub_function_ == NULL);
121 CHECK(sidestep::SIDESTEP_SUCCESS ==
122 sidestep::PreamblePatcher::Patch(original_function_,
123 patch_function_, &stub_function_));
126 // Un-patches the function.
127 void Uninstall() {
128 if (stub_function_)
129 sidestep::PreamblePatcher::Unpatch(original_function_,
130 patch_function_, stub_function_);
131 stub_function_ = NULL;
134 // Set the function to be patched.
135 void set_original(T original) { original_function_ = original; }
137 // Get the original function being patched.
138 T original() { return original_function_; }
140 // Get the patched function. (e.g. the replacement function)
141 T patched() { return patch_function_; }
143 // Access to the stub for calling the original function
144 // while it is patched.
145 T operator()() {
146 DCHECK(stub_function_);
147 return stub_function_;
150 private:
151 // The function that we plan to patch.
152 T original_function_;
153 // The function to replace the original with.
154 T patch_function_;
155 // To unpatch, we also need to keep around a "stub" that points to the
156 // pre-patched Windows function.
157 T stub_function_;
161 // All Windows memory-allocation routines call through to one of these.
162 DECLARE_PATCH(HeapCreate);
163 DECLARE_PATCH(HeapDestroy);
164 DECLARE_PATCH(HeapAlloc);
165 DECLARE_PATCH(HeapReAlloc);
166 DECLARE_PATCH(HeapFree);
167 DECLARE_PATCH(VirtualAllocEx);
168 DECLARE_PATCH(VirtualFreeEx);
169 DECLARE_PATCH(MapViewOfFile);
170 DECLARE_PATCH(MapViewOfFileEx);
171 DECLARE_PATCH(UnmapViewOfFile);
172 DECLARE_PATCH(GlobalAlloc);
173 DECLARE_PATCH(GlobalReAlloc);
174 DECLARE_PATCH(GlobalFree);
175 DECLARE_PATCH(LocalAlloc);
176 DECLARE_PATCH(LocalReAlloc);
177 DECLARE_PATCH(LocalFree);
178 DECLARE_PATCH(NtUnmapViewOfSection);
180 // Our replacement functions.
182 static HANDLE WINAPI Perftools_HeapCreate(DWORD flOptions,
183 SIZE_T dwInitialSize,
184 SIZE_T dwMaximumSize) {
185 if (dwInitialSize > 4096)
186 dwInitialSize = 4096;
187 return patch_HeapCreate()(flOptions, dwInitialSize, dwMaximumSize);
190 static BOOL WINAPI Perftools_HeapDestroy(HANDLE hHeap) {
191 return patch_HeapDestroy()(hHeap);
194 static LPVOID WINAPI Perftools_HeapAlloc(HANDLE hHeap, DWORD dwFlags,
195 DWORD_PTR dwBytes) {
196 LPVOID rv = patch_HeapAlloc()(hHeap, dwFlags, dwBytes);
197 MemoryHook::hook()->OnTrack(hHeap, reinterpret_cast<int32>(rv), dwBytes);
198 return rv;
201 static BOOL WINAPI Perftools_HeapFree(HANDLE hHeap, DWORD dwFlags,
202 LPVOID lpMem) {
203 size_t size = 0;
204 if (lpMem != 0) {
205 size = HeapSize(hHeap, 0, lpMem); // Will crash if lpMem is 0.
206 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
208 MemoryHook::hook()->OnUntrack(hHeap, reinterpret_cast<int32>(lpMem), size);
209 return patch_HeapFree()(hHeap, dwFlags, lpMem);
212 static LPVOID WINAPI Perftools_HeapReAlloc(HANDLE hHeap, DWORD dwFlags,
213 LPVOID lpMem, SIZE_T dwBytes) {
214 // Don't call realloc, but instead do a free/malloc. The problem is that
215 // the builtin realloc may either expand a buffer, or it may simply
216 // just call free/malloc. If so, we will already have tracked the new
217 // block via Perftools_HeapAlloc.
219 LPVOID rv = Perftools_HeapAlloc(hHeap, dwFlags, dwBytes);
220 DCHECK_EQ((HEAP_REALLOC_IN_PLACE_ONLY & dwFlags), 0u);
222 // If there was an old buffer, now copy the data to the new buffer.
223 if (lpMem != 0) {
224 size_t size = HeapSize(hHeap, 0, lpMem);
225 if (size > dwBytes)
226 size = dwBytes;
227 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
228 memcpy(rv, lpMem, size);
229 Perftools_HeapFree(hHeap, dwFlags, lpMem);
231 return rv;
234 static LPVOID WINAPI Perftools_VirtualAllocEx(HANDLE process, LPVOID address,
235 SIZE_T size, DWORD type,
236 DWORD protect) {
237 bool already_committed = false;
238 if (address != NULL) {
239 MEMORY_BASIC_INFORMATION info;
240 CHECK(VirtualQuery(address, &info, sizeof(info)));
241 if (info.State & MEM_COMMIT) {
242 already_committed = true;
243 CHECK(size >= info.RegionSize);
246 bool reserving = (address == NULL) || (type & MEM_RESERVE);
247 bool committing = !already_committed && (type & MEM_COMMIT);
250 LPVOID result = patch_VirtualAllocEx()(process, address, size, type,
251 protect);
252 MEMORY_BASIC_INFORMATION info;
253 CHECK(VirtualQuery(result, &info, sizeof(info)));
254 size = info.RegionSize;
256 if (committing)
257 MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result), size);
259 return result;
262 static BOOL WINAPI Perftools_VirtualFreeEx(HANDLE process, LPVOID address,
263 SIZE_T size, DWORD type) {
264 int chunk_size = size;
265 MEMORY_BASIC_INFORMATION info;
266 CHECK(VirtualQuery(address, &info, sizeof(info)));
267 if (chunk_size == 0)
268 chunk_size = info.RegionSize;
269 bool decommit = (info.State & MEM_COMMIT) != 0;
271 if (decommit)
272 MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(address),
273 chunk_size);
275 return patch_VirtualFreeEx()(process, address, size, type);
278 static base::Lock known_maps_lock;
279 static std::map<void*, int> known_maps;
281 static LPVOID WINAPI Perftools_MapViewOfFileEx(HANDLE hFileMappingObject,
282 DWORD dwDesiredAccess,
283 DWORD dwFileOffsetHigh,
284 DWORD dwFileOffsetLow,
285 SIZE_T dwNumberOfBytesToMap,
286 LPVOID lpBaseAddress) {
287 // For this function pair, you always deallocate the full block of
288 // data that you allocate, so NewHook/DeleteHook is the right API.
289 LPVOID result = patch_MapViewOfFileEx()(hFileMappingObject, dwDesiredAccess,
290 dwFileOffsetHigh, dwFileOffsetLow,
291 dwNumberOfBytesToMap, lpBaseAddress);
293 base::AutoLock lock(known_maps_lock);
294 MEMORY_BASIC_INFORMATION info;
295 if (known_maps.find(result) == known_maps.end()) {
296 CHECK(VirtualQuery(result, &info, sizeof(info)));
297 // TODO(mbelshe): THIS map uses the standard heap!!!!
298 known_maps[result] = 1;
299 MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result),
300 info.RegionSize);
301 } else {
302 known_maps[result] = known_maps[result] + 1;
305 return result;
308 static LPVOID WINAPI Perftools_MapViewOfFile(HANDLE hFileMappingObject,
309 DWORD dwDesiredAccess,
310 DWORD dwFileOffsetHigh,
311 DWORD dwFileOffsetLow,
312 SIZE_T dwNumberOfBytesToMap) {
313 return Perftools_MapViewOfFileEx(hFileMappingObject, dwDesiredAccess,
314 dwFileOffsetHigh, dwFileOffsetLow,
315 dwNumberOfBytesToMap, 0);
318 static BOOL WINAPI Perftools_UnmapViewOfFile(LPVOID lpBaseAddress) {
319 // This will call into NtUnmapViewOfSection().
320 return patch_UnmapViewOfFile()(lpBaseAddress);
323 static DWORD WINAPI Perftools_NtUnmapViewOfSection(HANDLE process,
324 LPVOID lpBaseAddress) {
325 // Some windows APIs call directly into this routine rather
326 // than calling UnmapViewOfFile. If we didn't trap this function,
327 // then we appear to have bogus leaks.
329 base::AutoLock lock(known_maps_lock);
330 MEMORY_BASIC_INFORMATION info;
331 CHECK(VirtualQuery(lpBaseAddress, &info, sizeof(info)));
332 if (known_maps.find(lpBaseAddress) != known_maps.end()) {
333 if (known_maps[lpBaseAddress] == 1) {
334 MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(lpBaseAddress),
335 info.RegionSize);
336 known_maps.erase(lpBaseAddress);
337 } else {
338 known_maps[lpBaseAddress] = known_maps[lpBaseAddress] - 1;
342 return patch_NtUnmapViewOfSection()(process, lpBaseAddress);
345 static HGLOBAL WINAPI Perftools_GlobalAlloc(UINT uFlags, SIZE_T dwBytes) {
346 // GlobalAlloc is built atop HeapAlloc anyway. So we don't track these.
347 // GlobalAlloc will internally call into HeapAlloc and we track there.
349 // Force all memory to be fixed.
350 uFlags &= ~GMEM_MOVEABLE;
351 HGLOBAL rv = patch_GlobalAlloc()(uFlags, dwBytes);
352 return rv;
355 static HGLOBAL WINAPI Perftools_GlobalFree(HGLOBAL hMem) {
356 return patch_GlobalFree()(hMem);
359 static HGLOBAL WINAPI Perftools_GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes,
360 UINT uFlags) {
361 // TODO(jar): [The following looks like a copy/paste typo from LocalRealloc.]
362 // GlobalDiscard is a macro which calls LocalReAlloc with size 0.
363 if (dwBytes == 0) {
364 return patch_GlobalReAlloc()(hMem, dwBytes, uFlags);
367 HGLOBAL rv = Perftools_GlobalAlloc(uFlags, dwBytes);
368 if (hMem != 0) {
369 size_t size = GlobalSize(hMem);
370 if (size > dwBytes)
371 size = dwBytes;
372 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
373 memcpy(rv, hMem, size);
374 Perftools_GlobalFree(hMem);
377 return rv;
380 static HLOCAL WINAPI Perftools_LocalAlloc(UINT uFlags, SIZE_T dwBytes) {
381 // LocalAlloc is built atop HeapAlloc anyway. So we don't track these.
382 // LocalAlloc will internally call into HeapAlloc and we track there.
384 // Force all memory to be fixed.
385 uFlags &= ~LMEM_MOVEABLE;
386 HLOCAL rv = patch_LocalAlloc()(uFlags, dwBytes);
387 return rv;
390 static HLOCAL WINAPI Perftools_LocalFree(HLOCAL hMem) {
391 return patch_LocalFree()(hMem);
394 static HLOCAL WINAPI Perftools_LocalReAlloc(HLOCAL hMem, SIZE_T dwBytes,
395 UINT uFlags) {
396 // LocalDiscard is a macro which calls LocalReAlloc with size 0.
397 if (dwBytes == 0) {
398 return patch_LocalReAlloc()(hMem, dwBytes, uFlags);
401 HGLOBAL rv = Perftools_LocalAlloc(uFlags, dwBytes);
402 if (hMem != 0) {
403 size_t size = LocalSize(hMem);
404 if (size > dwBytes)
405 size = dwBytes;
406 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
407 memcpy(rv, hMem, size);
408 Perftools_LocalFree(hMem);
411 return rv;
414 bool MemoryHook::hooked_ = false;
415 MemoryHook* MemoryHook::global_hook_ = NULL;
417 MemoryHook::MemoryHook()
418 : watcher_(NULL),
419 heap_(NULL) {
420 CreateHeap();
423 MemoryHook::~MemoryHook() {
424 // It's a bit dangerous to ever close this heap; MemoryWatchers may have
425 // used this heap for their tracking data. Closing the heap while any
426 // MemoryWatchers still exist is pretty dangerous.
427 CloseHeap();
430 bool MemoryHook::Initialize() {
431 if (global_hook_ == NULL)
432 global_hook_ = new MemoryHook();
433 return true;
436 bool MemoryHook::Hook() {
437 DCHECK(!hooked_);
438 if (!hooked_) {
439 DCHECK(global_hook_);
441 // Luckily, Patch() doesn't call malloc or windows alloc routines
442 // itself -- though it does call new (we can use PatchWithStub to
443 // get around that, and will need to if we need to patch new).
445 HMODULE hkernel32 = ::GetModuleHandle(L"kernel32");
446 CHECK(hkernel32 != NULL);
448 HMODULE hntdll = ::GetModuleHandle(L"ntdll");
449 CHECK(hntdll != NULL);
451 // Now that we've found all the functions, patch them
452 INSTALL_PATCH(HeapCreate);
453 INSTALL_PATCH(HeapDestroy);
454 INSTALL_PATCH(HeapAlloc);
455 INSTALL_PATCH(HeapReAlloc);
456 INSTALL_PATCH(HeapFree);
457 INSTALL_PATCH(VirtualAllocEx);
458 INSTALL_PATCH(VirtualFreeEx);
459 INSTALL_PATCH(MapViewOfFileEx);
460 INSTALL_PATCH(MapViewOfFile);
461 INSTALL_PATCH(UnmapViewOfFile);
462 INSTALL_NTDLLPATCH(NtUnmapViewOfSection);
463 INSTALL_PATCH(GlobalAlloc);
464 INSTALL_PATCH(GlobalReAlloc);
465 INSTALL_PATCH(GlobalFree);
466 INSTALL_PATCH(LocalAlloc);
467 INSTALL_PATCH(LocalReAlloc);
468 INSTALL_PATCH(LocalFree);
470 // We are finally completely hooked.
471 hooked_ = true;
473 return true;
476 bool MemoryHook::Unhook() {
477 if (hooked_) {
478 // We need to go back to the system malloc/etc at global destruct time,
479 // so objects that were constructed before tcmalloc, using the system
480 // malloc, can destroy themselves using the system free. This depends
481 // on DLLs unloading in the reverse order in which they load!
483 // We also go back to the default HeapAlloc/etc, just for consistency.
484 // Who knows, it may help avoid weird bugs in some situations.
485 UNINSTALL_PATCH(HeapCreate);
486 UNINSTALL_PATCH(HeapDestroy);
487 UNINSTALL_PATCH(HeapAlloc);
488 UNINSTALL_PATCH(HeapReAlloc);
489 UNINSTALL_PATCH(HeapFree);
490 UNINSTALL_PATCH(VirtualAllocEx);
491 UNINSTALL_PATCH(VirtualFreeEx);
492 UNINSTALL_PATCH(MapViewOfFile);
493 UNINSTALL_PATCH(MapViewOfFileEx);
494 UNINSTALL_PATCH(UnmapViewOfFile);
495 UNINSTALL_PATCH(NtUnmapViewOfSection);
496 UNINSTALL_PATCH(GlobalAlloc);
497 UNINSTALL_PATCH(GlobalReAlloc);
498 UNINSTALL_PATCH(GlobalFree);
499 UNINSTALL_PATCH(LocalAlloc);
500 UNINSTALL_PATCH(LocalReAlloc);
501 UNINSTALL_PATCH(LocalFree);
503 hooked_ = false;
505 return true;
508 bool MemoryHook::RegisterWatcher(MemoryObserver* watcher) {
509 DCHECK(global_hook_->watcher_ == NULL);
511 if (!hooked_)
512 Hook();
514 DCHECK(global_hook_);
515 global_hook_->watcher_ = watcher;
516 return true;
519 bool MemoryHook::UnregisterWatcher(MemoryObserver* watcher) {
520 DCHECK(hooked_);
521 DCHECK(global_hook_->watcher_ == watcher);
522 // TODO(jar): changing watcher_ here is very racy. Other threads may (without
523 // a lock) testing, and then calling through this value. We probably can't
524 // remove this until we are single threaded.
525 global_hook_->watcher_ = NULL;
527 // For now, since there are no more watchers, unhook memory.
528 return Unhook();
531 bool MemoryHook::CreateHeap() {
532 // Create a heap for our own memory.
533 DCHECK(heap_ == NULL);
534 heap_ = HeapCreate(0, 0, 0);
535 DCHECK(heap_ != NULL);
536 return heap_ != NULL;
539 bool MemoryHook::CloseHeap() {
540 DCHECK(heap_ != NULL);
541 HeapDestroy(heap_);
542 heap_ = NULL;
543 return true;
546 void MemoryHook::OnTrack(HANDLE heap, int32 id, int32 size) {
547 // Don't notify about allocations to our internal heap.
548 if (heap == heap_)
549 return;
551 if (watcher_)
552 watcher_->OnTrack(heap, id, size);
555 void MemoryHook::OnUntrack(HANDLE heap, int32 id, int32 size) {
556 // Don't notify about allocations to our internal heap.
557 if (heap == heap_)
558 return;
560 if (watcher_)
561 watcher_->OnUntrack(heap, id, size);