// 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 "tools/memory_watcher/call_stack.h"
#include <shlwapi.h>
#include <tlhelp32.h>
#include "base/strings/string_number_conversions.h"
#include "tools/memory_watcher/memory_hook.h"
// Typedefs for explicit dynamic linking with functions exported from
// dbghelp.dll.
typedef BOOL (__stdcall *t_StackWalk64)(DWORD, HANDLE, HANDLE,
LPSTACKFRAME64, PVOID,
PREAD_PROCESS_MEMORY_ROUTINE64,
PFUNCTION_TABLE_ACCESS_ROUTINE64,
PGET_MODULE_BASE_ROUTINE64,
PTRANSLATE_ADDRESS_ROUTINE64);
typedef PVOID (__stdcall *t_SymFunctionTableAccess64)(HANDLE, DWORD64);
typedef DWORD64 (__stdcall *t_SymGetModuleBase64)(HANDLE, DWORD64);
typedef BOOL (__stdcall *t_SymCleanup)(HANDLE);
typedef BOOL (__stdcall *t_SymGetSymFromAddr64)(HANDLE, DWORD64,
PDWORD64, PIMAGEHLP_SYMBOL64);
typedef BOOL (__stdcall *t_SymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD,
PIMAGEHLP_LINE64);
typedef BOOL (__stdcall *t_SymInitialize)(HANDLE, PCTSTR, BOOL);
typedef DWORD (__stdcall *t_SymGetOptions)(void);
typedef DWORD (__stdcall *t_SymSetOptions)(DWORD);
typedef BOOL (__stdcall *t_SymGetSearchPath)(HANDLE, PTSTR, DWORD);
typedef DWORD64 (__stdcall *t_SymLoadModule64)(HANDLE, HANDLE, PCSTR,
PCSTR, DWORD64, DWORD);
typedef BOOL (__stdcall *t_SymGetModuleInfo64)(HANDLE, DWORD64,
PIMAGEHLP_MODULE64);
// static
base::Lock CallStack::dbghelp_lock_;
// static
bool CallStack::dbghelp_loaded_ = false;
// static
DWORD CallStack::active_thread_id_ = 0;
static t_StackWalk64 pStackWalk64 = NULL;
static t_SymCleanup pSymCleanup = NULL;
static t_SymGetSymFromAddr64 pSymGetSymFromAddr64 = NULL;
static t_SymFunctionTableAccess64 pSymFunctionTableAccess64 = NULL;
static t_SymGetModuleBase64 pSymGetModuleBase64 = NULL;
static t_SymGetLineFromAddr64 pSymGetLineFromAddr64 = NULL;
static t_SymInitialize pSymInitialize = NULL;
static t_SymGetOptions pSymGetOptions = NULL;
static t_SymSetOptions pSymSetOptions = NULL;
static t_SymGetModuleInfo64 pSymGetModuleInfo64 = NULL;
static t_SymGetSearchPath pSymGetSearchPath = NULL;
static t_SymLoadModule64 pSymLoadModule64 = NULL;
#define LOADPROC(module, name) do { \
p##name = reinterpret_cast<t_##name>(GetProcAddress(module, #name)); \
if (p##name == NULL) return false; \
} while (0)
// This code has to be VERY careful to not induce any allocations, as memory
// watching code may cause recursion, which may obscure the stack for the truly
// offensive issue. We use this function to break into a debugger, and it
// is guaranteed to not do any allocations (in fact, not do anything).
static void UltraSafeDebugBreak() {
_asm int(3);
}
// static
bool CallStack::LoadDbgHelp() {
if (!dbghelp_loaded_) {
base::AutoLock Lock(dbghelp_lock_);
// Re-check if we've loaded successfully now that we have the lock.
if (dbghelp_loaded_)
return true;
// Load dbghelp.dll, and obtain pointers to the exported functions that we
// will be using.
HMODULE dbghelp_module = LoadLibrary(L"dbghelp.dll");
if (dbghelp_module) {
LOADPROC(dbghelp_module, StackWalk64);
LOADPROC(dbghelp_module, SymFunctionTableAccess64);
LOADPROC(dbghelp_module, SymGetModuleBase64);
LOADPROC(dbghelp_module, SymCleanup);
LOADPROC(dbghelp_module, SymGetSymFromAddr64);
LOADPROC(dbghelp_module, SymGetLineFromAddr64);
LOADPROC(dbghelp_module, SymInitialize);
LOADPROC(dbghelp_module, SymGetOptions);
LOADPROC(dbghelp_module, SymSetOptions);
LOADPROC(dbghelp_module, SymGetModuleInfo64);
LOADPROC(dbghelp_module, SymGetSearchPath);
LOADPROC(dbghelp_module, SymLoadModule64);
dbghelp_loaded_ = true;
} else {
UltraSafeDebugBreak();
return false;
}
}
return dbghelp_loaded_;
}
// Load the symbols for generating stack traces.
static bool LoadSymbols(HANDLE process_handle) {
static bool symbols_loaded = false;
if (symbols_loaded) return true;
BOOL ok;
// Initialize the symbol engine.
ok = pSymInitialize(process_handle, /* hProcess */
NULL, /* UserSearchPath */
FALSE); /* fInvadeProcess */
if (!ok) return false;
DWORD options = pSymGetOptions();
options |= SYMOPT_LOAD_LINES;
options |= SYMOPT_FAIL_CRITICAL_ERRORS;
options |= SYMOPT_UNDNAME;
options = pSymSetOptions(options);
const DWORD kMaxSearchPath = 1024;
TCHAR buf[kMaxSearchPath] = {0};
ok = pSymGetSearchPath(process_handle, buf, kMaxSearchPath);
if (!ok)
return false;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
GetCurrentProcessId());
if (snapshot == INVALID_HANDLE_VALUE)
return false;
MODULEENTRY32W module;
module.dwSize = sizeof(module); // Set the size of the structure.
BOOL cont = Module32FirstW(snapshot, &module);
while (cont) {
DWORD64 base;
// NOTE the SymLoadModule64 function has the peculiarity of accepting a
// both unicode and ASCII strings even though the parameter is PSTR.
base = pSymLoadModule64(process_handle,
0,
reinterpret_cast<PSTR>(module.szExePath),
reinterpret_cast<PSTR>(module.szModule),
reinterpret_cast<DWORD64>(module.modBaseAddr),
module.modBaseSize);
if (base == 0) {
int err = GetLastError();
if (err != ERROR_MOD_NOT_FOUND && err != ERROR_INVALID_HANDLE)
return false;
}
cont = Module32NextW(snapshot, &module);
}
CloseHandle(snapshot);
symbols_loaded = true;
return true;
}
CallStack::SymbolCache* CallStack::symbol_cache_;
bool CallStack::Initialize() {
// We need to delay load the symbol cache until after
// the MemoryHook heap is alive.
symbol_cache_ = new SymbolCache();
return LoadDbgHelp();
}
CallStack::CallStack() {
static LONG callstack_id = 0;
frame_count_ = 0;
hash_ = 0;
id_ = InterlockedIncrement(&callstack_id);
valid_ = false;
if (!dbghelp_loaded_) {
UltraSafeDebugBreak(); // Initialize should have been called.
return;
}
GetStackTrace();
}
bool CallStack::IsEqual(const CallStack &target) {
if (frame_count_ != target.frame_count_)
return false; // They can't be equal if the sizes are different.
// Walk the frames array until we
// either find a mismatch, or until we reach the end of the call stacks.
for (int index = 0; index < frame_count_; index++) {
if (frames_[index] != target.frames_[index])
return false; // Found a mismatch. They are not equal.
}
// Reached the end of the call stacks. They are equal.
return true;
}
void CallStack::AddFrame(DWORD_PTR pc) {
DCHECK(frame_count_ < kMaxTraceFrames);
frames_[frame_count_++] = pc;
// Create a unique id for this CallStack.
pc = pc + (frame_count_ * 13); // Alter the PC based on position in stack.
hash_ = ~hash_ + (pc << 15);
hash_ = hash_ ^ (pc >> 12);
hash_ = hash_ + (pc << 2);
hash_ = hash_ ^ (pc >> 4);
hash_ = hash_ * 2057;
hash_ = hash_ ^ (pc >> 16);
}
bool CallStack::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_;
}
bool CallStack::GetStackTrace() {
if (LockedRecursionDetected())
return false;
// Initialize the context record.
CONTEXT context;
memset(&context, 0, sizeof(context));
context.ContextFlags = CONTEXT_FULL;
__asm call x
__asm x: pop eax
__asm mov context.Eip, eax
__asm mov context.Ebp, ebp
__asm mov context.Esp, esp
STACKFRAME64 frame;
memset(&frame, 0, sizeof(frame));
#ifdef _M_IX86
DWORD image_type = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context.Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Esp;
frame.AddrStack.Mode = AddrModeFlat;
#elif
NOT IMPLEMENTED!
#endif
HANDLE current_process = GetCurrentProcess();
HANDLE current_thread = GetCurrentThread();
// Walk the stack.
unsigned int count = 0;
{
AutoDbgHelpLock thread_monitoring_lock;
while (count < kMaxTraceFrames) {
count++;
if (!pStackWalk64(image_type,
current_process,
current_thread,
&frame,
&context,
0,
pSymFunctionTableAccess64,
pSymGetModuleBase64,
NULL))
break; // Couldn't trace back through any more frames.
if (frame.AddrFrame.Offset == 0)
continue; // End of stack.
// Push this frame's program counter onto the provided CallStack.
AddFrame((DWORD_PTR)frame.AddrPC.Offset);
}
valid_ = true;
}
return true;
}
void CallStack::ToString(PrivateAllocatorString* output) {
static const int kStackWalkMaxNameLen = MAX_SYM_NAME;
HANDLE current_process = GetCurrentProcess();
if (!LoadSymbols(current_process)) {
*output = "Error";
return;
}
base::AutoLock lock(dbghelp_lock_);
// Iterate through each frame in the call stack.
for (int32 index = 0; index < frame_count_; index++) {
PrivateAllocatorString line;
DWORD_PTR intruction_pointer = frame(index);
SymbolCache::iterator it;
it = symbol_cache_->find(intruction_pointer);
if (it != symbol_cache_->end()) {
line = it->second;
} else {
// Try to locate a symbol for this frame.
DWORD64 symbol_displacement = 0;
ULONG64 buffer[(sizeof(IMAGEHLP_SYMBOL64) +
sizeof(TCHAR)*kStackWalkMaxNameLen +
sizeof(ULONG64) - 1) / sizeof(ULONG64)];
IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(buffer);
memset(buffer, 0, sizeof(buffer));
symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
symbol->MaxNameLength = kStackWalkMaxNameLen;
BOOL ok = pSymGetSymFromAddr64(current_process, // hProcess
intruction_pointer, // Address
&symbol_displacement, // Displacement
symbol); // Symbol
if (ok) {
// Try to locate more source information for the symbol.
IMAGEHLP_LINE64 Line;
memset(&Line, 0, sizeof(Line));
Line.SizeOfStruct = sizeof(Line);
DWORD line_displacement;
ok = pSymGetLineFromAddr64(current_process,
intruction_pointer,
&line_displacement,
&Line);
if (ok) {
// Skip junk symbols from our internal stuff.
if (strstr(symbol->Name, "CallStack::") ||
strstr(symbol->Name, "MemoryWatcher::") ||
strstr(symbol->Name, "Perftools_") ||
strstr(symbol->Name, "MemoryHook::") ) {
// Just record a blank string.
(*symbol_cache_)[intruction_pointer] = "";
continue;
}
line += " ";
line += static_cast<char*>(Line.FileName);
line += " (";
// TODO(jar): get something like this template to work :-/
// line += IntToCustomString<PrivateAllocatorString>(Line.LineNumber);
// ...and then delete this line, which uses std::string.
line += base::IntToString(Line.LineNumber).c_str();
line += "): ";
line += symbol->Name;
line += "\n";
} else {
line += " unknown (0):";
line += symbol->Name;
line += "\n";
}
} else {
// OK - couldn't get any info. Try for the module.
IMAGEHLP_MODULE64 module_info;
module_info.SizeOfStruct = sizeof(module_info);
if (pSymGetModuleInfo64(current_process, intruction_pointer,
&module_info)) {
line += " (";
line += static_cast<char*>(module_info.ModuleName);
line += ")\n";
} else {
line += " ???\n";
}
}
}
(*symbol_cache_)[intruction_pointer] = line;
*output += line;
}
*output += "==================\n";
}
base::Lock AllocationStack::freelist_lock_;
AllocationStack* AllocationStack::freelist_ = NULL;
void* AllocationStack::operator new(size_t size) {
DCHECK(size == sizeof(AllocationStack));
{
base::AutoLock lock(freelist_lock_);
if (freelist_ != NULL) {
AllocationStack* stack = freelist_;
freelist_ = freelist_->next_;
stack->next_ = NULL;
return stack;
}
}
return MemoryHook::Alloc(size);
}
void AllocationStack::operator delete(void* ptr) {
AllocationStack *stack = reinterpret_cast<AllocationStack*>(ptr);
base::AutoLock lock(freelist_lock_);
DCHECK(stack->next_ == NULL);
stack->next_ = freelist_;
freelist_ = stack;
}