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