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 } 563