stack_trace_win.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
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 "base/debug/stack_trace.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
17namespace base {
18namespace debug {
19
20namespace {
21
22// SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family
23// of functions.  The Sym* family of functions may only be invoked by one
24// thread at a time.  SymbolContext code may access a symbol server over the
25// network while holding the lock for this singleton.  In the case of high
26// latency, this code will adversly affect performance.
27//
28// There is also a known issue where this backtrace code can interact
29// badly with breakpad if breakpad is invoked in a separate thread while
30// we are using the Sym* functions.  This is because breakpad does now
31// share a lock with this function.  See this related bug:
32//
33//   http://code.google.com/p/google-breakpad/issues/detail?id=311
34//
35// This is a very unlikely edge case, and the current solution is to
36// just ignore it.
37class SymbolContext {
38 public:
39  static SymbolContext* GetInstance() {
40    // We use a leaky singleton because code may call this during process
41    // termination.
42    return
43      Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get();
44  }
45
46  // Returns the error code of a failed initialization.
47  DWORD init_error() const {
48    return init_error_;
49  }
50
51  // For the given trace, attempts to resolve the symbols, and output a trace
52  // to the ostream os.  The format for each line of the backtrace is:
53  //
54  //    <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
55  //
56  // This function should only be called if Init() has been called.  We do not
57  // LOG(FATAL) here because this code is called might be triggered by a
58  // LOG(FATAL) itself.
59  void OutputTraceToStream(const void* const* trace,
60                           int count,
61                           std::ostream* os) {
62    AutoLock lock(lock_);
63
64    for (size_t i = 0; (i < count) && os->good(); ++i) {
65      const int kMaxNameLength = 256;
66      DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
67
68      // Code adapted from MSDN example:
69      // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
70      ULONG64 buffer[
71        (sizeof(SYMBOL_INFO) +
72          kMaxNameLength * sizeof(wchar_t) +
73          sizeof(ULONG64) - 1) /
74        sizeof(ULONG64)];
75      memset(buffer, 0, sizeof(buffer));
76
77      // Initialize symbol information retrieval structures.
78      DWORD64 sym_displacement = 0;
79      PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
80      symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
81      symbol->MaxNameLen = kMaxNameLength - 1;
82      BOOL has_symbol = SymFromAddr(GetCurrentProcess(), frame,
83                                    &sym_displacement, symbol);
84
85      // Attempt to retrieve line number information.
86      DWORD line_displacement = 0;
87      IMAGEHLP_LINE64 line = {};
88      line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
89      BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
90                                           &line_displacement, &line);
91
92      // Output the backtrace line.
93      (*os) << "\t";
94      if (has_symbol) {
95        (*os) << symbol->Name << " [0x" << trace[i] << "+"
96              << sym_displacement << "]";
97      } else {
98        // If there is no symbol informtion, add a spacer.
99        (*os) << "(No symbol) [0x" << trace[i] << "]";
100      }
101      if (has_line) {
102        (*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
103      }
104      (*os) << "\n";
105    }
106  }
107
108 private:
109  friend struct DefaultSingletonTraits<SymbolContext>;
110
111  SymbolContext() : init_error_(ERROR_SUCCESS) {
112    // Initializes the symbols for the process.
113    // Defer symbol load until they're needed, use undecorated names, and
114    // get line numbers.
115    SymSetOptions(SYMOPT_DEFERRED_LOADS |
116                  SYMOPT_UNDNAME |
117                  SYMOPT_LOAD_LINES);
118    if (SymInitialize(GetCurrentProcess(), NULL, TRUE)) {
119      init_error_ = ERROR_SUCCESS;
120    } else {
121      init_error_ = GetLastError();
122      // TODO(awong): Handle error: SymInitialize can fail with
123      // ERROR_INVALID_PARAMETER.
124      // When it fails, we should not call debugbreak since it kills the current
125      // process (prevents future tests from running or kills the browser
126      // process).
127      DLOG(ERROR) << "SymInitialize failed: " << init_error_;
128    }
129  }
130
131  DWORD init_error_;
132  Lock lock_;
133  DISALLOW_COPY_AND_ASSIGN(SymbolContext);
134};
135
136}  // namespace
137
138StackTrace::StackTrace() {
139  // When walking our own stack, use CaptureStackBackTrace().
140  count_ = CaptureStackBackTrace(0, arraysize(trace_), trace_, NULL);
141}
142
143StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) {
144  // When walking an exception stack, we need to use StackWalk64().
145  count_ = 0;
146  // Initialize stack walking.
147  STACKFRAME64 stack_frame;
148  memset(&stack_frame, 0, sizeof(stack_frame));
149#if defined(_WIN64)
150  int machine_type = IMAGE_FILE_MACHINE_AMD64;
151  stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Rip;
152  stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Rbp;
153  stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Rsp;
154#else
155  int machine_type = IMAGE_FILE_MACHINE_I386;
156  stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Eip;
157  stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Ebp;
158  stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Esp;
159#endif
160  stack_frame.AddrPC.Mode = AddrModeFlat;
161  stack_frame.AddrFrame.Mode = AddrModeFlat;
162  stack_frame.AddrStack.Mode = AddrModeFlat;
163  while (StackWalk64(machine_type,
164                     GetCurrentProcess(),
165                     GetCurrentThread(),
166                     &stack_frame,
167                     exception_pointers->ContextRecord,
168                     NULL,
169                     &SymFunctionTableAccess64,
170                     &SymGetModuleBase64,
171                     NULL) &&
172         count_ < arraysize(trace_)) {
173    trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
174  }
175}
176
177void StackTrace::PrintBacktrace() {
178  OutputToStream(&std::cerr);
179}
180
181void StackTrace::OutputToStream(std::ostream* os) {
182  SymbolContext* context = SymbolContext::GetInstance();
183  DWORD error = context->init_error();
184  if (error != ERROR_SUCCESS) {
185    (*os) << "Error initializing symbols (" << error
186          << ").  Dumping unresolved backtrace:\n";
187    for (int i = 0; (i < count_) && os->good(); ++i) {
188      (*os) << "\t" << trace_[i] << "\n";
189    }
190  } else {
191    (*os) << "Backtrace:\n";
192    context->OutputTraceToStream(trace_, count_, os);
193  }
194}
195
196}  // namespace debug
197}  // namespace base
198