1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "tools/memory_watcher/call_stack.h" 6 7 #include <shlwapi.h> 8 #include <tlhelp32.h> 9 10 #include "base/strings/string_number_conversions.h" 11 #include "tools/memory_watcher/memory_hook.h" 12 13 // Typedefs for explicit dynamic linking with functions exported from 14 // dbghelp.dll. 15 typedef BOOL (__stdcall *t_StackWalk64)(DWORD, HANDLE, HANDLE, 16 LPSTACKFRAME64, PVOID, 17 PREAD_PROCESS_MEMORY_ROUTINE64, 18 PFUNCTION_TABLE_ACCESS_ROUTINE64, 19 PGET_MODULE_BASE_ROUTINE64, 20 PTRANSLATE_ADDRESS_ROUTINE64); 21 typedef PVOID (__stdcall *t_SymFunctionTableAccess64)(HANDLE, DWORD64); 22 typedef DWORD64 (__stdcall *t_SymGetModuleBase64)(HANDLE, DWORD64); 23 typedef BOOL (__stdcall *t_SymCleanup)(HANDLE); 24 typedef BOOL (__stdcall *t_SymGetSymFromAddr64)(HANDLE, DWORD64, 25 PDWORD64, PIMAGEHLP_SYMBOL64); 26 typedef BOOL (__stdcall *t_SymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, 27 PIMAGEHLP_LINE64); 28 typedef BOOL (__stdcall *t_SymInitialize)(HANDLE, PCTSTR, BOOL); 29 typedef DWORD (__stdcall *t_SymGetOptions)(void); 30 typedef DWORD (__stdcall *t_SymSetOptions)(DWORD); 31 typedef BOOL (__stdcall *t_SymGetSearchPath)(HANDLE, PTSTR, DWORD); 32 typedef DWORD64 (__stdcall *t_SymLoadModule64)(HANDLE, HANDLE, PCSTR, 33 PCSTR, DWORD64, DWORD); 34 typedef BOOL (__stdcall *t_SymGetModuleInfo64)(HANDLE, DWORD64, 35 PIMAGEHLP_MODULE64); 36 37 // static 38 base::Lock CallStack::dbghelp_lock_; 39 // static 40 bool CallStack::dbghelp_loaded_ = false; 41 // static 42 DWORD CallStack::active_thread_id_ = 0; 43 44 45 static t_StackWalk64 pStackWalk64 = NULL; 46 static t_SymCleanup pSymCleanup = NULL; 47 static t_SymGetSymFromAddr64 pSymGetSymFromAddr64 = NULL; 48 static t_SymFunctionTableAccess64 pSymFunctionTableAccess64 = NULL; 49 static t_SymGetModuleBase64 pSymGetModuleBase64 = NULL; 50 static t_SymGetLineFromAddr64 pSymGetLineFromAddr64 = NULL; 51 static t_SymInitialize pSymInitialize = NULL; 52 static t_SymGetOptions pSymGetOptions = NULL; 53 static t_SymSetOptions pSymSetOptions = NULL; 54 static t_SymGetModuleInfo64 pSymGetModuleInfo64 = NULL; 55 static t_SymGetSearchPath pSymGetSearchPath = NULL; 56 static t_SymLoadModule64 pSymLoadModule64 = NULL; 57 58 #define LOADPROC(module, name) do { \ 59 p##name = reinterpret_cast<t_##name>(GetProcAddress(module, #name)); \ 60 if (p##name == NULL) return false; \ 61 } while (0) 62 63 // This code has to be VERY careful to not induce any allocations, as memory 64 // watching code may cause recursion, which may obscure the stack for the truly 65 // offensive issue. We use this function to break into a debugger, and it 66 // is guaranteed to not do any allocations (in fact, not do anything). 67 static void UltraSafeDebugBreak() { 68 _asm int(3); 69 } 70 71 // static 72 bool CallStack::LoadDbgHelp() { 73 if (!dbghelp_loaded_) { 74 base::AutoLock Lock(dbghelp_lock_); 75 76 // Re-check if we've loaded successfully now that we have the lock. 77 if (dbghelp_loaded_) 78 return true; 79 80 // Load dbghelp.dll, and obtain pointers to the exported functions that we 81 // will be using. 82 HMODULE dbghelp_module = LoadLibrary(L"dbghelp.dll"); 83 if (dbghelp_module) { 84 LOADPROC(dbghelp_module, StackWalk64); 85 LOADPROC(dbghelp_module, SymFunctionTableAccess64); 86 LOADPROC(dbghelp_module, SymGetModuleBase64); 87 LOADPROC(dbghelp_module, SymCleanup); 88 LOADPROC(dbghelp_module, SymGetSymFromAddr64); 89 LOADPROC(dbghelp_module, SymGetLineFromAddr64); 90 LOADPROC(dbghelp_module, SymInitialize); 91 LOADPROC(dbghelp_module, SymGetOptions); 92 LOADPROC(dbghelp_module, SymSetOptions); 93 LOADPROC(dbghelp_module, SymGetModuleInfo64); 94 LOADPROC(dbghelp_module, SymGetSearchPath); 95 LOADPROC(dbghelp_module, SymLoadModule64); 96 dbghelp_loaded_ = true; 97 } else { 98 UltraSafeDebugBreak(); 99 return false; 100 } 101 } 102 return dbghelp_loaded_; 103 } 104 105 // Load the symbols for generating stack traces. 106 static bool LoadSymbols(HANDLE process_handle) { 107 static bool symbols_loaded = false; 108 if (symbols_loaded) return true; 109 110 BOOL ok; 111 112 // Initialize the symbol engine. 113 ok = pSymInitialize(process_handle, /* hProcess */ 114 NULL, /* UserSearchPath */ 115 FALSE); /* fInvadeProcess */ 116 if (!ok) return false; 117 118 DWORD options = pSymGetOptions(); 119 options |= SYMOPT_LOAD_LINES; 120 options |= SYMOPT_FAIL_CRITICAL_ERRORS; 121 options |= SYMOPT_UNDNAME; 122 options = pSymSetOptions(options); 123 124 const DWORD kMaxSearchPath = 1024; 125 TCHAR buf[kMaxSearchPath] = {0}; 126 ok = pSymGetSearchPath(process_handle, buf, kMaxSearchPath); 127 if (!ok) 128 return false; 129 130 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 131 GetCurrentProcessId()); 132 if (snapshot == INVALID_HANDLE_VALUE) 133 return false; 134 135 MODULEENTRY32W module; 136 module.dwSize = sizeof(module); // Set the size of the structure. 137 BOOL cont = Module32FirstW(snapshot, &module); 138 while (cont) { 139 DWORD64 base; 140 // NOTE the SymLoadModule64 function has the peculiarity of accepting a 141 // both unicode and ASCII strings even though the parameter is PSTR. 142 base = pSymLoadModule64(process_handle, 143 0, 144 reinterpret_cast<PSTR>(module.szExePath), 145 reinterpret_cast<PSTR>(module.szModule), 146 reinterpret_cast<DWORD64>(module.modBaseAddr), 147 module.modBaseSize); 148 if (base == 0) { 149 int err = GetLastError(); 150 if (err != ERROR_MOD_NOT_FOUND && err != ERROR_INVALID_HANDLE) 151 return false; 152 } 153 cont = Module32NextW(snapshot, &module); 154 } 155 CloseHandle(snapshot); 156 157 symbols_loaded = true; 158 return true; 159 } 160 161 162 CallStack::SymbolCache* CallStack::symbol_cache_; 163 164 bool CallStack::Initialize() { 165 // We need to delay load the symbol cache until after 166 // the MemoryHook heap is alive. 167 symbol_cache_ = new SymbolCache(); 168 return LoadDbgHelp(); 169 } 170 171 CallStack::CallStack() { 172 static LONG callstack_id = 0; 173 frame_count_ = 0; 174 hash_ = 0; 175 id_ = InterlockedIncrement(&callstack_id); 176 valid_ = false; 177 178 if (!dbghelp_loaded_) { 179 UltraSafeDebugBreak(); // Initialize should have been called. 180 return; 181 } 182 183 GetStackTrace(); 184 } 185 186 bool CallStack::IsEqual(const CallStack &target) { 187 if (frame_count_ != target.frame_count_) 188 return false; // They can't be equal if the sizes are different. 189 190 // Walk the frames array until we 191 // either find a mismatch, or until we reach the end of the call stacks. 192 for (int index = 0; index < frame_count_; index++) { 193 if (frames_[index] != target.frames_[index]) 194 return false; // Found a mismatch. They are not equal. 195 } 196 197 // Reached the end of the call stacks. They are equal. 198 return true; 199 } 200 201 void CallStack::AddFrame(DWORD_PTR pc) { 202 DCHECK(frame_count_ < kMaxTraceFrames); 203 frames_[frame_count_++] = pc; 204 205 // Create a unique id for this CallStack. 206 pc = pc + (frame_count_ * 13); // Alter the PC based on position in stack. 207 hash_ = ~hash_ + (pc << 15); 208 hash_ = hash_ ^ (pc >> 12); 209 hash_ = hash_ + (pc << 2); 210 hash_ = hash_ ^ (pc >> 4); 211 hash_ = hash_ * 2057; 212 hash_ = hash_ ^ (pc >> 16); 213 } 214 215 bool CallStack::LockedRecursionDetected() const { 216 if (!active_thread_id_) return false; 217 DWORD thread_id = GetCurrentThreadId(); 218 // TODO(jar): Perchance we should use atomic access to member. 219 return thread_id == active_thread_id_; 220 } 221 222 bool CallStack::GetStackTrace() { 223 if (LockedRecursionDetected()) 224 return false; 225 226 // Initialize the context record. 227 CONTEXT context; 228 memset(&context, 0, sizeof(context)); 229 context.ContextFlags = CONTEXT_FULL; 230 __asm call x 231 __asm x: pop eax 232 __asm mov context.Eip, eax 233 __asm mov context.Ebp, ebp 234 __asm mov context.Esp, esp 235 236 STACKFRAME64 frame; 237 memset(&frame, 0, sizeof(frame)); 238 239 #ifdef _M_IX86 240 DWORD image_type = IMAGE_FILE_MACHINE_I386; 241 frame.AddrPC.Offset = context.Eip; 242 frame.AddrPC.Mode = AddrModeFlat; 243 frame.AddrFrame.Offset = context.Ebp; 244 frame.AddrFrame.Mode = AddrModeFlat; 245 frame.AddrStack.Offset = context.Esp; 246 frame.AddrStack.Mode = AddrModeFlat; 247 #elif 248 NOT IMPLEMENTED! 249 #endif 250 251 HANDLE current_process = GetCurrentProcess(); 252 HANDLE current_thread = GetCurrentThread(); 253 254 // Walk the stack. 255 unsigned int count = 0; 256 { 257 AutoDbgHelpLock thread_monitoring_lock; 258 259 while (count < kMaxTraceFrames) { 260 count++; 261 if (!pStackWalk64(image_type, 262 current_process, 263 current_thread, 264 &frame, 265 &context, 266 0, 267 pSymFunctionTableAccess64, 268 pSymGetModuleBase64, 269 NULL)) 270 break; // Couldn't trace back through any more frames. 271 272 if (frame.AddrFrame.Offset == 0) 273 continue; // End of stack. 274 275 // Push this frame's program counter onto the provided CallStack. 276 AddFrame((DWORD_PTR)frame.AddrPC.Offset); 277 } 278 valid_ = true; 279 } 280 return true; 281 } 282 283 void CallStack::ToString(PrivateAllocatorString* output) { 284 static const int kStackWalkMaxNameLen = MAX_SYM_NAME; 285 HANDLE current_process = GetCurrentProcess(); 286 287 if (!LoadSymbols(current_process)) { 288 *output = "Error"; 289 return; 290 } 291 292 base::AutoLock lock(dbghelp_lock_); 293 294 // Iterate through each frame in the call stack. 295 for (int32 index = 0; index < frame_count_; index++) { 296 PrivateAllocatorString line; 297 298 DWORD_PTR intruction_pointer = frame(index); 299 300 SymbolCache::iterator it; 301 it = symbol_cache_->find(intruction_pointer); 302 if (it != symbol_cache_->end()) { 303 line = it->second; 304 } else { 305 // Try to locate a symbol for this frame. 306 DWORD64 symbol_displacement = 0; 307 ULONG64 buffer[(sizeof(IMAGEHLP_SYMBOL64) + 308 sizeof(TCHAR)*kStackWalkMaxNameLen + 309 sizeof(ULONG64) - 1) / sizeof(ULONG64)]; 310 IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(buffer); 311 memset(buffer, 0, sizeof(buffer)); 312 symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); 313 symbol->MaxNameLength = kStackWalkMaxNameLen; 314 BOOL ok = pSymGetSymFromAddr64(current_process, // hProcess 315 intruction_pointer, // Address 316 &symbol_displacement, // Displacement 317 symbol); // Symbol 318 if (ok) { 319 // Try to locate more source information for the symbol. 320 IMAGEHLP_LINE64 Line; 321 memset(&Line, 0, sizeof(Line)); 322 Line.SizeOfStruct = sizeof(Line); 323 DWORD line_displacement; 324 ok = pSymGetLineFromAddr64(current_process, 325 intruction_pointer, 326 &line_displacement, 327 &Line); 328 if (ok) { 329 // Skip junk symbols from our internal stuff. 330 if (strstr(symbol->Name, "CallStack::") || 331 strstr(symbol->Name, "MemoryWatcher::") || 332 strstr(symbol->Name, "Perftools_") || 333 strstr(symbol->Name, "MemoryHook::") ) { 334 // Just record a blank string. 335 (*symbol_cache_)[intruction_pointer] = ""; 336 continue; 337 } 338 339 line += " "; 340 line += static_cast<char*>(Line.FileName); 341 line += " ("; 342 // TODO(jar): get something like this template to work :-/ 343 // line += IntToCustomString<PrivateAllocatorString>(Line.LineNumber); 344 // ...and then delete this line, which uses std::string. 345 line += base::IntToString(Line.LineNumber).c_str(); 346 line += "): "; 347 line += symbol->Name; 348 line += "\n"; 349 } else { 350 line += " unknown (0):"; 351 line += symbol->Name; 352 line += "\n"; 353 } 354 } else { 355 // OK - couldn't get any info. Try for the module. 356 IMAGEHLP_MODULE64 module_info; 357 module_info.SizeOfStruct = sizeof(module_info); 358 if (pSymGetModuleInfo64(current_process, intruction_pointer, 359 &module_info)) { 360 line += " ("; 361 line += static_cast<char*>(module_info.ModuleName); 362 line += ")\n"; 363 } else { 364 line += " ???\n"; 365 } 366 } 367 } 368 369 (*symbol_cache_)[intruction_pointer] = line; 370 *output += line; 371 } 372 *output += "==================\n"; 373 } 374 375 376 base::Lock AllocationStack::freelist_lock_; 377 AllocationStack* AllocationStack::freelist_ = NULL; 378 379 void* AllocationStack::operator new(size_t size) { 380 DCHECK(size == sizeof(AllocationStack)); 381 { 382 base::AutoLock lock(freelist_lock_); 383 if (freelist_ != NULL) { 384 AllocationStack* stack = freelist_; 385 freelist_ = freelist_->next_; 386 stack->next_ = NULL; 387 return stack; 388 } 389 } 390 return MemoryHook::Alloc(size); 391 } 392 393 void AllocationStack::operator delete(void* ptr) { 394 AllocationStack *stack = reinterpret_cast<AllocationStack*>(ptr); 395 base::AutoLock lock(freelist_lock_); 396 DCHECK(stack->next_ == NULL); 397 stack->next_ = freelist_; 398 freelist_ = stack; 399 } 400