Commit 57fa6dc6 authored by cbentzel@chromium.org's avatar cbentzel@chromium.org

Remove memory_watcher tool as well as --memory-profile command line flag.

This tool has not been maintained or used in a while, and tools such as UMDH work well as a replacement.

BUG=383024

Review URL: https://codereview.chromium.org/314253003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278607 0039d316-1c4b-4281-b951-d872f2087c98
parent 2871fcd2
......@@ -164,13 +164,6 @@
'../base/allocator/allocator.gyp:*',
],
}],
# Don't enable dependencies that don't work on Win64.
['target_arch!="x64"', {
'dependencies': [
# TODO(jschuh) Enable Win64 Memory Watcher. crbug.com/176877
'../tools/memory_watcher/memory_watcher.gyp:*',
],
}],
],
'dependencies': [
'../chrome_elf/chrome_elf.gyp:*',
......
......@@ -47,7 +47,6 @@
#include "base/strings/string_util.h"
#include "chrome/common/child_process_logging.h"
#include "sandbox/win/src/sandbox.h"
#include "tools/memory_watcher/memory_watcher.h"
#include "ui/base/resource/resource_bundle_win.h"
#endif
......@@ -134,15 +133,6 @@ extern int ServiceProcessMain(const content::MainFunctionParams&);
namespace {
#if defined(OS_WIN)
const wchar_t kProfilingDll[] = L"memory_watcher.dll";
// Load the memory profiling DLL. All it needs to be activated
// is to be loaded. Return true on success, false otherwise.
bool LoadMemoryProfiler() {
HMODULE prof_module = LoadLibrary(kProfilingDll);
return prof_module != NULL;
}
// Early versions of Chrome incorrectly registered a chromehtml: URL handler,
// which gives us nothing but trouble. Avoid launching chrome this way since
// some apps fail to properly escape arguments.
......@@ -225,16 +215,6 @@ static void AdjustLinuxOOMScore(const std::string& process_type) {
}
#endif // defined(OS_LINUX)
// Enable the heap profiler if the appropriate command-line switch is
// present, bailing out of the app we can't.
void EnableHeapProfiler(const CommandLine& command_line) {
#if defined(OS_WIN)
if (command_line.HasSwitch(switches::kMemoryProfiling))
if (!LoadMemoryProfiler())
exit(-1);
#endif
}
// Returns true if this subprocess type needs the ResourceBundle initialized
// and resources loaded.
bool SubprocessNeedsResourceBundle(const std::string& process_type) {
......@@ -672,9 +652,6 @@ void ChromeMainDelegate::PreSandboxStartup() {
startup_timer_.reset(new base::StatsScope<base::StatsCounterTimer>
(*stats_counter_timer_));
// Enable the heap profiler as early as possible!
EnableHeapProfiler(command_line);
// Enable Message Loop related state asap.
if (command_line.HasSwitch(switches::kMessageLoopHistogrammer))
base::MessageLoop::EnableHistogrammer(true);
......
......@@ -1721,7 +1721,6 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
switches::kEnableStreamlinedHostedApps,
switches::kEnableWatchdog,
switches::kEnableWebBasedSignin,
switches::kMemoryProfiling,
switches::kMessageLoopHistogrammer,
switches::kOutOfProcessPdf,
switches::kPlaybackMode,
......@@ -1748,15 +1747,14 @@ void ChromeContentBrowserClient::AppendExtraCommandLineSwitches(
command_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
arraysize(kSwitchNames));
} else if (process_type == switches::kPluginProcess) {
static const char* const kSwitchNames[] = {
#if defined(OS_CHROMEOS)
static const char* const kSwitchNames[] = {
chromeos::switches::kLoginProfile,
#endif
switches::kMemoryProfiling,
};
command_line->CopySwitchesFrom(browser_command_line, kSwitchNames,
arraysize(kSwitchNames));
#endif
} else if (process_type == switches::kZygoteProcess) {
static const char* const kSwitchNames[] = {
// Load (in-process) Pepper plugins in-process in the zygote pre-sandbox.
......
......@@ -832,10 +832,6 @@ const char kManualEnhancedBookmarksOptout[] =
// Forces the maximum disk space to be used by the media cache, in bytes.
const char kMediaCacheSize[] = "media-cache-size";
// Enables dynamic loading of the Memory Profiler DLL, which will trace all
// memory allocations during the run.
const char kMemoryProfiling[] = "memory-profile";
// Enables histograming of tasks served by MessageLoop. See
// about:histograms/Loop for results, which show frequency of messages on each
// thread, including APC count, object signalling count, etc.
......
......@@ -236,7 +236,6 @@ extern const char kSupervisedUserSyncToken[];
extern const char kManualEnhancedBookmarks[];
extern const char kManualEnhancedBookmarksOptout[];
extern const char kMediaCacheSize[];
extern const char kMemoryProfiling[];
extern const char kMessageLoopHistogrammer[];
extern const char kMetricsRecordingOnly[];
extern const char kNetLogLevel[];
......
include_rules = [
"+ui/gfx",
]
memory_watcher is a library that can be linked into chromium to trace the
memory allocations. It works by hooking the system allocation/deallocation
functions, and recording the actions.
To use memory_watcher in chromium:
(1) Compile the memory_watcher library (it is part of the solution by default)
(2) Run chromium with these flags "--memory-profile -no-sandbox"
(The instrumentation doesn't work with the sandbox)
(3) Hit ctrl-alt-D to generate a dump of the memory allocations.
This will create a log file called memorywatcher.logXXXX for every
chromium process (where XXXX is the pid).
The log file is a human readable text format, which can be further analyzed
using the helpers in the "scripts/" directory.
This diff is collapsed.
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Parts of this module come from:
// http://www.codeproject.com/KB/applications/visualleakdetector.aspx
// by Dan Moulding.
// http://www.codeproject.com/KB/threads/StackWalker.aspx
// by Jochen Kalmbach
#ifndef TOOLS_MEMORY_WATCHER_CALL_STACK_H_
#define TOOLS_MEMORY_WATCHER_CALL_STACK_H_
#include <windows.h>
#include <dbghelp.h>
#include <functional>
#include <map>
#include <string>
#include "base/logging.h"
#include "base/synchronization/lock.h"
#include "tools/memory_watcher/memory_watcher.h"
// The CallStack Class
// A stack where memory has been allocated.
class CallStack {
public:
// Initialize for tracing CallStacks.
static bool Initialize();
CallStack();
virtual ~CallStack() {}
// Get a hash for this CallStack.
// Identical stack traces will have matching hashes.
int32 hash() { return hash_; }
// Get a unique ID for this CallStack.
// No two CallStacks will ever have the same ID. The ID is a monotonically
// increasing number. Newer CallStacks always have larger IDs.
int32 id() { return id_; }
// Retrieves the frame at the specified index.
DWORD_PTR frame(int32 index) {
DCHECK(index < frame_count_ && index >= 0);
return frames_[index];
}
// Compares the CallStack to another CallStack
// for equality. Two CallStacks are equal if they are the same size and if
// every frame in each is identical to the corresponding frame in the other.
bool IsEqual(const CallStack &target);
typedef std::basic_string<char, std::char_traits<char>,
PrivateHookAllocator<char> > PrivateAllocatorString;
// Convert the callstack to a string stored in output.
void CallStack::ToString(PrivateAllocatorString* output);
//
bool Valid() const { return valid_; }
private:
// The maximum number of frames to trace.
static const int kMaxTraceFrames = 32;
// Pushes a frame's program counter onto the CallStack.
void AddFrame(DWORD_PTR programcounter);
// Traces the stack, starting from this function, up to kMaxTraceFrames
// frames.
bool GetStackTrace();
// Functions for manipulating the frame list.
void ClearFrames();
// Dynamically load the DbgHelp library and supporting routines that we
// will use.
static bool LoadDbgHelp();
static void LockDbgHelp() {
dbghelp_lock_.Acquire();
active_thread_id_ = GetCurrentThreadId();
}
static void UnlockDbgHelp() {
active_thread_id_ = 0;
dbghelp_lock_.Release();
}
class AutoDbgHelpLock {
public:
AutoDbgHelpLock() {
CallStack::LockDbgHelp();
}
~AutoDbgHelpLock() {
CallStack::UnlockDbgHelp();
}
};
// Check to see if this thread is already processing a stack.
bool LockedRecursionDetected() const;
// According to http://msdn2.microsoft.com/en-us/library/ms680650(VS.85).aspx
// "All DbgHelp functions, such as this one, are single threaded. Therefore,
// calls from more than one thread to this function will likely result in
// unexpected behavior or memory corruption. To avoid this, you must
// synchromize all concurrent calls from one thread to this function."
//
// dbghelp_lock_ is used to serialize access across all calls to the DbgHelp
// library. This may be overly conservative (serializing them all together),
// but does guarantee correctness.
static base::Lock dbghelp_lock_;
// Record the fact that dbghelp has been loaded.
// Changes to this variable are protected by dbghelp_lock_.
// It will only changes once... from false to true.
static bool dbghelp_loaded_;
// To prevent infinite recursion due to unexpected side effects in libraries,
// we track the thread_id of the thread currently holding the dbghelp_lock_.
// We avoid re-aquiring said lock and return an !valid_ instance when we
// detect recursion.
static DWORD active_thread_id_;
int frame_count_; // Current size (in frames)
DWORD_PTR frames_[kMaxTraceFrames];
int32 hash_;
int32 id_;
// Indicate is this is a valid stack.
// This is false if recursion precluded a real stack generation.
bool valid_;
// Cache ProgramCounter -> Symbol lookups.
// This cache is not thread safe.
typedef std::map<int32, PrivateAllocatorString, std::less<int32>,
PrivateHookAllocator<int32> > SymbolCache;
static SymbolCache* symbol_cache_;
DISALLOW_COPY_AND_ASSIGN(CallStack);
};
// An AllocationStack is a type of CallStack which represents a CallStack where
// memory has been allocated. This class is also a list item, so that it can
// be easilly allocated and deallocated from its static singly-linked-list of
// free instances.
class AllocationStack : public CallStack {
public:
explicit AllocationStack(int32 size)
: next_(NULL), size_(size), CallStack() {}
// We maintain a freelist of the AllocationStacks.
void* operator new(size_t s);
void operator delete(void*p);
int32 size() const { return size_; }
private:
AllocationStack* next_; // Pointer used when on the freelist.
int32 size_; // Size of block allocated.
static AllocationStack* freelist_;
static base::Lock freelist_lock_;
DISALLOW_COPY_AND_ASSIGN(AllocationStack);
};
#endif // TOOLS_MEMORY_WATCHER_CALL_STACK_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The memory_watcher.dll is hooked by simply linking it. When we get the
// windows notification that this DLL is loaded, we do a few things:
// 1) Register a Hot Key.
// Only one process can hook the Hot Key, so one will get it, and the
// others will silently fail.
// 2) Create a thread to wait on an event.
// Since only one process will get the HotKey, it will be responsible for
// notifying all process when it's time to do something. Each process
// will have a thread waiting for communication from the master to dump
// the callstacks.
#include <windows.h>
#include "base/at_exit.h"
#include "tools/memory_watcher/memory_watcher.h"
#include "tools/memory_watcher/hotkey.h"
class MemoryWatcherDumpKey; // Defined below.
static wchar_t* kDumpEvent = L"MemWatcher.DumpEvent";
static base::AtExitManager* g_memory_watcher_exit_manager = NULL;
static MemoryWatcher* g_memory_watcher = NULL;
static MemoryWatcherDumpKey* g_hotkey_handler = NULL;
static HANDLE g_dump_event = INVALID_HANDLE_VALUE;
static HANDLE g_quit_event = INVALID_HANDLE_VALUE;
static HANDLE g_watcher_thread = INVALID_HANDLE_VALUE;
// A HotKey to dump the memory statistics.
class MemoryWatcherDumpKey : public HotKeyHandler {
public:
MemoryWatcherDumpKey(UINT modifiers, UINT vkey)
: HotKeyHandler(modifiers, vkey) {}
virtual void OnHotKey(UINT, WPARAM, LPARAM) {
SetEvent(g_dump_event);
}
};
// Creates the global memory watcher.
void CreateMemoryWatcher() {
g_memory_watcher_exit_manager = new base::AtExitManager();
g_memory_watcher = new MemoryWatcher();
// Register ALT-CONTROL-D to Dump Memory stats.
g_hotkey_handler = new MemoryWatcherDumpKey(MOD_ALT|MOD_CONTROL, 0x44);
}
// Deletes the global memory watcher.
void DeleteMemoryWatcher() {
if (g_hotkey_handler)
delete g_hotkey_handler;
g_hotkey_handler = NULL;
if (g_memory_watcher)
delete g_memory_watcher;
g_memory_watcher = NULL;
// Intentionly leak g_memory_watcher_exit_manager.
}
// Thread for watching for key events.
DWORD WINAPI ThreadMain(LPVOID) {
bool stopping = false;
HANDLE events[2] = { g_dump_event, g_quit_event };
while (!stopping) {
DWORD rv = WaitForMultipleObjects(2, events, FALSE, INFINITE);
switch (rv) {
case WAIT_OBJECT_0:
if (g_memory_watcher) {
g_memory_watcher->DumpLeaks();
}
stopping = true;
break;
case WAIT_OBJECT_0 + 1:
stopping = true;
break;
default:
NOTREACHED();
break;
}
}
return 0;
}
// Creates the background thread
void CreateBackgroundThread() {
// Create a named event which can be used to notify
// all watched processes.
g_dump_event = CreateEvent(0, TRUE, FALSE, kDumpEvent);
DCHECK(g_dump_event != NULL);
// Create a local event which can be used to kill our
// background thread.
g_quit_event = CreateEvent(0, TRUE, FALSE, NULL);
DCHECK(g_quit_event != NULL);
// Create the background thread.
g_watcher_thread = CreateThread(0,
0,
ThreadMain,
0,
0,
0);
DCHECK(g_watcher_thread != NULL);
}
// Tell the background thread to stop.
void StopBackgroundThread() {
// Send notification to our background thread.
SetEvent(g_quit_event);
// Wait for our background thread to die.
DWORD rv = WaitForSingleObject(g_watcher_thread, INFINITE);
DCHECK(rv == WAIT_OBJECT_0);
// Cleanup our global handles.
CloseHandle(g_quit_event);
CloseHandle(g_dump_event);
CloseHandle(g_watcher_thread);
}
bool IsChromeExe() {
return GetModuleHandleA("chrome.exe") != NULL;
}
extern "C" {
// DllMain is the windows entry point to this DLL.
// We use the entry point as the mechanism for starting and stopping
// the MemoryWatcher.
BOOL WINAPI DllMain(HINSTANCE dll_instance, DWORD reason,
LPVOID reserved) {
if (!IsChromeExe())
return FALSE;
switch (reason) {
case DLL_PROCESS_ATTACH:
CreateMemoryWatcher();
CreateBackgroundThread();
break;
case DLL_PROCESS_DETACH:
DeleteMemoryWatcher();
StopBackgroundThread();
break;
}
return TRUE;
}
__declspec(dllexport) void __cdecl SetLogName(char* name) {
g_memory_watcher->SetLogName(name);
}
} // extern "C"
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef TOOLS_MEMORY_WATCHER_HOTKEY_H_
#define TOOLS_MEMORY_WATCHER_HOTKEY_H_
#include "ui/gfx/rect.h"
#include "ui/gfx/win/msg_util.h"
#include "ui/gfx/win/window_impl.h"
// HotKey handler.
// Programs wishing to register a hotkey can use this.
class HotKeyHandler : public gfx::WindowImpl {
public:
HotKeyHandler(UINT modifiers, UINT vk)
: modifiers_(modifiers),
vkey_(vk) {
Start();
}
~HotKeyHandler() { Stop(); }
CR_BEGIN_MSG_MAP_EX(HotKeyHandler)
CR_MSG_WM_HOTKEY(OnHotKey)
CR_END_MSG_MAP()
private:
static const int hotkey_id = 0x0000baba;
bool Start() {
set_window_style(WS_POPUP);
Init(NULL, gfx::Rect());
return RegisterHotKey(hwnd(), hotkey_id, modifiers_, vkey_) == TRUE;
}
void Stop() {
UnregisterHotKey(hwnd(), hotkey_id);
DestroyWindow(hwnd());
}
// Handle the registered Hotkey being pressed.
virtual void OnHotKey(UINT /*uMsg*/,
WPARAM /*wParam*/,
LPARAM /*lParam*/) = 0;
UINT modifiers_;
UINT vkey_;
};
#endif // TOOLS_MEMORY_WATCHER_HOTKEY_H_
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
* Table of relevant information about how to decode the ModR/M byte.
* Based on information in the IA 32 Intel Architecture
* Software Developer's Manual Volume 2: Instruction Set Reference.
*/
#include "mini_disassembler.h"
#include "mini_disassembler_types.h"
namespace sidestep {
const ModrmEntry MiniDisassembler::s_ia16_modrm_map_[] = {
// mod == 00
/* r/m == 000 */ { false, false, OS_ZERO },
/* r/m == 001 */ { false, false, OS_ZERO },
/* r/m == 010 */ { false, false, OS_ZERO },
/* r/m == 011 */ { false, false, OS_ZERO },
/* r/m == 100 */ { false, false, OS_ZERO },
/* r/m == 101 */ { false, false, OS_ZERO },
/* r/m == 110 */ { true, false, OS_WORD },
/* r/m == 111 */ { false, false, OS_ZERO },
// mod == 01
/* r/m == 000 */ { true, false, OS_BYTE },
/* r/m == 001 */ { true, false, OS_BYTE },
/* r/m == 010 */ { true, false, OS_BYTE },
/* r/m == 011 */ { true, false, OS_BYTE },
/* r/m == 100 */ { true, false, OS_BYTE },
/* r/m == 101 */ { true, false, OS_BYTE },
/* r/m == 110 */ { true, false, OS_BYTE },
/* r/m == 111 */ { true, false, OS_BYTE },
// mod == 10
/* r/m == 000 */ { true, false, OS_WORD },
/* r/m == 001 */ { true, false, OS_WORD },
/* r/m == 010 */ { true, false, OS_WORD },
/* r/m == 011 */ { true, false, OS_WORD },
/* r/m == 100 */ { true, false, OS_WORD },
/* r/m == 101 */ { true, false, OS_WORD },
/* r/m == 110 */ { true, false, OS_WORD },
/* r/m == 111 */ { true, false, OS_WORD },
// mod == 11
/* r/m == 000 */ { false, false, OS_ZERO },
/* r/m == 001 */ { false, false, OS_ZERO },
/* r/m == 010 */ { false, false, OS_ZERO },
/* r/m == 011 */ { false, false, OS_ZERO },
/* r/m == 100 */ { false, false, OS_ZERO },
/* r/m == 101 */ { false, false, OS_ZERO },
/* r/m == 110 */ { false, false, OS_ZERO },
/* r/m == 111 */ { false, false, OS_ZERO }
};
const ModrmEntry MiniDisassembler::s_ia32_modrm_map_[] = {
// mod == 00
/* r/m == 000 */ { false, false, OS_ZERO },
/* r/m == 001 */ { false, false, OS_ZERO },
/* r/m == 010 */ { false, false, OS_ZERO },
/* r/m == 011 */ { false, false, OS_ZERO },
/* r/m == 100 */ { false, true, OS_ZERO },
/* r/m == 101 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 110 */ { false, false, OS_ZERO },
/* r/m == 111 */ { false, false, OS_ZERO },
// mod == 01
/* r/m == 000 */ { true, false, OS_BYTE },
/* r/m == 001 */ { true, false, OS_BYTE },
/* r/m == 010 */ { true, false, OS_BYTE },
/* r/m == 011 */ { true, false, OS_BYTE },
/* r/m == 100 */ { true, true, OS_BYTE },
/* r/m == 101 */ { true, false, OS_BYTE },
/* r/m == 110 */ { true, false, OS_BYTE },
/* r/m == 111 */ { true, false, OS_BYTE },
// mod == 10
/* r/m == 000 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 001 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 010 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 011 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 100 */ { true, true, OS_DOUBLE_WORD },
/* r/m == 101 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 110 */ { true, false, OS_DOUBLE_WORD },
/* r/m == 111 */ { true, false, OS_DOUBLE_WORD },
// mod == 11
/* r/m == 000 */ { false, false, OS_ZERO },
/* r/m == 001 */ { false, false, OS_ZERO },
/* r/m == 010 */ { false, false, OS_ZERO },
/* r/m == 011 */ { false, false, OS_ZERO },
/* r/m == 100 */ { false, false, OS_ZERO },
/* r/m == 101 */ { false, false, OS_ZERO },
/* r/m == 110 */ { false, false, OS_ZERO },
/* r/m == 111 */ { false, false, OS_ZERO },
};
}; // namespace sidestep
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Static class for hooking Win32 API routines. For now,
// we only add one watcher at a time.
//
// TODO(mbelshe): Support multiple watchers.
#ifndef MEMORY_WATCHER_MEMORY_HOOK_
#define MEMORY_WATCHER_MEMORY_HOOK_
#include "base/logging.h"
// When allocating memory for internal use with the MemoryHook,
// we must always use the MemoryHook's heap; otherwise, the memory
// gets tracked, and it becomes an infinite loop (allocation() calls
// MemoryHook() which calls allocation(), etc).
//
// PrivateHookAllocator is an STL-friendly Allocator so that STL lists,
// maps, etc can be used on the global MemoryHook's heap.
template <class T>
class PrivateHookAllocator {
public:
// These type definitions are needed for stl allocators.
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef T value_type;
PrivateHookAllocator() {}
// Allocate memory for STL.
pointer allocate(size_type n, const void * = 0) {
return reinterpret_cast<T*>(MemoryHook::Alloc(n * sizeof(T)));
}
// Deallocate memory for STL.
void deallocate(void* p, size_type) {
if (p)
MemoryHook::Free(p);
}
// Construct the object
void construct(pointer p, const T& val) {
new (reinterpret_cast<T*>(p))T(val);
}
// Destruct an object
void destroy(pointer p) { p->~T(); }
size_type max_size() const { return size_t(-1); }
template <class U>
struct rebind { typedef PrivateHookAllocator<U> other; };
template <class U>
PrivateHookAllocator(const PrivateHookAllocator<U>&) {}
};
template<class T, class U> inline
bool operator==(const PrivateHookAllocator<T>&,
const PrivateHookAllocator<U>&) {
return (true);
}
template<class T, class U> inline
bool operator!=(const PrivateHookAllocator<T>& left,
const PrivateHookAllocator<U>& right) {
return (!(left == right));
}
// Classes which monitor memory from these hooks implement
// the MemoryObserver interface.
class MemoryObserver {
public:
virtual ~MemoryObserver() {}
// Track a pointer. Will capture the current StackTrace.
virtual void OnTrack(HANDLE heap, int32 id, int32 size) = 0;
// Untrack a pointer, removing it from our list.
virtual void OnUntrack(HANDLE heap, int32 id, int32 size) = 0;
};
class MemoryHook : MemoryObserver {
public:
// Initialize the MemoryHook. Must be called before
// registering watchers. This can be called repeatedly,
// but is not thread safe.
static bool Initialize();
// Returns true is memory allocations and deallocations
// are being traced.
static bool hooked() { return hooked_ != NULL; }
// Register a class to receive memory allocation & deallocation
// callbacks. If we haven't hooked memory yet, this call will
// force memory hooking to start.
static bool RegisterWatcher(MemoryObserver* watcher);
// Register a class to stop receiving callbacks. If there are
// no more watchers, this call will unhook memory.
static bool UnregisterWatcher(MemoryObserver* watcher);
// MemoryHook provides a private heap for allocating
// unwatched memory.
static void* Alloc(size_t size) {
DCHECK(global_hook_ && global_hook_->heap_);
return HeapAlloc(global_hook_->heap_, 0, size);
}
static void Free(void* ptr) {
DCHECK(global_hook_ && global_hook_->heap_);
HeapFree(global_hook_->heap_, 0, ptr);
}
// Access the global hook. For internal use only from static "C"
// hooks.
static MemoryHook* hook() { return global_hook_; }
// MemoryObserver interface.
virtual void OnTrack(HANDLE hHeap, int32 id, int32 size);
virtual void OnUntrack(HANDLE hHeap, int32 id, int32 size);
private:
MemoryHook();
~MemoryHook();
// Enable memory tracing. When memory is 'hooked',
// MemoryWatchers which have registered will be called
// as memory is allocated and deallocated.
static bool Hook();
// Disables memory tracing.
static bool Unhook();
// Create our private heap
bool CreateHeap();
// Close our private heap.
bool CloseHeap();
MemoryObserver* watcher_;
HANDLE heap_; // An internal accounting heap.
static bool hooked_;
static MemoryHook* global_hook_;
};
#endif // MEMORY_WATCHER_MEMORY_HOOK_
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <windows.h>
#include <tlhelp32.h> // for CreateToolhelp32Snapshot()
#include <map>
#include "tools/memory_watcher/memory_watcher.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/metrics/stats_counters.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "tools/memory_watcher/call_stack.h"
#include "tools/memory_watcher/preamble_patcher.h"
static base::StatsCounter mem_in_use("MemoryInUse.Bytes");
static base::StatsCounter mem_in_use_blocks("MemoryInUse.Blocks");
static base::StatsCounter mem_in_use_allocs("MemoryInUse.Allocs");
static base::StatsCounter mem_in_use_frees("MemoryInUse.Frees");
// ---------------------------------------------------------------------
MemoryWatcher::MemoryWatcher()
: file_(NULL),
hooked_(false),
active_thread_id_(0) {
MemoryHook::Initialize();
CallStack::Initialize();
block_map_ = new CallStackMap();
// Register last - only after we're ready for notifications!
Hook();
}
MemoryWatcher::~MemoryWatcher() {
Unhook();
CloseLogFile();
// Pointers in the block_map are part of the MemoryHook heap. Be sure
// to delete the map before closing the heap.
delete block_map_;
}
void MemoryWatcher::Hook() {
DCHECK(!hooked_);
MemoryHook::RegisterWatcher(this);
hooked_ = true;
}
void MemoryWatcher::Unhook() {
if (hooked_) {
MemoryHook::UnregisterWatcher(this);
hooked_ = false;
}
}
void MemoryWatcher::OpenLogFile() {
DCHECK(file_ == NULL);
file_name_ = "memwatcher";
if (!log_name_.empty()) {
file_name_ += ".";
file_name_ += log_name_;
}
file_name_ += ".log";
char buf[16];
file_name_ += _itoa(GetCurrentProcessId(), buf, 10);
std::string tmp_name(file_name_);
tmp_name += ".tmp";
file_ = fopen(tmp_name.c_str(), "w+");
}
void MemoryWatcher::CloseLogFile() {
if (file_ != NULL) {
fclose(file_);
file_ = NULL;
std::wstring tmp_name = base::ASCIIToWide(file_name_);
tmp_name += L".tmp";
base::Move(base::FilePath(tmp_name),
base::FilePath(base::ASCIIToWide(file_name_)));
}
}
bool MemoryWatcher::LockedRecursionDetected() const {
if (!active_thread_id_) return false;
DWORD thread_id = GetCurrentThreadId();
// TODO(jar): Perchance we should use atomic access to member.
return thread_id == active_thread_id_;
}
void MemoryWatcher::OnTrack(HANDLE heap, int32 id, int32 size) {
// Don't track zeroes. It's a waste of time.
if (size == 0)
return;
if (LockedRecursionDetected())
return;
// AllocationStack overrides new/delete to not allocate
// from the main heap.
AllocationStack* stack = new AllocationStack(size);
if (!stack->Valid()) return; // Recursion blocked generation of stack.
{
base::AutoLock lock(block_map_lock_);
// Ideally, we'd like to verify that the block being added
// here is not already in our list of tracked blocks. However,
// the lookup in our hash table is expensive and slows us too
// much.
CallStackMap::iterator block_it = block_map_->find(id);
if (block_it != block_map_->end()) {
#if 0 // Don't do this until stack->ToString() uses ONLY our heap.
active_thread_id_ = GetCurrentThreadId();
PrivateAllocatorString output;
block_it->second->ToString(&output);
// VLOG(1) << "First Stack size " << stack->size() << "was\n" << output;
stack->ToString(&output);
// VLOG(1) << "Second Stack size " << stack->size() << "was\n" << output;
#endif // 0
// TODO(jar): We should delete one stack, and keep the other, perhaps
// based on size.
// For now, just delete the first, and keep the second?
delete block_it->second;
}
// TODO(jar): Perchance we should use atomic access to member.
active_thread_id_ = 0; // Note: Only do this AFTER exiting above scope!
(*block_map_)[id] = stack;
}
mem_in_use.Add(size);
mem_in_use_blocks.Increment();
mem_in_use_allocs.Increment();
}
void MemoryWatcher::OnUntrack(HANDLE heap, int32 id, int32 size) {
DCHECK_GE(size, 0);
// Don't bother with these.
if (size == 0)
return;
if (LockedRecursionDetected())
return;
{
base::AutoLock lock(block_map_lock_);
active_thread_id_ = GetCurrentThreadId();
// First, find the block in our block_map.
CallStackMap::iterator it = block_map_->find(id);
if (it != block_map_->end()) {
AllocationStack* stack = it->second;
DCHECK(stack->size() == size);
block_map_->erase(id);
delete stack;
} else {
// Untracked item. This happens a fair amount, and it is
// normal. A lot of time elapses during process startup
// before the allocation routines are hooked.
size = 0; // Ignore size in tallies.
}
// TODO(jar): Perchance we should use atomic access to member.
active_thread_id_ = 0;
}
mem_in_use.Add(-size);
mem_in_use_blocks.Decrement();
mem_in_use_frees.Increment();
}
void MemoryWatcher::SetLogName(char* log_name) {
if (!log_name)
return;
log_name_ = log_name;
}
// Help sort lists of stacks based on allocation cost.
// Note: Sort based on allocation count is interesting too!
static bool CompareCallStackIdItems(MemoryWatcher::StackTrack* left,
MemoryWatcher::StackTrack* right) {
return left->size > right->size;
}
void MemoryWatcher::DumpLeaks() {
// We can only dump the leaks once. We'll cleanup the hooks here.
if (!hooked_)
return;
Unhook();
base::AutoLock lock(block_map_lock_);
active_thread_id_ = GetCurrentThreadId();
OpenLogFile();
// Aggregate contributions from each allocated block on per-stack basis.
CallStackIdMap stack_map;
for (CallStackMap::iterator block_it = block_map_->begin();
block_it != block_map_->end(); ++block_it) {
AllocationStack* stack = block_it->second;
int32 stack_hash = stack->hash();
int32 alloc_block_size = stack->size();
CallStackIdMap::iterator it = stack_map.find(stack_hash);
if (it == stack_map.end()) {
StackTrack tracker;
tracker.count = 1;
tracker.size = alloc_block_size;
tracker.stack = stack; // Temporary pointer into block_map_.
stack_map[stack_hash] = tracker;
} else {
it->second.count++;
it->second.size += alloc_block_size;
}
}
// Don't release lock yet, as block_map_ is still pointed into.
// Put references to StrackTracks into array for sorting.
std::vector<StackTrack*, PrivateHookAllocator<int32> >
stack_tracks(stack_map.size());
CallStackIdMap::iterator it = stack_map.begin();
for (size_t i = 0; i < stack_tracks.size(); ++i) {
stack_tracks[i] = &(it->second);
++it;
}
sort(stack_tracks.begin(), stack_tracks.end(), CompareCallStackIdItems);
int32 total_bytes = 0;
int32 total_blocks = 0;
for (size_t i = 0; i < stack_tracks.size(); ++i) {
StackTrack* stack_track = stack_tracks[i];
fwprintf(file_, L"%d bytes, %d allocs, #%d\n",
stack_track->size, stack_track->count, i);
total_bytes += stack_track->size;
total_blocks += stack_track->count;
CallStack* stack = stack_track->stack;
PrivateAllocatorString output;
stack->ToString(&output);
fprintf(file_, "%s", output.c_str());
}
fprintf(file_, "Total Leaks: %d\n", total_blocks);
fprintf(file_, "Total Stacks: %d\n", stack_tracks.size());
fprintf(file_, "Total Bytes: %d\n", total_bytes);
CloseLogFile();
}
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
{
'variables': {
'chromium_code': 1,
},
'targets': [
{
'target_name': 'memory_watcher',
'type': 'shared_library',
'dependencies': [
'../../base/base.gyp:base',
'../../ui/gfx/gfx.gyp:gfx',
'../../ui/gfx/gfx.gyp:gfx_geometry',
],
'defines': [
'BUILD_MEMORY_WATCHER',
],
'include_dirs': [
'../..',
],
# 4748 "/GS can not protect parameters and local variables from local
# buffer overrun because optimizations are disabled in function".
# 4740 "flow in or out of inline asm code suppresses global optimization"
# (result of __asm call x, __asm x:).
# Nothing to be done about these warnings.
'msvs_disabled_warnings': [ 4748, 4740 ],
'sources': [
'call_stack.cc',
'call_stack.h',
'dllmain.cc',
'hotkey.h',
'ia32_modrm_map.cc',
'ia32_opcode_map.cc',
'memory_hook.cc',
'memory_hook.h',
'memory_watcher.cc',
'memory_watcher.h',
'mini_disassembler.cc',
'preamble_patcher.cc',
'preamble_patcher.h',
'preamble_patcher_with_stub.cc',
],
},
],
}
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// MemoryWatcher.
// The MemoryWatcher is a library that can be linked into any
// win32 application. It will override the default memory allocators
// and track call stacks for any allocations that are made. It can
// then be used to see what memory is in use.
#ifndef TOOLS_MEMORY_WATCHER_MEMORY_WATCHER_
#define TOOLS_MEMORY_WATCHER_MEMORY_WATCHER_
#include <map>
#include <functional>
#include "base/synchronization/lock.h"
#include "tools/memory_watcher/memory_hook.h"
class CallStack;
class AllocationStack;
// The MemoryWatcher installs allocation hooks and monitors
// allocations and frees.
class MemoryWatcher : MemoryObserver {
public:
struct StackTrack {
CallStack* stack;
int count;
int size;
};
typedef std::map<int32, AllocationStack*, std::less<int32>,
PrivateHookAllocator<int32> > CallStackMap;
typedef std::map<int32, StackTrack, std::less<int32>,
PrivateHookAllocator<int32> > CallStackIdMap;
typedef std::basic_string<char, std::char_traits<char>,
PrivateHookAllocator<char> > PrivateAllocatorString;
MemoryWatcher();
virtual ~MemoryWatcher();
// Dump all tracked pointers still in use.
void DumpLeaks();
// MemoryObserver interface.
virtual void OnTrack(HANDLE heap, int32 id, int32 size);
virtual void OnUntrack(HANDLE heap, int32 id, int32 size);
// Sets a name that appears in the generated file name.
void SetLogName(char* log_name);
private:
// Opens the logfile which we create.
void OpenLogFile();
// Close the logfile.
void CloseLogFile();
// Hook the memory hooks.
void Hook();
// Unhooks our memory hooks.
void Unhook();
// Check to see if this thread is already processing a block, and should not
// recurse.
bool LockedRecursionDetected() const;
// This is for logging.
FILE* file_;
bool hooked_; // True when this class has the memory_hooks hooked.
// Either 0, or else the threadID for a thread that is actively working on
// a stack track. Used to avoid recursive tracking.
DWORD active_thread_id_;
base::Lock block_map_lock_;
// The block_map provides quick lookups based on the allocation
// pointer. This is important for having fast round trips through
// malloc/free.
CallStackMap *block_map_;
// The file name for that log.
std::string file_name_;
// An optional name that appears in the log file name (used to differentiate
// logs).
std::string log_name_;
};
#endif // TOOLS_MEMORY_WATCHER_MEMORY_WATCHER_
This diff is collapsed.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
* Definition of MiniDisassembler.
*/
#ifndef GOOGLE_PERFTOOLS_MINI_DISASSEMBLER_H__
#define GOOGLE_PERFTOOLS_MINI_DISASSEMBLER_H__
#include <windows.h>
#include "mini_disassembler_types.h"
// compatibility shim
#include "base/logging.h"
#define ASSERT(cond, msg) DCHECK(cond)
#define ASSERT1(cond) DCHECK(cond)
namespace sidestep {
// This small disassembler is very limited
// in its functionality, and in fact does only the bare minimum required by the
// preamble patching utility. It may be useful for other purposes, however.
//
// The limitations include at least the following:
// -# No support for coprocessor opcodes, MMX, etc.
// -# No machine-readable identification of opcodes or decoding of
// assembly parameters. The name of the opcode (as a string) is given,
// however, to aid debugging.
//
// You may ask what this little disassembler actually does, then? The answer is
// that it does the following, which is exactly what the patching utility needs:
// -# Indicates if opcode is a jump (any kind) or a return (any kind)
// because this is important for the patching utility to determine if
// a function is too short or there are jumps too early in it for it
// to be preamble patched.
// -# The opcode length is always calculated, so that the patching utility
// can figure out where the next instruction starts, and whether it
// already has enough instructions to replace with the absolute jump
// to the patching code.
//
// The usage is quite simple; just create a MiniDisassembler and use its
// Disassemble() method.
//
// If you would like to extend this disassembler, please refer to the
// IA-32 Intel Architecture Software Developer's Manual Volume 2:
// Instruction Set Reference for information about operand decoding
// etc.
class MiniDisassembler {
public:
// Creates a new instance and sets defaults.
//
// @param operand_default_32_bits If true, the default operand size is
// set to 32 bits, which is the default under Win32. Otherwise it is 16 bits.
// @param address_default_32_bits If true, the default address size is
// set to 32 bits, which is the default under Win32. Otherwise it is 16 bits.
MiniDisassembler(bool operand_default_32_bits,
bool address_default_32_bits);
// Equivalent to MiniDisassembler(true, true);
MiniDisassembler();
// Attempts to disassemble a single instruction starting from the
// address in memory it is pointed to.
//
// @param start Address where disassembly should start.
// @param instruction_bytes Variable that will be <b>incremented</b> by
// the length in bytes of the instruction.
// @return enItJump, enItReturn or enItGeneric on success. enItUnknown
// if unable to disassemble, enItUnused if this seems to be an unused
// opcode. In the last two (error) cases, cbInstruction will be set
// to 0xffffffff.
//
// @post This instance of the disassembler is ready to be used again,
// with unchanged defaults from creation time.
InstructionType Disassemble(unsigned char* start, unsigned int& instruction_bytes);
private:
// Makes the disassembler ready for reuse.
void Initialize();
// Sets the flags for address and operand sizes.
// @return Number of prefix bytes.
InstructionType ProcessPrefixes(unsigned char* start, unsigned int& size);
// Sets the flag for whether we have ModR/M, and increments
// operand_bytes_ if any are specifies by the opcode directly.
// @return Number of opcode bytes.
InstructionType ProcessOpcode(unsigned char * start,
unsigned int table,
unsigned int& size);
// Checks the type of the supplied operand. Increments
// operand_bytes_ if it directly indicates an immediate etc.
// operand. Asserts have_modrm_ if the operand specifies
// a ModR/M byte.
bool ProcessOperand(int flag_operand);
// Increments operand_bytes_ by size specified by ModR/M and
// by SIB if present.
// @return 0 in case of error, 1 if there is just a ModR/M byte,
// 2 if there is a ModR/M byte and a SIB byte.
bool ProcessModrm(unsigned char* start, unsigned int& size);
// Processes the SIB byte that it is pointed to.
// @param start Pointer to the SIB byte.
// @param mod The mod field from the ModR/M byte.
// @return 1 to indicate success (indicates 1 SIB byte)
bool ProcessSib(unsigned char* start, unsigned char mod, unsigned int& size);
// The instruction type we have decoded from the opcode.
InstructionType instruction_type_;
// Counts the number of bytes that is occupied by operands in
// the current instruction (note: we don't care about how large
// operands stored in registers etc. are).
unsigned int operand_bytes_;
// True iff there is a ModR/M byte in this instruction.
bool have_modrm_;
// True iff we need to decode the ModR/M byte (sometimes it just
// points to a register, we can tell by the addressing mode).
bool should_decode_modrm_;
// Current operand size is 32 bits if true, 16 bits if false.
bool operand_is_32_bits_;
// Default operand size is 32 bits if true, 16 bits if false.
bool operand_default_is_32_bits_;
// Current address size is 32 bits if true, 16 bits if false.
bool address_is_32_bits_;
// Default address size is 32 bits if true, 16 bits if false.
bool address_default_is_32_bits_;
// Huge big opcode table based on the IA-32 manual, defined
// in Ia32OpcodeMap.cc
static const OpcodeTable s_ia32_opcode_map_[];
// Somewhat smaller table to help with decoding ModR/M bytes
// when 16-bit addressing mode is being used. Defined in
// Ia32ModrmMap.cc
static const ModrmEntry s_ia16_modrm_map_[];
// Somewhat smaller table to help with decoding ModR/M bytes
// when 32-bit addressing mode is being used. Defined in
// Ia32ModrmMap.cc
static const ModrmEntry s_ia32_modrm_map_[];
// Indicators of whether we got certain prefixes that certain
// silly Intel instructions depend on in nonstandard ways for
// their behaviors.
bool got_f2_prefix_, got_f3_prefix_, got_66_prefix_;
};
}; // namespace sidestep
#endif // GOOGLE_PERFTOOLS_MINI_DISASSEMBLER_H__
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
* Several simple types used by the disassembler and some of the patching
* mechanisms.
*/
#ifndef GOOGLE_PERFTOOLS_MINI_DISASSEMBLER_TYPES_H__
#define GOOGLE_PERFTOOLS_MINI_DISASSEMBLER_TYPES_H__
namespace sidestep {
// Categories of instructions that we care about
enum InstructionType {
// This opcode is not used
IT_UNUSED,
// This disassembler does not recognize this opcode (error)
IT_UNKNOWN,
// This is not an instruction but a reference to another table
IT_REFERENCE,
// This byte is a prefix byte that we can ignore
IT_PREFIX,
// This is a prefix byte that switches to the nondefault address size
IT_PREFIX_ADDRESS,
// This is a prefix byte that switches to the nondefault operand size
IT_PREFIX_OPERAND,
// A jump or call instruction
IT_JUMP,
// A return instruction
IT_RETURN,
// Any other type of instruction (in this case we don't care what it is)
IT_GENERIC,
};
// Lists IA-32 operand sizes in multiples of 8 bits
enum OperandSize {
OS_ZERO = 0,
OS_BYTE = 1,
OS_WORD = 2,
OS_DOUBLE_WORD = 4,
OS_QUAD_WORD = 8,
OS_DOUBLE_QUAD_WORD = 16,
OS_32_BIT_POINTER = 32/8,
OS_48_BIT_POINTER = 48/8,
OS_SINGLE_PRECISION_FLOATING = 32/8,
OS_DOUBLE_PRECISION_FLOATING = 64/8,
OS_DOUBLE_EXTENDED_PRECISION_FLOATING = 80/8,
OS_128_BIT_PACKED_SINGLE_PRECISION_FLOATING = 128/8,
OS_PSEUDO_DESCRIPTOR = 6
};
// Operand addressing methods from the IA-32 manual. The enAmMask value
// is a mask for the rest. The other enumeration values are named for the
// names given to the addressing methods in the manual, e.g. enAm_D is for
// the D addressing method.
//
// The reason we use a full 4 bytes and a mask, is that we need to combine
// these flags with the enOperandType to store the details
// on the operand in a single integer.
enum AddressingMethod {
AM_NOT_USED = 0, // This operand is not used for this instruction
AM_MASK = 0x00FF0000, // Mask for the rest of the values in this enumeration
AM_A = 0x00010000, // A addressing type
AM_C = 0x00020000, // C addressing type
AM_D = 0x00030000, // D addressing type
AM_E = 0x00040000, // E addressing type
AM_F = 0x00050000, // F addressing type
AM_G = 0x00060000, // G addressing type
AM_I = 0x00070000, // I addressing type
AM_J = 0x00080000, // J addressing type
AM_M = 0x00090000, // M addressing type
AM_O = 0x000A0000, // O addressing type
AM_P = 0x000B0000, // P addressing type
AM_Q = 0x000C0000, // Q addressing type
AM_R = 0x000D0000, // R addressing type
AM_S = 0x000E0000, // S addressing type
AM_T = 0x000F0000, // T addressing type
AM_V = 0x00100000, // V addressing type
AM_W = 0x00110000, // W addressing type
AM_X = 0x00120000, // X addressing type
AM_Y = 0x00130000, // Y addressing type
AM_REGISTER = 0x00140000, // Specific register is always used as this op
AM_IMPLICIT = 0x00150000, // An implicit, fixed value is used
};
// Operand types from the IA-32 manual. The enOtMask value is
// a mask for the rest. The rest of the values are named for the
// names given to these operand types in the manual, e.g. enOt_ps
// is for the ps operand type in the manual.
//
// The reason we use a full 4 bytes and a mask, is that we need
// to combine these flags with the enAddressingMethod to store the details
// on the operand in a single integer.
enum OperandType {
OT_MASK = 0xFF000000,
OT_A = 0x01000000,
OT_B = 0x02000000,
OT_C = 0x03000000,
OT_D = 0x04000000,
OT_DQ = 0x05000000,
OT_P = 0x06000000,
OT_PI = 0x07000000,
OT_PS = 0x08000000, // actually unsupported for (we don't know its size)
OT_Q = 0x09000000,
OT_S = 0x0A000000,
OT_SS = 0x0B000000,
OT_SI = 0x0C000000,
OT_V = 0x0D000000,
OT_W = 0x0E000000,
OT_SD = 0x0F000000, // scalar double-precision floating-point value
OT_PD = 0x10000000, // double-precision floating point
// dummy "operand type" for address mode M - which doesn't specify
// operand type
OT_ADDRESS_MODE_M = 0x80000000
};
// Everything that's in an Opcode (see below) except the three
// alternative opcode structs for different prefixes.
struct SpecificOpcode {
// Index to continuation table, or 0 if this is the last
// byte in the opcode.
int table_index_;
// The opcode type
InstructionType type_;
// Description of the type of the dest, src and aux operands,
// put together from an enOperandType flag and an enAddressingMethod
// flag.
int flag_dest_;
int flag_source_;
int flag_aux_;
// We indicate the mnemonic for debugging purposes
const char* mnemonic_;
};
// The information we keep in our tables about each of the different
// valid instructions recognized by the IA-32 architecture.
struct Opcode {
// Index to continuation table, or 0 if this is the last
// byte in the opcode.
int table_index_;
// The opcode type
InstructionType type_;
// Description of the type of the dest, src and aux operands,
// put together from an enOperandType flag and an enAddressingMethod
// flag.
int flag_dest_;
int flag_source_;
int flag_aux_;
// We indicate the mnemonic for debugging purposes
const char* mnemonic_;
// Alternative opcode info if certain prefixes are specified.
// In most cases, all of these are zeroed-out. Only used if
// bPrefixDependent is true.
bool is_prefix_dependent_;
SpecificOpcode opcode_if_f2_prefix_;
SpecificOpcode opcode_if_f3_prefix_;
SpecificOpcode opcode_if_66_prefix_;
};
// Information about each table entry.
struct OpcodeTable {
// Table of instruction entries
const Opcode* table_;
// How many bytes left to shift ModR/M byte <b>before</b> applying mask
unsigned char shift_;
// Mask to apply to byte being looked at before comparing to table
unsigned char mask_;
// Minimum/maximum indexes in table.
unsigned char min_lim_;
unsigned char max_lim_;
};
// Information about each entry in table used to decode ModR/M byte.
struct ModrmEntry {
// Is the operand encoded as bytes in the instruction (rather than
// if it's e.g. a register in which case it's just encoded in the
// ModR/M byte)
bool is_encoded_in_instruction_;
// Is there a SIB byte? In this case we always need to decode it.
bool use_sib_byte_;
// What is the size of the operand (only important if it's encoded
// in the instruction)?
OperandSize operand_size_;
};
}; // namespace sidestep
#endif // GOOGLE_PERFTOOLS_MINI_DISASSEMBLER_TYPES_H__
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "preamble_patcher.h"
#include "memory_hook.h"
#include "mini_disassembler.h"
// compatibility shims
#include "base/logging.h"
// Definitions of assembly statements we need
#define ASM_JMP32REL 0xE9
#define ASM_INT3 0xCC
namespace sidestep {
SideStepError PreamblePatcher::RawPatchWithStubAndProtections(
void* target_function, void *replacement_function,
unsigned char* preamble_stub, unsigned long stub_size,
unsigned long* bytes_needed) {
// We need to be able to write to a process-local copy of the first
// MAX_PREAMBLE_STUB_SIZE bytes of target_function. We may be giving execute
// privilege to something that doesn't have it, but that's the price to pay
// for tools.
DWORD old_target_function_protect = 0;
BOOL succeeded = ::VirtualProtect(reinterpret_cast<void*>(target_function),
MAX_PREAMBLE_STUB_SIZE,
PAGE_EXECUTE_READWRITE,
&old_target_function_protect);
if (!succeeded) {
ASSERT(false, "Failed to make page containing target function "
"copy-on-write.");
return SIDESTEP_ACCESS_DENIED;
}
SideStepError error_code = RawPatchWithStub(target_function,
replacement_function,
preamble_stub,
stub_size,
bytes_needed);
if (SIDESTEP_SUCCESS != error_code) {
ASSERT1(false);
return error_code;
}
// Restore the protection of the first MAX_PREAMBLE_STUB_SIZE bytes of
// pTargetFunction to what they were before we started goofing around.
succeeded = ::VirtualProtect(reinterpret_cast<void*>(target_function),
MAX_PREAMBLE_STUB_SIZE,
old_target_function_protect,
&old_target_function_protect);
if (!succeeded) {
ASSERT(false, "Failed to restore protection to target function.");
// We must not return an error here because the function has actually
// been patched, and returning an error would likely cause our client
// code not to unpatch it. So we just keep going.
}
// Flush the instruction cache to make sure the processor doesn't execute the
// old version of the instructions (before our patch).
//
// FlushInstructionCache is actually a no-op at least on single-processor
// XP machines. I'm not sure why this is so, but it is, yet I want to keep
// the call to the API here for correctness in case there is a difference in
// some variants of Windows/hardware.
succeeded = ::FlushInstructionCache(::GetCurrentProcess(),
target_function,
MAX_PREAMBLE_STUB_SIZE);
if (!succeeded) {
ASSERT(false, "Failed to flush instruction cache.");
// We must not return an error here because the function has actually
// been patched, and returning an error would likely cause our client
// code not to unpatch it. So we just keep going.
}
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::RawPatch(void* target_function,
void* replacement_function,
void** original_function_stub) {
if (!target_function || !replacement_function || !original_function_stub ||
(*original_function_stub) || target_function == replacement_function) {
ASSERT(false, "Preconditions not met");
return SIDESTEP_INVALID_PARAMETER;
}
// @see MAX_PREAMBLE_STUB_SIZE for an explanation of how we arrives at
// this size
unsigned char* preamble_stub =
reinterpret_cast<unsigned char*>(
MemoryHook::Alloc(sizeof(unsigned char) * MAX_PREAMBLE_STUB_SIZE));
if (!preamble_stub) {
ASSERT(false, "Unable to allocate preamble-stub.");
return SIDESTEP_INSUFFICIENT_BUFFER;
}
// Change the protection of the newly allocated preamble stub to
// PAGE_EXECUTE_READWRITE. This is required to work with DEP (Data
// Execution Prevention) which will cause an exception if code is executed
// from a page on which you do not have read access.
DWORD old_stub_protect = 0;
BOOL succeeded = VirtualProtect(preamble_stub, MAX_PREAMBLE_STUB_SIZE,
PAGE_EXECUTE_READWRITE, &old_stub_protect);
if (!succeeded) {
ASSERT(false, "Failed to make page preamble stub read-write-execute.");
delete[] preamble_stub;
return SIDESTEP_ACCESS_DENIED;
}
SideStepError error_code = RawPatchWithStubAndProtections(target_function,
replacement_function,
preamble_stub,
MAX_PREAMBLE_STUB_SIZE,
NULL);
if (SIDESTEP_SUCCESS != error_code) {
ASSERT1(false);
delete[] preamble_stub;
return error_code;
}
*original_function_stub = reinterpret_cast<void*>(preamble_stub);
// NOTE: For hooking malloc/free, we don't want to use streams which
// allocate. Basically, we've hooked malloc, but not necessarily
// hooked free yet. To do anything which uses the heap could crash
// with a mismatched malloc/free!
//VLOG(1) << "PreamblePatcher::RawPatch successfully patched 0x"
// << target_function;
return SIDESTEP_SUCCESS;
}
SideStepError PreamblePatcher::Unpatch(void* target_function,
void* replacement_function,
void* original_function_stub) {
ASSERT1(target_function && original_function_stub);
if (!target_function || !original_function_stub) {
return SIDESTEP_INVALID_PARAMETER;
}
// We disassemble the preamble of the _stub_ to see how many bytes we
// originally copied to the stub.
MiniDisassembler disassembler;
unsigned int preamble_bytes = 0;
while (preamble_bytes < 5) {
InstructionType instruction_type = disassembler.Disassemble(
reinterpret_cast<unsigned char*>(original_function_stub) +
preamble_bytes, preamble_bytes);
if (IT_GENERIC != instruction_type) {
ASSERT(false, "Should only have generic instructions in stub!!");
return SIDESTEP_UNSUPPORTED_INSTRUCTION;
}
}
// Before unpatching, target_function should be a JMP to
// replacement_function. If it's not, then either it's an error, or
// we're falling into the case where the original instruction was a
// JMP, and we patched the jumped_to address rather than the JMP
// itself. (For instance, if malloc() is just a JMP to __malloc(),
// we patched __malloc() and not malloc().)
unsigned char* target = reinterpret_cast<unsigned char*>(target_function);
while (1) { // we stop when target is a JMP to replacement_function
if (target[0] != ASM_JMP32REL) {
ASSERT(false, "target_function does not look like it was patched.");
return SIDESTEP_INVALID_PARAMETER;
}
int relative_offset; // Windows guarantees int is 4 bytes
ASSERT1(sizeof(relative_offset) == 4);
memcpy(reinterpret_cast<void*>(&relative_offset),
reinterpret_cast<void*>(target + 1), 4);
unsigned char* jump_to = target + 5 + relative_offset;
if (jump_to == replacement_function)
break;
target = jump_to; // follow the jmp
}
// We need to be able to write to a process-local copy of the first
// MAX_PREAMBLE_STUB_SIZE bytes of target_function. We may be giving execute
// privilege to something that doesn't have it, but that's the price to pay
// for tools.
DWORD old_target_function_protect = 0;
BOOL succeeded = ::VirtualProtect(reinterpret_cast<void*>(target),
MAX_PREAMBLE_STUB_SIZE,
PAGE_EXECUTE_READWRITE,
&old_target_function_protect);
if (!succeeded) {
ASSERT(false, "Failed to make page containing target function "
"copy-on-write.");
return SIDESTEP_ACCESS_DENIED;
}
// Replace the first few bytes of the original function with the bytes we
// previously moved to the preamble stub.
memcpy(reinterpret_cast<void*>(target),
original_function_stub, preamble_bytes);
// Stub is now useless so delete it.
// [csilvers: Commented out for perftools because it causes big problems
// when we're unpatching malloc. We just let this live on as a leak.]
//delete original_function_stub;
// Restore the protection of the first MAX_PREAMBLE_STUB_SIZE bytes of
// target to what they were before we started goofing around.
succeeded = ::VirtualProtect(reinterpret_cast<void*>(target),
MAX_PREAMBLE_STUB_SIZE,
old_target_function_protect,
&old_target_function_protect);
// Flush the instruction cache to make sure the processor doesn't execute the
// old version of the instructions (before our patch).
//
// See comment on FlushInstructionCache elsewhere in this file.
succeeded = ::FlushInstructionCache(::GetCurrentProcess(),
target,
MAX_PREAMBLE_STUB_SIZE);
if (!succeeded) {
ASSERT(false, "Failed to flush instruction cache.");
return SIDESTEP_UNEXPECTED;
}
VLOG(1) << "PreamblePatcher::Unpatch successfully unpatched 0x"
<< target_function;
return SIDESTEP_SUCCESS;
}
}; // namespace sidestep
This diff is collapsed.
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/*
* Implementation of PreamblePatcher
*/
#include "preamble_patcher.h"
#include "mini_disassembler.h"
// Definitions of assembly statements we need
#define ASM_JMP32REL 0xE9
#define ASM_INT3 0xCC
namespace sidestep {
SideStepError PreamblePatcher::RawPatchWithStub(
void* target_function,
void *replacement_function,
unsigned char* preamble_stub,
unsigned long stub_size,
unsigned long* bytes_needed) {
if ((NULL == target_function) ||
(NULL == replacement_function) ||
(NULL == preamble_stub)) {
ASSERT(false, "Invalid parameters - either pTargetFunction or "
"pReplacementFunction or pPreambleStub were NULL.");
return SIDESTEP_INVALID_PARAMETER;
}
// TODO(V7:joi) Siggi and I just had a discussion and decided that both
// patching and unpatching are actually unsafe. We also discussed a
// method of making it safe, which is to freeze all other threads in the
// process, check their thread context to see if their eip is currently
// inside the block of instructions we need to copy to the stub, and if so
// wait a bit and try again, then unfreeze all threads once we've patched.
// Not implementing this for now since we're only using SideStep for unit
// testing, but if we ever use it for production code this is what we
// should do.
//
// NOTE: Stoyan suggests we can write 8 or even 10 bytes atomically using
// FPU instructions, and on newer processors we could use cmpxchg8b or
// cmpxchg16b. So it might be possible to do the patching/unpatching
// atomically and avoid having to freeze other threads. Note though, that
// doing it atomically does not help if one of the other threads happens
// to have its eip in the middle of the bytes you change while you change
// them.
unsigned char* target = reinterpret_cast<unsigned char*>(target_function);
// First, deal with a special case that we see with functions that
// point into an IAT table (including functions linked statically
// into the application): these function already starts with
// ASM_JMP32REL. For instance, malloc() might be implemented as a
// JMP to __malloc(). In that case, we replace the destination of
// the JMP (__malloc), rather than the JMP itself (malloc). This
// way we get the correct behavior no matter how malloc gets called.
if (target[0] == ASM_JMP32REL) {
// target[1-4] holds the place the jmp goes to, but it's
// relative to the next instruction.
int relative_offset; // Windows guarantees int is 4 bytes
ASSERT1(sizeof(relative_offset) == 4);
memcpy(reinterpret_cast<void*>(&relative_offset),
reinterpret_cast<void*>(target + 1), 4);
// I'd like to just say "target = target + 5 + relative_offset" here, but
// I can't, because the new target will need to have its protections set.
return RawPatchWithStubAndProtections(target + 5 + relative_offset,
replacement_function, preamble_stub,
stub_size, bytes_needed);
}
// Let's disassemble the preamble of the target function to see if we can
// patch, and to see how much of the preamble we need to take. We need 5
// bytes for our jmp instruction, so let's find the minimum number of
// instructions to get 5 bytes.
MiniDisassembler disassembler;
unsigned int preamble_bytes = 0;
while (preamble_bytes < 5) {
InstructionType instruction_type =
disassembler.Disassemble(target + preamble_bytes, preamble_bytes);
if (IT_JUMP == instruction_type) {
ASSERT(false, "Unable to patch because there is a jump instruction "
"in the first 5 bytes.");
return SIDESTEP_JUMP_INSTRUCTION;
} else if (IT_RETURN == instruction_type) {
ASSERT(false, "Unable to patch because function is too short");
return SIDESTEP_FUNCTION_TOO_SMALL;
} else if (IT_GENERIC != instruction_type) {
ASSERT(false, "Disassembler encountered unsupported instruction "
"(either unused or unknown)");
return SIDESTEP_UNSUPPORTED_INSTRUCTION;
}
}
if (NULL != bytes_needed)
*bytes_needed = preamble_bytes + 5;
// Inv: cbPreamble is the number of bytes (at least 5) that we need to take
// from the preamble to have whole instructions that are 5 bytes or more
// in size total. The size of the stub required is cbPreamble + size of
// jmp (5)
if (preamble_bytes + 5 > stub_size) {
ASSERT1(false);
return SIDESTEP_INSUFFICIENT_BUFFER;
}
// First, copy the preamble that we will overwrite.
memcpy(reinterpret_cast<void*>(preamble_stub),
reinterpret_cast<void*>(target), preamble_bytes);
// Now, make a jmp instruction to the rest of the target function (minus the
// preamble bytes we moved into the stub) and copy it into our preamble-stub.
// find address to jump to, relative to next address after jmp instruction
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4244)
#endif
int relative_offset_to_target_rest
= ((reinterpret_cast<unsigned char*>(target) + preamble_bytes) -
(preamble_stub + preamble_bytes + 5));
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// jmp (Jump near, relative, displacement relative to next instruction)
preamble_stub[preamble_bytes] = ASM_JMP32REL;
// copy the address
memcpy(reinterpret_cast<void*>(preamble_stub + preamble_bytes + 1),
reinterpret_cast<void*>(&relative_offset_to_target_rest), 4);
// Inv: preamble_stub points to assembly code that will execute the
// original function by first executing the first cbPreamble bytes of the
// preamble, then jumping to the rest of the function.
// Overwrite the first 5 bytes of the target function with a jump to our
// replacement function.
// (Jump near, relative, displacement relative to next instruction)
target[0] = ASM_JMP32REL;
// Find offset from instruction after jmp, to the replacement function.
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4244)
#endif
int offset_to_replacement_function =
reinterpret_cast<unsigned char*>(replacement_function) -
reinterpret_cast<unsigned char*>(target) - 5;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// complete the jmp instruction
memcpy(reinterpret_cast<void*>(target + 1),
reinterpret_cast<void*>(&offset_to_replacement_function), 4);
// Set any remaining bytes that were moved to the preamble-stub to INT3 so
// as not to cause confusion (otherwise you might see some strange
// instructions if you look at the disassembly, or even invalid
// instructions). Also, by doing this, we will break into the debugger if
// some code calls into this portion of the code. If this happens, it
// means that this function cannot be patched using this patcher without
// further thought.
if (preamble_bytes > 5) {
memset(reinterpret_cast<void*>(target + 5), ASM_INT3, preamble_bytes - 5);
}
// Inv: The memory pointed to by target_function now points to a relative
// jump instruction that jumps over to the preamble_stub. The preamble
// stub contains the first stub_size bytes of the original target
// function's preamble code, followed by a relative jump back to the next
// instruction after the first cbPreamble bytes.
return SIDESTEP_SUCCESS;
}
}; // namespace sidestep
#!/usr/bin/perl
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
sub process_raw($$) {
my $file = shift;
my $search = shift;
my %leaks = ();
my $save = 0;
my $print = 0;
my $bytes = 0;
my $calls = 0;
my $sum_bytes = 0;
my $sum_calls = 0;
open (LOGFILE, "$file") or die("could not open $file");
while(<LOGFILE>) {
my $line = $_;
if ($line =~ m/([0-9]*) bytes, ([0-9]*) allocs/) {
$save = "";
$print = 0;
$bytes = $1;
$calls = $2;
}
elsif ($line =~ m/$search/) {
$print = 1;
}
elsif ($line =~ m/=============/) {
$save .= $line;
if ($print) {
print "$bytes bytes ($calls calls)\n";
print $save;
$sum_bytes += $bytes;
$sum_calls += $calls;
$save = "";
$print = 0;
$calls = 0;
}
}
$save .= $line;
}
print("TOTAL: $sum_bytes bytes ($sum_calls calls)\n");
}
# ----- Main ------------------------------------------------
# Get the command line argument
my $filename = shift;
my $search = shift;
# Process the file.
process_raw($filename, $search);
#!/usr/bin/perl
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Given a memwatcher logfile, group memory allocations by callstack.
#
# Usage:
#
# memprof.pl <logfile>
#
# logfile -- The memwatcher.logXXXX file to summarize.
#
#
#
# Sample output:
#
# 54,061,617 100.00% AllocationStack::AllocationStack
# 41,975,368 77.64% malloc
# 11,886,592 21.99% VirtualAlloc
# 7,168,000 13.26% v8::internal::OS::Allocate
# 7,168,000 13.26% v8::internal::MemoryAllocator::AllocateRawMemory
# 5,976,184 11.05% WebCore::V8Bridge::evaluate
# 5,767,168 10.67% v8::internal::MemoryAllocator::AllocatePages
# 5,451,776 10.08% WebCore::V8Proxy::initContextIfNeeded
# ....
#
#
#
# ********
# Note: The output is currently sorted by decreasing size.
# ********
#
sub process_raw($$) {
my $file = shift;
my $filter = shift;
my %leaks = ();
my %stackframes = ();
my $blamed = 0;
my $bytes = 0;
my $hits = 0;
open (LOGFILE, "$file") or die("could not open $file");
while(<LOGFILE>) {
my $line = $_;
#print "$line";
chomp($line);
if ($line =~ m/([0-9]*) bytes, ([0-9]*) allocs/) {
# If we didn't find any frames to account this to, log that.
if ($blamed == 0) {
$leaks{"UNACCOUNTED"} += $bytes;
}
#print "START\n";
#print("stackframe " . $1 . ", " . $2 . "\n");
$hits = $2;
$bytes = $1;
%stackframes = (); # we have a new frame, clear the list.
$blamed = 0; # we haven't blamed anyone yet
}
elsif ($line =~ m/Total Bytes:[ ]*([0-9]*)/) {
$total_bytes += $1;
}
elsif ($line =~ m/=============/) {
next;
}
elsif ($line =~ m/[ ]*([\-a-zA-Z_\\0-9\.]*) \(([0-9]*)\):[ ]*([<>_a-zA-Z_0-9:]*)/) {
# print("junk: " . $line . "\n");
# print("file: $1\n");
# print("line: $2\n");
# print("function: $3\n");
#
# blame the function
my $pig = $3;
# my $pig = $1;
# only add the memory if this function is not yet on our callstack
if (!exists $stackframes{$pig}) {
$leaks{$pig} += $bytes;
}
$stackframes{$pig}++;
$blamed++;
}
}
# now dump our hash table
my $sum = 0;
my @keys = sort { $leaks{$b} <=> $leaks{$a} }keys %leaks;
for ($i=0; $i<@keys; $i++) {
my $key = @keys[$i];
printf "%11s\t%3.2f%%\t%s\n", comma_print($leaks{$key}), (100* $leaks{$key} / $total_bytes), $key;
$sum += $leaks{$key};
}
printf("TOTAL: %s\n", comma_print($sum));
}
# Insert commas into an integer after each three digits for printing.
sub comma_print {
my $num = "$_[0]";
$num =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1,/g;
return $num;
}
# ----- Main ------------------------------------------------
# Get the command line argument
my $filename = shift;
my $filter = shift;
# Process the file.
process_raw($filename, $filter);
#!/usr/bin/perl
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Blame callstacks for each memory allocation.
# Similar to memprof.pl, will also try to filter out unuseful stacks.
# TODO: better describe how these tools differ.
#
# Usage:
#
# memtrace.pl <logfile>
#
# logfile -- The memwatcher.logXXXX file to summarize.
#
#
#
# Sample output:
#
# 41,975,368 77.64% f:\sp\vctools\crt_bld\self_x86\crt\src\malloc.c (163): malloc
# 2,097,152 3.88% c:\src\chrome1\src\webkit\pending\frameloader.cpp (3300): WebCore::FrameLoader::committedLoad
# 1,572,864 2.91% c:\src\chrome1\src\webkit\port\bridge\v8bridge.cpp (214): WebCore::V8Bridge::evaluate
# 1,572,864 2.91% c:\src\chrome1\src\webkit\glue\webframeloaderclient_impl.cc (1071): WebFrameLoaderClient::committedLoad
# 1,572,864 2.91% c:\src\chrome1\src\v8\src\ast.h (1181): v8::internal::Visitor::Visit
#
#
#
sub process_raw($) {
my $file = shift;
my %leaks = ();
my $location_bytes = 0;
my $location_hits = 0;
my $location_blame = "";
my $location_last = "";
my $contains_load_lib = 0;
my $total_bytes = 0;
open (LOGFILE, "$file") or die("could not open $file");
while(<LOGFILE>) {
my $line = $_;
#print "$line";
chomp($line);
if ($line =~ m/([0-9]*) bytes, ([0-9]*) allocs/) {
#print "START\n";
# Dump "prior" frame here
if ($location_bytes > 0) {
#print("GOTLEAK: $location_bytes ($location_hits) $location_blame\n");
if ($location_blame eq "") {
$location_blame = $location_last;
}
if (!$contains_load_lib) {
$leaks{$location_blame} += $location_bytes;
}
$location_bytes = 0;
$location_blame = "";
$contains_load_lib = 0;
}
#print("stackframe " . $1 . ", " . $2 . "\n");
$location_hits = $2;
$location_bytes = $1;
}
elsif ($line =~ m/Total Bytes:[ ]*([0-9]*)/) {
$total_bytes += $1;
}
elsif ($line =~ m/LoadLibrary/) {
# skip these, they contain false positives.
$contains_load_lib = 1;
next;
}
elsif ($line =~ m/=============/) {
next;
}
elsif ($line =~ m/Untracking untracked/) {
next;
}
elsif ($line =~ m/[ ]*([a-z]:\\[a-z]*\\[a-zA-Z_\\0-9\.]*) /) {
my $filename = $1;
if ($filename =~ m/memory_watcher/) {
next;
}
if ($filename =~ m/skmemory_stdlib.cpp/) {
next;
}
if ($filename =~ m/stringimpl.cpp/) {
next;
}
if ($filename =~ m/stringbuffer.h/) {
next;
}
if ($filename =~ m/fastmalloc.h/) {
next;
}
if ($filename =~ m/microsoft visual studio 8/) {
next;
}
if ($filename =~ m/platformsdk_win2008_6_1/) {
next;
}
if ($location_blame eq "") {
# use this to blame the line
$location_blame = $line;
# use this to blame the file.
# $location_blame = $filename;
#print("blaming $location_blame\n");
}
} else {
# print("junk: " . $line . "\n");
if (! ($line =~ m/GetModuleFileNameA/) ) {
$location_last = $line;
}
}
}
# now dump our hash table
my $sum = 0;
my @keys = sort { $leaks{$b} <=> $leaks{$a} }keys %leaks;
for ($i=0; $i<@keys; $i++) {
my $key = @keys[$i];
if (0 == $total_bytes) { $total_bytes = 1; }
printf "%11s\t%3.2f%%\t%s\n", comma_print($leaks{$key}), (100* $leaks{$key} / $total_bytes), $key;
$sum += $leaks{$key};
}
printf("TOTAL: %s\n", comma_print($sum));
}
# Insert commas into an integer after each three digits for printing.
sub comma_print {
my $num = "$_[0]";
$num =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1,/g;
return $num;
}
# ----- Main ------------------------------------------------
# Get the command line argument
my $filename = shift;
# Process the file.
process_raw($filename);
#!/usr/bin/perl
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Read a memtrace logfile from stdin and group memory allocations by logical
# code component. The code component is guessed from the callstack, and
# is something like {v8, sqlite, disk cache, skia, etc..}
#
# Usage:
#
# summary.pl
#
# [STDIN] -- The memwatcher.logXXXX file to summarize.
#
sub process_stdin() {
my %leaks = ();
my $total_bytes = 0;
while(<STDIN>) {
my $line = $_;
chomp($line);
my $bytes, $loc;
($bytes, $loc) = ($line =~ m/[ \t]*([0-9]*)[ \t]*[0-9\.%]*[ \t]*(.*)/);
chomp($loc);
while(<STDIN>) {
my $cont = $_;
chomp($cont);
last if $cont =~ m/=====/;
$loc .= "\n" . $cont;
}
my $location_blame = "";
# print "Found: $bytes, $loc\n";
if ($loc =~ m/v8::internal::Snapshot::Deserialize/) {
$location_blame = "v8 Snapshot Deserialize";
} elsif ($loc =~ m/RenderStyle::create/) {
$location_blame = "RenderStyle::create";
} elsif ($loc =~ m/v8::internal::OldSpace::SlowAllocateRaw/) {
$location_blame = "v8 OldSpace";
} elsif ($loc =~ m/sqlite/) {
$location_blame = "sqlite";
} elsif ($loc =~ m/ TransportDIB::Map/) {
$location_blame = "Shared Memory Backing Store";
} elsif ($loc =~ m/imagedecoder/) {
$location_blame = "img decoder";
} elsif ($loc =~ m/SkBitmap/) {
$location_blame = "skia";
} elsif ($loc =~ m/disk_cache/) {
$location_blame = "disk cache";
} elsif ($loc =~ m/skia/) {
$location_blame = "skia";
} elsif ($loc =~ m/:WSA/) {
$location_blame = "net";
} elsif ($loc =~ m/dns/) {
$location_blame = "net";
} elsif ($loc =~ m/trunk\\net/) {
$location_blame = "net";
} elsif ($loc =~ m/WinHttp/) {
$location_blame = "WinHttp";
} elsif ($loc =~ m/:I_Crypt/) {
$location_blame = "WinHttpSSL";
} elsif ($loc =~ m/CryptGetTls/) {
$location_blame = "WinHttpSSL";
} elsif ($loc =~ m/WinVerifyTrust/) {
$location_blame = "WinHttpSSL";
} elsif ($loc =~ m/Cert/) {
$location_blame = "WinHttpSSL";
} elsif ($loc =~ m/plugin/) {
$location_blame = "plugin";
} elsif ($loc =~ m/NP_/) {
$location_blame = "plugin";
} elsif ($loc =~ m/hunspell/) {
$location_blame = "hunspell";
} elsif ($loc =~ m/TextCodec/) {
$location_blame = "fonts";
} elsif ($loc =~ m/glyph/) {
$location_blame = "fonts";
} elsif ($loc =~ m/cssparser/) {
$location_blame = "webkit css";
} elsif ($loc =~ m/::CSS/) {
$location_blame = "webkit css";
} elsif ($loc =~ m/Arena/) {
$location_blame = "webkit arenas";
} elsif ($loc =~ m/WebCore::.*ResourceLoader::addData/) {
$location_blame = "WebCore *ResourceLoader addData";
} elsif ($loc =~ m/OnUpdateVisitedLinks/) {
$location_blame = "OnUpdateVisitedLinks";
} elsif ($loc =~ m/IPC/) {
$location_blame = "ipc";
} elsif ($loc =~ m/trunk\\chrome\\browser/) {
$location_blame = "browser";
} elsif ($loc =~ m/trunk\\chrome\\renderer/) {
$location_blame = "renderer";
} elsif ($loc =~ m/webcore\\html/) {
$location_blame = "webkit webcore html";
} elsif ($loc =~ m/webkit.*string/) {
$location_blame = "webkit strings";
} elsif ($loc =~ m/htmltokenizer/) {
$location_blame = "webkit HTMLTokenizer";
} elsif ($loc =~ m/javascriptcore/) {
$location_blame = "webkit javascriptcore";
} elsif ($loc =~ m/webkit/) {
$location_blame = "webkit other";
} elsif ($loc =~ m/safe_browsing/) {
$location_blame = "safe_browsing";
} elsif ($loc =~ m/VisitedLinkMaster/) {
$location_blame = "VisitedLinkMaster";
} elsif ($loc =~ m/NewDOMUI/) {
$location_blame = "NewDOMUI";
} elsif ($loc =~ m/RegistryControlledDomainService/) {
$location_blame = "RegistryControlledDomainService";
} elsif ($loc =~ m/URLRequestChromeJob::DataAvailable/) {
$location_blame = "URLRequestChromeJob DataAvailable";
} else {
$location_blame = "unknown";
}
# Surface large outliers in an "interesting" group.
my $interesting_group = "unknown";
my $interesting_size = 10000000; # Make this smaller as needed.
# TODO(jar): Add this as a pair of shell arguments.
if ($bytes > $interesting_size && $location_blame eq $interesting_group) {
# Create a special group for the exact stack that contributed so much.
$location_blame = $loc;
}
$total_bytes += $bytes;
$leaks{$location_blame} += $bytes;
}
# now dump our hash table
my $sum = 0;
my @keys = sort { $leaks{$b} <=> $leaks{$a} }keys %leaks;
for ($i=0; $i<@keys; $i++) {
my $key = @keys[$i];
printf "%11s\t(%3.2f%%)\t%s\n", comma_print($leaks{$key}), (100* $leaks{$key} / $total_bytes), $key;
$sum += $leaks{$key};
}
printf("TOTAL: %s\n", comma_print($sum));
}
# Insert commas into an integer after each three digits for printing.
sub comma_print {
my $num = "$_[0]";
$num =~ s/(\d{1,3}?)(?=(\d{3})+$)/$1,/g;
return $num;
}
# ----- Main ------------------------------------------------
process_stdin();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment