xref: /aosp_15_r20/external/cronet/base/debug/stack_trace_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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