Home | History | Annotate | Download | only in memory_watcher
      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