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.
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
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); \
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); \
40 // Macro to uninstall Patch functions.
41 #define UNINSTALL_PATCH(name) patch_##name.Uninstall();
45 // Windows APIs to be hooked
48 typedef HANDLE (WINAPI
*Type_HeapCreate
)(DWORD flOptions
,
50 SIZE_T dwMaximumSize
);
51 typedef BOOL (WINAPI
*Type_HeapDestroy
)(HANDLE hHeap
);
52 typedef LPVOID (WINAPI
*Type_HeapAlloc
)(HANDLE hHeap
, DWORD dwFlags
,
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
,
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
,
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
,
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
,
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.
102 // Constructor. Does not hook the function yet.
104 : original_function_(NULL
),
105 patch_function_(NULL
),
106 stub_function_(NULL
) {
109 // Destructor. Unhooks the function if it has been hooked.
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.
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.
146 DCHECK(stub_function_
);
147 return stub_function_
;
151 // The function that we plan to patch.
152 T original_function_
;
153 // The function to replace the original with.
155 // To unpatch, we also need to keep around a "stub" that points to the
156 // pre-patched Windows 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
,
196 LPVOID rv
= patch_HeapAlloc()(hHeap
, dwFlags
, dwBytes
);
197 MemoryHook::hook()->OnTrack(hHeap
, reinterpret_cast<int32
>(rv
), dwBytes
);
201 static BOOL WINAPI
Perftools_HeapFree(HANDLE hHeap
, DWORD dwFlags
,
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.
224 size_t size
= HeapSize(hHeap
, 0, lpMem
);
227 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
228 memcpy(rv
, lpMem
, size
);
229 Perftools_HeapFree(hHeap
, dwFlags
, lpMem
);
234 static LPVOID WINAPI
Perftools_VirtualAllocEx(HANDLE process
, LPVOID address
,
235 SIZE_T size
, DWORD type
,
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
,
252 MEMORY_BASIC_INFORMATION info
;
253 CHECK(VirtualQuery(result
, &info
, sizeof(info
)));
254 size
= info
.RegionSize
;
257 MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32
>(result
), size
);
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
)));
268 chunk_size
= info
.RegionSize
;
269 bool decommit
= (info
.State
& MEM_COMMIT
) != 0;
272 MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32
>(address
),
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
),
302 known_maps
[result
] = known_maps
[result
] + 1;
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
),
336 known_maps
.erase(lpBaseAddress
);
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
);
355 static HGLOBAL WINAPI
Perftools_GlobalFree(HGLOBAL hMem
) {
356 return patch_GlobalFree()(hMem
);
359 static HGLOBAL WINAPI
Perftools_GlobalReAlloc(HGLOBAL hMem
, SIZE_T dwBytes
,
361 // TODO(jar): [The following looks like a copy/paste typo from LocalRealloc.]
362 // GlobalDiscard is a macro which calls LocalReAlloc with size 0.
364 return patch_GlobalReAlloc()(hMem
, dwBytes
, uFlags
);
367 HGLOBAL rv
= Perftools_GlobalAlloc(uFlags
, dwBytes
);
369 size_t size
= GlobalSize(hMem
);
372 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
373 memcpy(rv
, hMem
, size
);
374 Perftools_GlobalFree(hMem
);
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
);
390 static HLOCAL WINAPI
Perftools_LocalFree(HLOCAL hMem
) {
391 return patch_LocalFree()(hMem
);
394 static HLOCAL WINAPI
Perftools_LocalReAlloc(HLOCAL hMem
, SIZE_T dwBytes
,
396 // LocalDiscard is a macro which calls LocalReAlloc with size 0.
398 return patch_LocalReAlloc()(hMem
, dwBytes
, uFlags
);
401 HGLOBAL rv
= Perftools_LocalAlloc(uFlags
, dwBytes
);
403 size_t size
= LocalSize(hMem
);
406 // Note: size could be 0; HeapAlloc does allocate 0 length buffers.
407 memcpy(rv
, hMem
, size
);
408 Perftools_LocalFree(hMem
);
414 bool MemoryHook::hooked_
= false;
415 MemoryHook
* MemoryHook::global_hook_
= NULL
;
417 MemoryHook::MemoryHook()
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.
430 bool MemoryHook::Initialize() {
431 if (global_hook_
== NULL
)
432 global_hook_
= new MemoryHook();
436 bool MemoryHook::Hook() {
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.
476 bool MemoryHook::Unhook() {
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
);
508 bool MemoryHook::RegisterWatcher(MemoryObserver
* watcher
) {
509 DCHECK(global_hook_
->watcher_
== NULL
);
514 DCHECK(global_hook_
);
515 global_hook_
->watcher_
= watcher
;
519 bool MemoryHook::UnregisterWatcher(MemoryObserver
* watcher
) {
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.
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
);
546 void MemoryHook::OnTrack(HANDLE heap
, int32 id
, int32 size
) {
547 // Don't notify about allocations to our internal heap.
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.
561 watcher_
->OnUntrack(heap
, id
, size
);