// 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 #include #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(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(module.szExePath), reinterpret_cast(module.szModule), reinterpret_cast(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(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(Line.FileName); line += " ("; // TODO(jar): get something like this template to work :-/ // line += IntToCustomString(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(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(ptr); base::AutoLock lock(freelist_lock_); DCHECK(stack->next_ == NULL); stack->next_ = freelist_; freelist_ = stack; }