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