- add sources.
[platform/framework/web/crosswalk.git] / src / tools / memory_watcher / memory_hook.cc
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.
4
5 // Static class for hooking Win32 API routines.
6
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.
14
15 #include <windows.h>
16
17 #include "memory_hook.h"
18 #include "memory_watcher.h"
19 #include "preamble_patcher.h"
20
21 // Calls GetProcAddress, but casts to the correct type.
22 #define GET_PROC_ADDRESS(hmodule, name) \
23   ( (Type_##name)(::GetProcAddress(hmodule, #name)) )
24
25 // Macro to declare Patch functions.
26 #define DECLARE_PATCH(name) Patch<Type_##name> patch_##name
27
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)
33
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)
39
40 // Macro to uninstall Patch functions.
41 #define UNINSTALL_PATCH(name) patch_##name.Uninstall();
42
43
44
45 // Windows APIs to be hooked
46
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);
58
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);
64
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);
70
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);
77
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);
91
92 typedef DWORD (WINAPI *Type_NtUnmapViewOfSection)(HANDLE process,
93                                                   LPVOID lpBaseAddress);
94
95
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) {
107   }
108
109   // Destructor.  Unhooks the function if it has been hooked.
110   ~Patch<T>() {
111     Uninstall();
112   }
113
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_));
124   }
125
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;
132   }
133
134   // Set the function to be patched.
135   void set_original(T original) { original_function_ = original; }
136
137   // Get the original function being patched.
138   T original() { return original_function_; }
139
140   // Get the patched function.  (e.g. the replacement function)
141   T patched() { return patch_function_; }
142
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_;
148   }
149
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_;
158 };
159
160
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);
179
180 // Our replacement functions.
181
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);
188 }
189
190 static BOOL WINAPI Perftools_HeapDestroy(HANDLE hHeap) {
191   return patch_HeapDestroy()(hHeap);
192 }
193
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;
199 }
200
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.
207   }
208   MemoryHook::hook()->OnUntrack(hHeap, reinterpret_cast<int32>(lpMem), size);
209   return patch_HeapFree()(hHeap, dwFlags, lpMem);
210 }
211
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.
218
219   LPVOID rv = Perftools_HeapAlloc(hHeap, dwFlags, dwBytes);
220   DCHECK_EQ((HEAP_REALLOC_IN_PLACE_ONLY & dwFlags), 0u);
221
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);
230   }
231   return rv;
232 }
233
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);
244     }
245   }
246   bool reserving = (address == NULL) || (type & MEM_RESERVE);
247   bool committing = !already_committed && (type & MEM_COMMIT);
248
249
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;
255
256   if (committing)
257     MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result), size);
258
259   return result;
260 }
261
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;
270
271   if (decommit)
272       MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(address),
273                                      chunk_size);
274
275   return patch_VirtualFreeEx()(process, address, size, type);
276 }
277
278 static base::Lock known_maps_lock;
279 static std::map<void*, int> known_maps;
280
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);
292   {
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;
303     }
304   }
305   return result;
306 }
307
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);
316 }
317
318 static BOOL WINAPI Perftools_UnmapViewOfFile(LPVOID lpBaseAddress) {
319   // This will call into NtUnmapViewOfSection().
320   return patch_UnmapViewOfFile()(lpBaseAddress);
321 }
322
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.
328   {
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;
339       }
340     }
341   }
342   return patch_NtUnmapViewOfSection()(process, lpBaseAddress);
343 }
344
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.
348
349   // Force all memory to be fixed.
350   uFlags &= ~GMEM_MOVEABLE;
351   HGLOBAL rv = patch_GlobalAlloc()(uFlags, dwBytes);
352   return rv;
353 }
354
355 static HGLOBAL WINAPI Perftools_GlobalFree(HGLOBAL hMem) {
356   return patch_GlobalFree()(hMem);
357 }
358
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);
365   }
366
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);
375   }
376
377   return rv;
378 }
379
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.
383
384   // Force all memory to be fixed.
385   uFlags &= ~LMEM_MOVEABLE;
386   HLOCAL rv = patch_LocalAlloc()(uFlags, dwBytes);
387   return rv;
388 }
389
390 static HLOCAL WINAPI Perftools_LocalFree(HLOCAL hMem) {
391   return patch_LocalFree()(hMem);
392 }
393
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);
399   }
400
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);
409   }
410
411   return rv;
412 }
413
414 bool MemoryHook::hooked_ = false;
415 MemoryHook* MemoryHook::global_hook_ = NULL;
416
417 MemoryHook::MemoryHook()
418   : watcher_(NULL),
419     heap_(NULL) {
420   CreateHeap();
421 }
422
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();
428 }
429
430 bool MemoryHook::Initialize() {
431   if (global_hook_ == NULL)
432     global_hook_ = new MemoryHook();
433   return true;
434 }
435
436 bool MemoryHook::Hook() {
437   DCHECK(!hooked_);
438   if (!hooked_) {
439     DCHECK(global_hook_);
440
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).
444
445     HMODULE hkernel32 = ::GetModuleHandle(L"kernel32");
446     CHECK(hkernel32 != NULL);
447
448     HMODULE hntdll = ::GetModuleHandle(L"ntdll");
449     CHECK(hntdll != NULL);
450
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);
469
470     // We are finally completely hooked.
471     hooked_ = true;
472   }
473   return true;
474 }
475
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!
482     //
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);
502
503     hooked_ = false;
504   }
505   return true;
506 }
507
508 bool MemoryHook::RegisterWatcher(MemoryObserver* watcher) {
509   DCHECK(global_hook_->watcher_ == NULL);
510
511   if (!hooked_)
512     Hook();
513
514   DCHECK(global_hook_);
515   global_hook_->watcher_ = watcher;
516   return true;
517 }
518
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;
526
527   // For now, since there are no more watchers, unhook memory.
528   return Unhook();
529 }
530
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;
537 }
538
539 bool MemoryHook::CloseHeap() {
540   DCHECK(heap_ != NULL);
541   HeapDestroy(heap_);
542   heap_ = NULL;
543   return true;
544 }
545
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;
550
551   if (watcher_)
552     watcher_->OnTrack(heap, id, size);
553 }
554
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;
559
560   if (watcher_)
561     watcher_->OnUntrack(heap, id, size);
562 }