Home | History | Annotate | Download | only in base
      1 // Copyright (c) 2006-2009 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 "base/debug_util.h"
      6 
      7 #include <windows.h>
      8 #include <dbghelp.h>
      9 
     10 #include <iostream>
     11 
     12 #include "base/basictypes.h"
     13 #include "base/lock.h"
     14 #include "base/logging.h"
     15 #include "base/singleton.h"
     16 
     17 namespace {
     18 
     19 // Minimalist key reader.
     20 // Note: Does not use the CRT.
     21 bool RegReadString(HKEY root, const wchar_t* subkey,
     22                    const wchar_t* value_name, wchar_t* buffer, int* len) {
     23   HKEY key = NULL;
     24   DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key);
     25   if (ERROR_SUCCESS != res || key == NULL)
     26     return false;
     27 
     28   DWORD type = 0;
     29   DWORD buffer_size = *len * sizeof(wchar_t);
     30   // We don't support REG_EXPAND_SZ.
     31   res = RegQueryValueEx(key, value_name, NULL, &type,
     32                         reinterpret_cast<BYTE*>(buffer), &buffer_size);
     33   if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) {
     34     // Make sure the buffer is NULL terminated.
     35     buffer[*len - 1] = 0;
     36     *len = lstrlen(buffer);
     37     RegCloseKey(key);
     38     return true;
     39   }
     40   RegCloseKey(key);
     41   return false;
     42 }
     43 
     44 // Replaces each "%ld" in input per a value. Not efficient but it works.
     45 // Note: Does not use the CRT.
     46 bool StringReplace(const wchar_t* input, int value, wchar_t* output,
     47                    int output_len) {
     48   memset(output, 0, output_len*sizeof(wchar_t));
     49   int input_len = lstrlen(input);
     50 
     51   for (int i = 0; i < input_len; ++i) {
     52     int current_output_len = lstrlen(output);
     53 
     54     if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') {
     55       // Make sure we have enough place left.
     56       if ((current_output_len + 12) >= output_len)
     57         return false;
     58 
     59       // Cheap _itow().
     60       wsprintf(output+current_output_len, L"%d", value);
     61       i += 2;
     62     } else {
     63       if (current_output_len >= output_len)
     64         return false;
     65       output[current_output_len] = input[i];
     66     }
     67   }
     68   return true;
     69 }
     70 
     71 // SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family
     72 // of functions.  The Sym* family of functions may only be invoked by one
     73 // thread at a time.  SymbolContext code may access a symbol server over the
     74 // network while holding the lock for this singleton.  In the case of high
     75 // latency, this code will adversly affect performance.
     76 //
     77 // There is also a known issue where this backtrace code can interact
     78 // badly with breakpad if breakpad is invoked in a separate thread while
     79 // we are using the Sym* functions.  This is because breakpad does now
     80 // share a lock with this function.  See this related bug:
     81 //
     82 //   http://code.google.com/p/google-breakpad/issues/detail?id=311
     83 //
     84 // This is a very unlikely edge case, and the current solution is to
     85 // just ignore it.
     86 class SymbolContext {
     87  public:
     88   static SymbolContext* Get() {
     89     // We use a leaky singleton because code may call this during process
     90     // termination.
     91     return
     92       Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get();
     93   }
     94 
     95   // Returns the error code of a failed initialization.
     96   DWORD init_error() const {
     97     return init_error_;
     98   }
     99 
    100   // For the given trace, attempts to resolve the symbols, and output a trace
    101   // to the ostream os.  The format for each line of the backtrace is:
    102   //
    103   //    <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
    104   //
    105   // This function should only be called if Init() has been called.  We do not
    106   // LOG(FATAL) here because this code is called might be triggered by a
    107   // LOG(FATAL) itself.
    108   void OutputTraceToStream(const void* const* trace,
    109                            int count,
    110                            std::ostream* os) {
    111     AutoLock lock(lock_);
    112 
    113     for (size_t i = 0; (i < count) && os->good(); ++i) {
    114       const int kMaxNameLength = 256;
    115       DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
    116 
    117       // Code adapted from MSDN example:
    118       // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
    119       ULONG64 buffer[
    120         (sizeof(SYMBOL_INFO) +
    121           kMaxNameLength * sizeof(wchar_t) +
    122           sizeof(ULONG64) - 1) /
    123         sizeof(ULONG64)];
    124       memset(buffer, 0, sizeof(buffer));
    125 
    126       // Initialize symbol information retrieval structures.
    127       DWORD64 sym_displacement = 0;
    128       PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
    129       symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    130       symbol->MaxNameLen = kMaxNameLength - 1;
    131       BOOL has_symbol = SymFromAddr(GetCurrentProcess(), frame,
    132                                     &sym_displacement, symbol);
    133 
    134       // Attempt to retrieve line number information.
    135       DWORD line_displacement = 0;
    136       IMAGEHLP_LINE64 line = {};
    137       line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    138       BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
    139                                            &line_displacement, &line);
    140 
    141       // Output the backtrace line.
    142       (*os) << "\t";
    143       if (has_symbol) {
    144         (*os) << symbol->Name << " [0x" << trace[i] << "+"
    145               << sym_displacement << "]";
    146       } else {
    147         // If there is no symbol informtion, add a spacer.
    148         (*os) << "(No symbol) [0x" << trace[i] << "]";
    149       }
    150       if (has_line) {
    151         (*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
    152       }
    153       (*os) << "\n";
    154     }
    155   }
    156 
    157  private:
    158   friend struct DefaultSingletonTraits<SymbolContext>;
    159 
    160   SymbolContext() : init_error_(ERROR_SUCCESS) {
    161     // Initializes the symbols for the process.
    162     // Defer symbol load until they're needed, use undecorated names, and
    163     // get line numbers.
    164     SymSetOptions(SYMOPT_DEFERRED_LOADS |
    165                   SYMOPT_UNDNAME |
    166                   SYMOPT_LOAD_LINES);
    167     if (SymInitialize(GetCurrentProcess(), NULL, TRUE)) {
    168       init_error_ = ERROR_SUCCESS;
    169     } else {
    170       init_error_ = GetLastError();
    171       // TODO(awong): Handle error: SymInitialize can fail with
    172       // ERROR_INVALID_PARAMETER.
    173       // When it fails, we should not call debugbreak since it kills the current
    174       // process (prevents future tests from running or kills the browser
    175       // process).
    176       DLOG(ERROR) << "SymInitialize failed: " << init_error_;
    177     }
    178   }
    179 
    180   DWORD init_error_;
    181   Lock lock_;
    182   DISALLOW_COPY_AND_ASSIGN(SymbolContext);
    183 };
    184 
    185 }  // namespace
    186 
    187 // Note: Does not use the CRT.
    188 bool DebugUtil::SpawnDebuggerOnProcess(unsigned process_id) {
    189   wchar_t reg_value[1026];
    190   int len = arraysize(reg_value);
    191   if (RegReadString(HKEY_LOCAL_MACHINE,
    192       L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug",
    193       L"Debugger", reg_value, &len)) {
    194     wchar_t command_line[1026];
    195     if (StringReplace(reg_value, process_id, command_line,
    196                       arraysize(command_line))) {
    197       // We don't mind if the debugger is present because it will simply fail
    198       // to attach to this process.
    199       STARTUPINFO startup_info = {0};
    200       startup_info.cb = sizeof(startup_info);
    201       PROCESS_INFORMATION process_info = {0};
    202 
    203       if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL,
    204                         &startup_info, &process_info)) {
    205         CloseHandle(process_info.hThread);
    206         WaitForInputIdle(process_info.hProcess, 10000);
    207         CloseHandle(process_info.hProcess);
    208         return true;
    209       }
    210     }
    211   }
    212   return false;
    213 }
    214 
    215 // static
    216 bool DebugUtil::BeingDebugged() {
    217   return ::IsDebuggerPresent() != 0;
    218 }
    219 
    220 // static
    221 void DebugUtil::BreakDebugger() {
    222   __debugbreak();
    223 }
    224 
    225 StackTrace::StackTrace() {
    226   // When walking our own stack, use CaptureStackBackTrace().
    227   count_ = CaptureStackBackTrace(0, arraysize(trace_), trace_, NULL);
    228 }
    229 
    230 StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) {
    231   // When walking an exception stack, we need to use StackWalk64().
    232   count_ = 0;
    233   // Initialize stack walking.
    234   STACKFRAME64 stack_frame;
    235   memset(&stack_frame, 0, sizeof(stack_frame));
    236 #if defined(_WIN64)
    237   int machine_type = IMAGE_FILE_MACHINE_AMD64;
    238   stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Rip;
    239   stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Rbp;
    240   stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Rsp;
    241 #else
    242   int machine_type = IMAGE_FILE_MACHINE_I386;
    243   stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Eip;
    244   stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Ebp;
    245   stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Esp;
    246 #endif
    247   stack_frame.AddrPC.Mode = AddrModeFlat;
    248   stack_frame.AddrFrame.Mode = AddrModeFlat;
    249   stack_frame.AddrStack.Mode = AddrModeFlat;
    250   while (StackWalk64(machine_type,
    251                      GetCurrentProcess(),
    252                      GetCurrentThread(),
    253                      &stack_frame,
    254                      exception_pointers->ContextRecord,
    255                      NULL,
    256                      &SymFunctionTableAccess64,
    257                      &SymGetModuleBase64,
    258                      NULL) &&
    259          count_ < arraysize(trace_)) {
    260     trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
    261   }
    262 }
    263 
    264 void StackTrace::PrintBacktrace() {
    265   OutputToStream(&std::cerr);
    266 }
    267 
    268 void StackTrace::OutputToStream(std::ostream* os) {
    269   SymbolContext* context = SymbolContext::Get();
    270   DWORD error = context->init_error();
    271   if (error != ERROR_SUCCESS) {
    272     (*os) << "Error initializing symbols (" << error
    273           << ").  Dumping unresolved backtrace:\n";
    274     for (int i = 0; (i < count_) && os->good(); ++i) {
    275       (*os) << "\t" << trace_[i] << "\n";
    276     }
    277   } else {
    278     (*os) << "Backtrace:\n";
    279     context->OutputTraceToStream(trace_, count_, os);
    280   }
    281 }
    282