diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-27 00:12:16 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-27 00:12:16 +0000 |
commit | 920c091ac3ee15079194c82ae8a7a18215f3f23c (patch) | |
tree | d28515d1e7732e2b6d077df1b4855ace3f4ac84f /tools/memory_watcher/memory_hook.cc | |
parent | ae2c20f398933a9e86c387dcc465ec0f71065ffc (diff) | |
download | chromium_src-920c091ac3ee15079194c82ae8a7a18215f3f23c.zip chromium_src-920c091ac3ee15079194c82ae8a7a18215f3f23c.tar.gz chromium_src-920c091ac3ee15079194c82ae8a7a18215f3f23c.tar.bz2 |
Add tools to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/memory_watcher/memory_hook.cc')
-rw-r--r-- | tools/memory_watcher/memory_hook.cc | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/tools/memory_watcher/memory_hook.cc b/tools/memory_watcher/memory_hook.cc new file mode 100644 index 0000000..2997fc0 --- /dev/null +++ b/tools/memory_watcher/memory_hook.cc @@ -0,0 +1,580 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Static class for hooking Win32 API routines. + +// Some notes about how to hook Memory Allocation Routines in Windows. +// +// For our purposes we do not hook the libc routines. There are two +// reasons for this. First, the libc routines all go through HeapAlloc +// anyway. So, it's redundant to log both HeapAlloc and malloc. +// Second, it can be tricky to hook in both static and dynamic linkages +// of libc. + +#include <windows.h> + +#include "memory_hook.h" +#include "memory_watcher.h" +#include "preamble_patcher.h" + +// Calls GetProcAddress, but casts to the correct type. +#define GET_PROC_ADDRESS(hmodule, name) \ + ( (Type_##name)(::GetProcAddress(hmodule, #name)) ) + +// Macro to declare Patch functions. +#define DECLARE_PATCH(name) Patch<Type_##name> patch_##name + +// Macro to install Patch functions. +#define INSTALL_PATCH(name) do { \ + patch_##name.set_original(GET_PROC_ADDRESS(hkernel32, ##name)); \ + patch_##name.Install(&Perftools_##name); \ +} while (0) + +// Macro to install Patch functions. +#define INSTALL_NTDLLPATCH(name) do { \ + patch_##name.set_original(GET_PROC_ADDRESS(hntdll, ##name)); \ + patch_##name.Install(&Perftools_##name); \ +} while (0) + +// Macro to uninstall Patch functions. +#define UNINSTALL_PATCH(name) patch_##name.Uninstall(); + + + +// Windows APIs to be hooked + +// HeapAlloc routines +typedef HANDLE (WINAPI *Type_HeapCreate)(DWORD flOptions, + SIZE_T dwInitialSize, + SIZE_T dwMaximumSize); +typedef BOOL (WINAPI *Type_HeapDestroy)(HANDLE hHeap); +typedef LPVOID (WINAPI *Type_HeapAlloc)(HANDLE hHeap, DWORD dwFlags, + DWORD_PTR dwBytes); +typedef LPVOID (WINAPI *Type_HeapReAlloc)(HANDLE hHeap, DWORD dwFlags, + LPVOID lpMem, SIZE_T dwBytes); +typedef BOOL (WINAPI *Type_HeapFree)(HANDLE hHeap, DWORD dwFlags, + LPVOID lpMem); + +// GlobalAlloc routines +typedef HGLOBAL (WINAPI *Type_GlobalAlloc)(UINT uFlags, SIZE_T dwBytes); +typedef HGLOBAL (WINAPI *Type_GlobalReAlloc)(HGLOBAL hMem, SIZE_T dwBytes, + UINT uFlags); +typedef HGLOBAL (WINAPI *Type_GlobalFree)(HGLOBAL hMem); + +// LocalAlloc routines +typedef HLOCAL (WINAPI *Type_LocalAlloc)(UINT uFlags, SIZE_T uBytes); +typedef HLOCAL (WINAPI *Type_LocalReAlloc)(HLOCAL hMem, SIZE_T uBytes, + UINT uFlags); +typedef HLOCAL (WINAPI *Type_LocalFree)(HLOCAL hMem); + +// A Windows-API equivalent of mmap and munmap, for "anonymous regions" +typedef LPVOID (WINAPI *Type_VirtualAllocEx)(HANDLE process, LPVOID address, + SIZE_T size, DWORD type, + DWORD protect); +typedef BOOL (WINAPI *Type_VirtualFreeEx)(HANDLE process, LPVOID address, + SIZE_T size, DWORD type); + +// A Windows-API equivalent of mmap and munmap, for actual files +typedef LPVOID (WINAPI *Type_MapViewOfFile)(HANDLE hFileMappingObject, + DWORD dwDesiredAccess, + DWORD dwFileOffsetHigh, + DWORD dwFileOffsetLow, + SIZE_T dwNumberOfBytesToMap); +typedef LPVOID (WINAPI *Type_MapViewOfFileEx)(HANDLE hFileMappingObject, + DWORD dwDesiredAccess, + DWORD dwFileOffsetHigh, + DWORD dwFileOffsetLow, + SIZE_T dwNumberOfBytesToMap, + LPVOID lpBaseAddress); +typedef BOOL (WINAPI *Type_UnmapViewOfFile)(LPVOID lpBaseAddress); + +typedef DWORD (WINAPI *Type_NtUnmapViewOfSection)(HANDLE process, + LPVOID lpBaseAddress); + + +// Patch is a template for keeping the pointer to the original +// hooked routine, the function to call when hooked, and the +// stub routine which is patched. +template<class T> +class Patch { + public: + // Constructor. Does not hook the function yet. + Patch<T>() + : original_function_(NULL), + patch_function_(NULL), + stub_function_(NULL) { + } + + // Destructor. Unhooks the function if it has been hooked. + ~Patch<T>() { + Uninstall(); + } + + // Patches original function with func. + // Must have called set_original to set the original function. + void Install(T func) { + patch_function_ = func; + CHECK(patch_function_ != NULL); + CHECK(original_function_ != NULL); + CHECK(stub_function_ == NULL); + CHECK(sidestep::SIDESTEP_SUCCESS == + sidestep::PreamblePatcher::Patch(original_function_, + patch_function_, &stub_function_)); + } + + // Un-patches the function. + void Uninstall() { + if (stub_function_) + sidestep::PreamblePatcher::Unpatch(original_function_, + patch_function_, stub_function_); + stub_function_ = NULL; + } + + // Set the function to be patched. + void set_original(T original) { original_function_ = original; } + + // Get the original function being patched. + T original() { return original_function_; } + + // Get the patched function. (e.g. the replacement function) + T patched() { return patch_function_; } + + // Access to the stub for calling the original function + // while it is patched. + T operator()() { + DCHECK(stub_function_); + return stub_function_; + } + + private: + // The function that we plan to patch. + T original_function_; + // The function to replace the original with. + T patch_function_; + // To unpatch, we also need to keep around a "stub" that points to the + // pre-patched Windows function. + T stub_function_; +}; + + +// All Windows memory-allocation routines call through to one of these. +DECLARE_PATCH(HeapCreate); +DECLARE_PATCH(HeapDestroy); +DECLARE_PATCH(HeapAlloc); +DECLARE_PATCH(HeapReAlloc); +DECLARE_PATCH(HeapFree); +DECLARE_PATCH(VirtualAllocEx); +DECLARE_PATCH(VirtualFreeEx); +DECLARE_PATCH(MapViewOfFile); +DECLARE_PATCH(MapViewOfFileEx); +DECLARE_PATCH(UnmapViewOfFile); +DECLARE_PATCH(GlobalAlloc); +DECLARE_PATCH(GlobalReAlloc); +DECLARE_PATCH(GlobalFree); +DECLARE_PATCH(LocalAlloc); +DECLARE_PATCH(LocalReAlloc); +DECLARE_PATCH(LocalFree); +DECLARE_PATCH(NtUnmapViewOfSection); + +// Our replacement functions. + +static HANDLE WINAPI Perftools_HeapCreate(DWORD flOptions, + SIZE_T dwInitialSize, + SIZE_T dwMaximumSize) { + if (dwInitialSize > 4096) + dwInitialSize = 4096; + return patch_HeapCreate()(flOptions, dwInitialSize, dwMaximumSize); +} + +static BOOL WINAPI Perftools_HeapDestroy(HANDLE hHeap) { + return patch_HeapDestroy()(hHeap); +} + +static LPVOID WINAPI Perftools_HeapAlloc(HANDLE hHeap, DWORD dwFlags, + DWORD_PTR dwBytes) { + LPVOID rv = patch_HeapAlloc()(hHeap, dwFlags, dwBytes); + MemoryHook::hook()->OnTrack(hHeap, reinterpret_cast<int32>(rv), dwBytes); + return rv; +} + +static BOOL WINAPI Perftools_HeapFree(HANDLE hHeap, DWORD dwFlags, + LPVOID lpMem) { + size_t size = 0; + if (lpMem != 0) { + size = HeapSize(hHeap, 0, lpMem); // Will crash if lpMem is 0. + // Note: size could be 0; HeapAlloc does allocate 0 length buffers. + } + MemoryHook::hook()->OnUntrack(hHeap, reinterpret_cast<int32>(lpMem), size); + return patch_HeapFree()(hHeap, dwFlags, lpMem); +} + +static LPVOID WINAPI Perftools_HeapReAlloc(HANDLE hHeap, DWORD dwFlags, + LPVOID lpMem, SIZE_T dwBytes) { + // Don't call realloc, but instead do a free/malloc. The problem is that + // the builtin realloc may either expand a buffer, or it may simply + // just call free/malloc. If so, we will already have tracked the new + // block via Perftools_HeapAlloc. + + LPVOID rv = Perftools_HeapAlloc(hHeap, dwFlags, dwBytes); + + // If there was an old buffer, now copy the data to the new buffer. + if (lpMem != 0) { + size_t size = HeapSize(hHeap, 0, lpMem); + if (size > dwBytes) + size = dwBytes; + // Note: size could be 0; HeapAlloc does allocate 0 length buffers. + memcpy(rv, lpMem, size); + Perftools_HeapFree(hHeap, dwFlags, lpMem); + } + return rv; +} + +static LPVOID WINAPI Perftools_VirtualAllocEx(HANDLE process, LPVOID address, + SIZE_T size, DWORD type, + DWORD protect) { + bool already_committed = false; + if (address != NULL) { + MEMORY_BASIC_INFORMATION info; + CHECK(VirtualQuery(address, &info, sizeof(info))); + if (info.State & MEM_COMMIT) + already_committed = true; + } + bool reserving = (address == NULL) || (type & MEM_RESERVE); + bool committing = !already_committed && (type & MEM_COMMIT); + + + LPVOID result = patch_VirtualAllocEx()(process, address, size, type, + protect); + MEMORY_BASIC_INFORMATION info; + CHECK(VirtualQuery(result, &info, sizeof(info))); + size = info.RegionSize; + + if (committing) + MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result), size); + + return result; +} + +static BOOL WINAPI Perftools_VirtualFreeEx(HANDLE process, LPVOID address, + SIZE_T size, DWORD type) { + int chunk_size = size; + MEMORY_BASIC_INFORMATION info; + CHECK(VirtualQuery(address, &info, sizeof(info))); + if (chunk_size == 0) + chunk_size = info.RegionSize; + bool decommit = (info.State & MEM_COMMIT); + + if (decommit) + MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(address), + chunk_size); + + return patch_VirtualFreeEx()(process, address, size, type); +} + +static Lock known_maps_lock; +static std::map<void*, int> known_maps; + +static LPVOID WINAPI Perftools_MapViewOfFileEx(HANDLE hFileMappingObject, + DWORD dwDesiredAccess, + DWORD dwFileOffsetHigh, + DWORD dwFileOffsetLow, + SIZE_T dwNumberOfBytesToMap, + LPVOID lpBaseAddress) { + // For this function pair, you always deallocate the full block of + // data that you allocate, so NewHook/DeleteHook is the right API. + LPVOID result = patch_MapViewOfFileEx()(hFileMappingObject, dwDesiredAccess, + dwFileOffsetHigh, dwFileOffsetLow, + dwNumberOfBytesToMap, lpBaseAddress); + { + AutoLock lock(known_maps_lock); + MEMORY_BASIC_INFORMATION info; + if (known_maps.find(result) == known_maps.end()) { + CHECK(VirtualQuery(result, &info, sizeof(info))); + // TODO(mbelshe): THIS map uses the standard heap!!!! + known_maps[result] = 1; + MemoryHook::hook()->OnTrack(0, reinterpret_cast<int32>(result), + info.RegionSize); + } else { + known_maps[result] = known_maps[result] + 1; + } + } + return result; +} + +static LPVOID WINAPI Perftools_MapViewOfFile(HANDLE hFileMappingObject, + DWORD dwDesiredAccess, + DWORD dwFileOffsetHigh, + DWORD dwFileOffsetLow, + SIZE_T dwNumberOfBytesToMap) { + return Perftools_MapViewOfFileEx(hFileMappingObject, dwDesiredAccess, + dwFileOffsetHigh, dwFileOffsetLow, + dwNumberOfBytesToMap, 0); +} + +static BOOL WINAPI Perftools_UnmapViewOfFile(LPVOID lpBaseAddress) { + // This will call into NtUnmapViewOfSection(). + return patch_UnmapViewOfFile()(lpBaseAddress); +} + +static DWORD WINAPI Perftools_NtUnmapViewOfSection(HANDLE process, + LPVOID lpBaseAddress) { + // Some windows APIs call directly into this routine rather + // than calling UnmapViewOfFile. If we didn't trap this function, + // then we appear to have bogus leaks. + { + AutoLock lock(known_maps_lock); + MEMORY_BASIC_INFORMATION info; + CHECK(VirtualQuery(lpBaseAddress, &info, sizeof(info))); + if (known_maps.find(lpBaseAddress) != known_maps.end()) { + if (known_maps[lpBaseAddress] == 1) { + MemoryHook::hook()->OnUntrack(0, reinterpret_cast<int32>(lpBaseAddress), + info.RegionSize); + known_maps.erase(lpBaseAddress); + } else { + known_maps[lpBaseAddress] = known_maps[lpBaseAddress] - 1; + } + } + } + return patch_NtUnmapViewOfSection()(process, lpBaseAddress); +} + +static HGLOBAL WINAPI Perftools_GlobalAlloc(UINT uFlags, SIZE_T dwBytes) { + // GlobalAlloc is built atop HeapAlloc anyway. So we don't track these. + // GlobalAlloc will internally call into HeapAlloc and we track there. + + // Force all memory to be fixed. + uFlags &= ~GMEM_MOVEABLE; + HGLOBAL rv = patch_GlobalAlloc()(uFlags, dwBytes); + return rv; +} + +static HGLOBAL WINAPI Perftools_GlobalFree(HGLOBAL hMem) { + return patch_GlobalFree()(hMem); +} + +static HGLOBAL WINAPI Perftools_GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes, + UINT uFlags) { + // GlobalDiscard is a macro which calls LocalReAlloc with size 0. + if (dwBytes == 0) { + return patch_GlobalReAlloc()(hMem, dwBytes, uFlags); + } + + HGLOBAL rv = Perftools_GlobalAlloc(uFlags, dwBytes); + if (hMem != 0) { + size_t size = GlobalSize(hMem); + if (size > dwBytes) + size = dwBytes; + // Note: size could be 0; HeapAlloc does allocate 0 length buffers. + memcpy(rv, hMem, size); + Perftools_GlobalFree(hMem); + } + + return rv; +} + +static HLOCAL WINAPI Perftools_LocalAlloc(UINT uFlags, SIZE_T dwBytes) { + // LocalAlloc is built atop HeapAlloc anyway. So we don't track these. + // LocalAlloc will internally call into HeapAlloc and we track there. + + // Force all memory to be fixed. + uFlags &= ~LMEM_MOVEABLE; + HLOCAL rv = patch_LocalAlloc()(uFlags, dwBytes); + return rv; +} + +static HLOCAL WINAPI Perftools_LocalFree(HLOCAL hMem) { + return patch_LocalFree()(hMem); +} + +static HLOCAL WINAPI Perftools_LocalReAlloc(HLOCAL hMem, SIZE_T dwBytes, + UINT uFlags) { + // LocalDiscard is a macro which calls LocalReAlloc with size 0. + if (dwBytes == 0) { + return patch_LocalReAlloc()(hMem, dwBytes, uFlags); + } + + HGLOBAL rv = Perftools_LocalAlloc(uFlags, dwBytes); + if (hMem != 0) { + size_t size = LocalSize(hMem); + if (size > dwBytes) + size = dwBytes; + // Note: size could be 0; HeapAlloc does allocate 0 length buffers. + memcpy(rv, hMem, size); + Perftools_LocalFree(hMem); + } + + return rv; +} + +bool MemoryHook::hooked_ = false; +MemoryHook* MemoryHook::global_hook_ = NULL; + +MemoryHook::MemoryHook() + : watcher_(NULL), + heap_(NULL) { + CreateHeap(); +} + +MemoryHook::~MemoryHook() { + // It's a bit dangerous to ever close this heap; MemoryWatchers may have + // used this heap for their tracking data. Closing the heap while any + // MemoryWatchers still exist is pretty dangerous. + CloseHeap(); +} + +bool MemoryHook::Initialize() { + if (global_hook_ == NULL) + global_hook_ = new MemoryHook(); + return true; +} + +bool MemoryHook::Hook() { + DCHECK(!hooked_); + if (!hooked_) { + DCHECK(global_hook_); + + // Luckily, Patch() doesn't call malloc or windows alloc routines + // itself -- though it does call new (we can use PatchWithStub to + // get around that, and will need to if we need to patch new). + + HMODULE hkernel32 = ::GetModuleHandle(L"kernel32"); + CHECK(hkernel32 != NULL); + + HMODULE hntdll = ::GetModuleHandle(L"ntdll"); + CHECK(hntdll != NULL); + + // Now that we've found all the functions, patch them + INSTALL_PATCH(HeapCreate); + INSTALL_PATCH(HeapDestroy); + INSTALL_PATCH(HeapAlloc); + INSTALL_PATCH(HeapReAlloc); + INSTALL_PATCH(HeapFree); + INSTALL_PATCH(VirtualAllocEx); + INSTALL_PATCH(VirtualFreeEx); + INSTALL_PATCH(MapViewOfFileEx); + INSTALL_PATCH(MapViewOfFile); + INSTALL_PATCH(UnmapViewOfFile); + INSTALL_NTDLLPATCH(NtUnmapViewOfSection); + INSTALL_PATCH(GlobalAlloc); + INSTALL_PATCH(GlobalReAlloc); + INSTALL_PATCH(GlobalFree); + INSTALL_PATCH(LocalAlloc); + INSTALL_PATCH(LocalReAlloc); + INSTALL_PATCH(LocalFree); + + // We are finally completely hooked. + hooked_ = true; + } + return true; +} + +bool MemoryHook::Unhook() { + if (hooked_) { + // We need to go back to the system malloc/etc at global destruct time, + // so objects that were constructed before tcmalloc, using the system + // malloc, can destroy themselves using the system free. This depends + // on DLLs unloading in the reverse order in which they load! + // + // We also go back to the default HeapAlloc/etc, just for consistency. + // Who knows, it may help avoid weird bugs in some situations. + UNINSTALL_PATCH(HeapCreate); + UNINSTALL_PATCH(HeapDestroy); + UNINSTALL_PATCH(HeapAlloc); + UNINSTALL_PATCH(HeapReAlloc); + UNINSTALL_PATCH(HeapFree); + UNINSTALL_PATCH(VirtualAllocEx); + UNINSTALL_PATCH(VirtualFreeEx); + UNINSTALL_PATCH(MapViewOfFile); + UNINSTALL_PATCH(MapViewOfFileEx); + UNINSTALL_PATCH(UnmapViewOfFile); + UNINSTALL_PATCH(NtUnmapViewOfSection); + UNINSTALL_PATCH(GlobalAlloc); + UNINSTALL_PATCH(GlobalReAlloc); + UNINSTALL_PATCH(GlobalFree); + UNINSTALL_PATCH(LocalAlloc); + UNINSTALL_PATCH(LocalReAlloc); + UNINSTALL_PATCH(LocalFree); + + hooked_ = false; + } + return true; +} + +bool MemoryHook::RegisterWatcher(MemoryObserver* watcher) { + DCHECK(global_hook_->watcher_ == NULL); + + if (!hooked_) + Hook(); + + DCHECK(global_hook_); + global_hook_->watcher_ = watcher; + return true; +} + +bool MemoryHook::UnregisterWatcher(MemoryObserver* watcher) { + DCHECK(hooked_); + DCHECK(global_hook_->watcher_ == watcher); + global_hook_->watcher_ = NULL; + + // For now, since there are no more watchers, unhook memory. + return Unhook(); +} + +bool MemoryHook::CreateHeap() { + // Create a heap for our own memory. + DCHECK(heap_ == NULL); + heap_ = HeapCreate(0, 0, 0); + DCHECK(heap_ != NULL); + return heap_ != NULL; +} + +bool MemoryHook::CloseHeap() { + DCHECK(heap_ != NULL); + HeapDestroy(heap_); + heap_ = NULL; + return true; +} + +void MemoryHook::OnTrack(HANDLE heap, int32 id, int32 size) { + // Don't notify about allocations to our internal heap. + if (heap == heap_) + return; + + if (watcher_) + watcher_->OnTrack(heap, id, size); +} + +void MemoryHook::OnUntrack(HANDLE heap, int32 id, int32 size) { + // Don't notify about allocations to our internal heap. + if (heap == heap_) + return; + + if (watcher_) + watcher_->OnUntrack(heap, id, size); +} |