• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
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 
9 #include <dbghelp.h>
10 #include <stddef.h>
11 
12 #include <algorithm>
13 #include <iostream>
14 #include <iterator>
15 #include <memory>
16 
17 #include "base/files/file_path.h"
18 #include "base/logging.h"
19 #include "base/memory/singleton.h"
20 #include "base/strings/strcat_win.h"
21 #include "base/strings/string_util.h"
22 #include "base/synchronization/lock.h"
23 #include "build/build_config.h"
24 
25 namespace base {
26 namespace debug {
27 
28 namespace {
29 
30 // Previous unhandled filter. Will be called if not NULL when we intercept an
31 // exception. Only used in unit tests.
32 LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL;
33 
34 bool g_initialized_symbols = false;
35 DWORD g_init_error = ERROR_SUCCESS;
36 // STATUS_INFO_LENGTH_MISMATCH is declared in <ntstatus.h>, but including that
37 // header creates a conflict with base/win/windows_types.h, so re-declaring it
38 // here.
39 DWORD g_status_info_length_mismatch = 0xC0000004;
40 
41 // Prints the exception call stack.
42 // This is the unit tests exception filter.
StackDumpExceptionFilter(EXCEPTION_POINTERS * info)43 long WINAPI StackDumpExceptionFilter(EXCEPTION_POINTERS* info) {
44   DWORD exc_code = info->ExceptionRecord->ExceptionCode;
45   std::cerr << "Received fatal exception ";
46   switch (exc_code) {
47     case EXCEPTION_ACCESS_VIOLATION:
48       std::cerr << "EXCEPTION_ACCESS_VIOLATION";
49       break;
50     case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
51       std::cerr << "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
52       break;
53     case EXCEPTION_BREAKPOINT:
54       std::cerr << "EXCEPTION_BREAKPOINT";
55       break;
56     case EXCEPTION_DATATYPE_MISALIGNMENT:
57       std::cerr << "EXCEPTION_DATATYPE_MISALIGNMENT";
58       break;
59     case EXCEPTION_FLT_DENORMAL_OPERAND:
60       std::cerr << "EXCEPTION_FLT_DENORMAL_OPERAND";
61       break;
62     case EXCEPTION_FLT_DIVIDE_BY_ZERO:
63       std::cerr << "EXCEPTION_FLT_DIVIDE_BY_ZERO";
64       break;
65     case EXCEPTION_FLT_INEXACT_RESULT:
66       std::cerr << "EXCEPTION_FLT_INEXACT_RESULT";
67       break;
68     case EXCEPTION_FLT_INVALID_OPERATION:
69       std::cerr << "EXCEPTION_FLT_INVALID_OPERATION";
70       break;
71     case EXCEPTION_FLT_OVERFLOW:
72       std::cerr << "EXCEPTION_FLT_OVERFLOW";
73       break;
74     case EXCEPTION_FLT_STACK_CHECK:
75       std::cerr << "EXCEPTION_FLT_STACK_CHECK";
76       break;
77     case EXCEPTION_FLT_UNDERFLOW:
78       std::cerr << "EXCEPTION_FLT_UNDERFLOW";
79       break;
80     case EXCEPTION_ILLEGAL_INSTRUCTION:
81       std::cerr << "EXCEPTION_ILLEGAL_INSTRUCTION";
82       break;
83     case EXCEPTION_IN_PAGE_ERROR:
84       std::cerr << "EXCEPTION_IN_PAGE_ERROR";
85       break;
86     case EXCEPTION_INT_DIVIDE_BY_ZERO:
87       std::cerr << "EXCEPTION_INT_DIVIDE_BY_ZERO";
88       break;
89     case EXCEPTION_INT_OVERFLOW:
90       std::cerr << "EXCEPTION_INT_OVERFLOW";
91       break;
92     case EXCEPTION_INVALID_DISPOSITION:
93       std::cerr << "EXCEPTION_INVALID_DISPOSITION";
94       break;
95     case EXCEPTION_NONCONTINUABLE_EXCEPTION:
96       std::cerr << "EXCEPTION_NONCONTINUABLE_EXCEPTION";
97       break;
98     case EXCEPTION_PRIV_INSTRUCTION:
99       std::cerr << "EXCEPTION_PRIV_INSTRUCTION";
100       break;
101     case EXCEPTION_SINGLE_STEP:
102       std::cerr << "EXCEPTION_SINGLE_STEP";
103       break;
104     case EXCEPTION_STACK_OVERFLOW:
105       std::cerr << "EXCEPTION_STACK_OVERFLOW";
106       break;
107     default:
108       std::cerr << "0x" << std::hex << exc_code;
109       break;
110   }
111   std::cerr << "\n";
112 
113   debug::StackTrace(info).Print();
114   if (g_previous_filter)
115     return g_previous_filter(info);
116   return EXCEPTION_CONTINUE_SEARCH;
117 }
118 
GetExePath()119 FilePath GetExePath() {
120   wchar_t system_buffer[MAX_PATH];
121   GetModuleFileName(NULL, system_buffer, MAX_PATH);
122   system_buffer[MAX_PATH - 1] = L'\0';
123   return FilePath(system_buffer);
124 }
125 
126 constexpr size_t kSymInitializeRetryCount = 3;
127 
128 // A wrapper for SymInitialize. SymInitialize seems to occasionally fail
129 // because of an internal race condition. So  wrap it and retry a finite
130 // number of times.
131 // See crbug.com/1339753
SymInitializeWrapper(HANDLE handle,BOOL invade_process)132 bool SymInitializeWrapper(HANDLE handle, BOOL invade_process) {
133   for (size_t i = 0; i < kSymInitializeRetryCount; ++i) {
134     if (SymInitialize(handle, nullptr, invade_process))
135       return true;
136 
137     g_init_error = GetLastError();
138     if (g_init_error != g_status_info_length_mismatch)
139       return false;
140   }
141   DLOG(ERROR) << "SymInitialize failed repeatedly.";
142   return false;
143 }
144 
SymInitializeCurrentProc()145 bool SymInitializeCurrentProc() {
146   const HANDLE current_process = GetCurrentProcess();
147   if (SymInitializeWrapper(current_process, TRUE))
148     return true;
149 
150   // g_init_error is updated by SymInitializeWrapper.
151   // No need to do "g_init_error = GetLastError()" here.
152   if (g_init_error != ERROR_INVALID_PARAMETER)
153     return false;
154 
155   // SymInitialize() can fail with ERROR_INVALID_PARAMETER when something has
156   // already called SymInitialize() in this process. For example, when absl
157   // support for gtest is enabled, it results in absl calling SymInitialize()
158   // almost immediately after startup. In such a case, try to reinit to see if
159   // that succeeds.
160   SymCleanup(current_process);
161   if (SymInitializeWrapper(current_process, TRUE))
162     return true;
163 
164   return false;
165 }
166 
InitializeSymbols()167 bool InitializeSymbols() {
168   if (g_initialized_symbols) {
169     // Force a reinitialization. Will ensure any modules loaded after process
170     // startup also get symbolized.
171     SymCleanup(GetCurrentProcess());
172     g_initialized_symbols = false;
173   }
174   g_initialized_symbols = true;
175   // Defer symbol load until they're needed, use undecorated names, and get line
176   // numbers.
177   SymSetOptions(SYMOPT_DEFERRED_LOADS |
178                 SYMOPT_UNDNAME |
179                 SYMOPT_LOAD_LINES);
180   if (!SymInitializeCurrentProc()) {
181     // When it fails, we should not call debugbreak since it kills the current
182     // process (prevents future tests from running or kills the browser
183     // process).
184     DLOG(ERROR) << "SymInitialize failed: " << g_init_error;
185     return false;
186   }
187 
188   // When transferring the binaries e.g. between bots, path put
189   // into the executable will get off. To still retrieve symbols correctly,
190   // add the directory of the executable to symbol search path.
191   // All following errors are non-fatal.
192   static constexpr size_t kSymbolsArraySize = 1024;
193   wchar_t symbols_path[kSymbolsArraySize];
194 
195   // Note: The below function takes buffer size as number of characters,
196   // not number of bytes!
197   if (!SymGetSearchPathW(GetCurrentProcess(), symbols_path,
198                          kSymbolsArraySize)) {
199     g_init_error = GetLastError();
200     DLOG(WARNING) << "SymGetSearchPath failed: " << g_init_error;
201     return false;
202   }
203 
204   std::wstring new_path =
205       StrCat({symbols_path, L";", GetExePath().DirName().value()});
206   if (!SymSetSearchPathW(GetCurrentProcess(), new_path.c_str())) {
207     g_init_error = GetLastError();
208     DLOG(WARNING) << "SymSetSearchPath failed." << g_init_error;
209     return false;
210   }
211 
212   g_init_error = ERROR_SUCCESS;
213   return true;
214 }
215 
216 // SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family
217 // of functions.  The Sym* family of functions may only be invoked by one
218 // thread at a time.  SymbolContext code may access a symbol server over the
219 // network while holding the lock for this singleton.  In the case of high
220 // latency, this code will adversely affect performance.
221 //
222 // There is also a known issue where this backtrace code can interact
223 // badly with breakpad if breakpad is invoked in a separate thread while
224 // we are using the Sym* functions.  This is because breakpad does now
225 // share a lock with this function.  See this related bug:
226 //
227 //   https://crbug.com/google-breakpad/311
228 //
229 // This is a very unlikely edge case, and the current solution is to
230 // just ignore it.
231 class SymbolContext {
232  public:
GetInstance()233   static SymbolContext* GetInstance() {
234     // We use a leaky singleton because code may call this during process
235     // termination.
236     return
237       Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get();
238   }
239 
240   SymbolContext(const SymbolContext&) = delete;
241   SymbolContext& operator=(const SymbolContext&) = delete;
242 
243   // For the given trace, attempts to resolve the symbols, and output a trace
244   // to the ostream os.  The format for each line of the backtrace is:
245   //
246   //    <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
247   //
248   // This function should only be called if Init() has been called.  We do not
249   // LOG(FATAL) here because this code is called might be triggered by a
250   // LOG(FATAL) itself. Also, it should not be calling complex code that is
251   // extensible like PathService since that can in turn fire CHECKs.
OutputTraceToStream(const void * const * trace,size_t count,std::ostream * os,const char * prefix_string)252   void OutputTraceToStream(const void* const* trace,
253                            size_t count,
254                            std::ostream* os,
255                            const char* prefix_string) {
256     AutoLock lock(lock_);
257 
258     for (size_t i = 0; (i < count) && os->good(); ++i) {
259       const int kMaxNameLength = 256;
260       DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
261 
262       // Code adapted from MSDN example:
263       // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
264       ULONG64 buffer[
265         (sizeof(SYMBOL_INFO) +
266           kMaxNameLength * sizeof(wchar_t) +
267           sizeof(ULONG64) - 1) /
268         sizeof(ULONG64)];
269       memset(buffer, 0, sizeof(buffer));
270 
271       // Initialize symbol information retrieval structures.
272       DWORD64 sym_displacement = 0;
273       PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
274       symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
275       symbol->MaxNameLen = kMaxNameLength - 1;
276       BOOL has_symbol = SymFromAddr(GetCurrentProcess(), frame,
277                                     &sym_displacement, symbol);
278 
279       // Attempt to retrieve line number information.
280       DWORD line_displacement = 0;
281       IMAGEHLP_LINE64 line = {};
282       line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
283       BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
284                                            &line_displacement, &line);
285 
286       // Output the backtrace line.
287       if (prefix_string)
288         (*os) << prefix_string;
289       (*os) << "\t";
290       if (has_symbol) {
291         (*os) << symbol->Name << " [0x" << trace[i] << "+"
292               << sym_displacement << "]";
293       } else {
294         // If there is no symbol information, add a spacer.
295         (*os) << "(No symbol) [0x" << trace[i] << "]";
296       }
297       if (has_line) {
298         (*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
299       }
300       (*os) << "\n";
301     }
302   }
303 
304  private:
305   friend struct DefaultSingletonTraits<SymbolContext>;
306 
SymbolContext()307   SymbolContext() {
308     InitializeSymbols();
309   }
310 
311   Lock lock_;
312 };
313 
314 }  // namespace
315 
EnableInProcessStackDumping()316 bool EnableInProcessStackDumping() {
317   // Add stack dumping support on exception on windows. Similar to OS_POSIX
318   // signal() handling in process_util_posix.cc.
319   g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter);
320 
321   // Need to initialize symbols early in the process or else this fails on
322   // swarming (since symbols are in different directory than in the exes) and
323   // also release x64.
324   return InitializeSymbols();
325 }
326 
CollectStackTrace(const void ** trace,size_t count)327 NOINLINE size_t CollectStackTrace(const void** trace, size_t count) {
328   // When walking our own stack, use CaptureStackBackTrace().
329   return CaptureStackBackTrace(0, count, const_cast<void**>(trace), NULL);
330 }
331 
StackTrace(EXCEPTION_POINTERS * exception_pointers)332 StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) {
333   InitTrace(exception_pointers->ContextRecord);
334 }
335 
StackTrace(const CONTEXT * context)336 StackTrace::StackTrace(const CONTEXT* context) {
337   InitTrace(context);
338 }
339 
InitTrace(const CONTEXT * context_record)340 void StackTrace::InitTrace(const CONTEXT* context_record) {
341   // StackWalk64 modifies the register context in place, so we have to copy it
342   // so that downstream exception handlers get the right context.  The incoming
343   // context may have had more register state (YMM, etc) than we need to unwind
344   // the stack. Typically StackWalk64 only needs integer and control registers.
345   CONTEXT context_copy;
346   memcpy(&context_copy, context_record, sizeof(context_copy));
347   context_copy.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
348 
349   // When walking an exception stack, we need to use StackWalk64().
350   count_ = 0;
351   // Initialize stack walking.
352   STACKFRAME64 stack_frame;
353   memset(&stack_frame, 0, sizeof(stack_frame));
354 #if defined(ARCH_CPU_X86_64)
355   DWORD machine_type = IMAGE_FILE_MACHINE_AMD64;
356   stack_frame.AddrPC.Offset = context_record->Rip;
357   stack_frame.AddrFrame.Offset = context_record->Rbp;
358   stack_frame.AddrStack.Offset = context_record->Rsp;
359 #elif defined(ARCH_CPU_ARM64)
360   DWORD machine_type = IMAGE_FILE_MACHINE_ARM64;
361   stack_frame.AddrPC.Offset = context_record->Pc;
362   stack_frame.AddrFrame.Offset = context_record->Fp;
363   stack_frame.AddrStack.Offset = context_record->Sp;
364 #elif defined(ARCH_CPU_X86)
365   DWORD machine_type = IMAGE_FILE_MACHINE_I386;
366   stack_frame.AddrPC.Offset = context_record->Eip;
367   stack_frame.AddrFrame.Offset = context_record->Ebp;
368   stack_frame.AddrStack.Offset = context_record->Esp;
369 #else
370 #error Unsupported Windows Arch
371 #endif
372   stack_frame.AddrPC.Mode = AddrModeFlat;
373   stack_frame.AddrFrame.Mode = AddrModeFlat;
374   stack_frame.AddrStack.Mode = AddrModeFlat;
375   while (StackWalk64(machine_type, GetCurrentProcess(), GetCurrentThread(),
376                      &stack_frame, &context_copy, NULL,
377                      &SymFunctionTableAccess64, &SymGetModuleBase64, NULL) &&
378          count_ < std::size(trace_)) {
379     trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
380   }
381 
382   for (size_t i = count_; i < std::size(trace_); ++i)
383     trace_[i] = NULL;
384 }
385 
PrintWithPrefix(const char * prefix_string) const386 void StackTrace::PrintWithPrefix(const char* prefix_string) const {
387   OutputToStreamWithPrefix(&std::cerr, prefix_string);
388 }
389 
OutputToStreamWithPrefix(std::ostream * os,const char * prefix_string) const390 void StackTrace::OutputToStreamWithPrefix(std::ostream* os,
391                                           const char* prefix_string) const {
392   SymbolContext* context = SymbolContext::GetInstance();
393   if (g_init_error != ERROR_SUCCESS) {
394     (*os) << "Error initializing symbols (" << g_init_error
395           << ").  Dumping unresolved backtrace:\n";
396     for (size_t i = 0; (i < count_) && os->good(); ++i) {
397       if (prefix_string)
398         (*os) << prefix_string;
399       (*os) << "\t" << trace_[i] << "\n";
400     }
401   } else {
402     context->OutputTraceToStream(trace_, count_, os, prefix_string);
403   }
404 }
405 
406 }  // namespace debug
407 }  // namespace base
408