V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
stack_trace_win.cc
1 // Copyright (c) 2012 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 // Slightly adapted for inclusion in V8.
6 // Copyright 2016 the V8 project authors. All rights reserved.
7 
8 #include "src/base/debug/stack_trace.h"
9 
10 // This file can't use "src/base/win32-headers.h" because it defines symbols
11 // that lead to compilation errors. But `NOMINMAX` should be defined to disable
12 // defining of the `min` and `max` MACROS.
13 #ifndef NOMINMAX
14 #define NOMINMAX
15 #endif
16 
17 #include <windows.h>
18 #include <dbghelp.h>
19 #include <Shlwapi.h>
20 #include <stddef.h>
21 
22 #include <iostream>
23 #include <memory>
24 
25 #include "src/base/logging.h"
26 #include "src/base/macros.h"
27 
28 namespace v8 {
29 namespace base {
30 namespace debug {
31 
32 namespace {
33 
34 // Previous unhandled filter. Will be called if not nullptr when we intercept an
35 // exception. Only used in unit tests.
36 LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = nullptr;
37 
38 bool g_dump_stack_in_signal_handler = true;
39 bool g_initialized_symbols = false;
40 DWORD g_init_error = ERROR_SUCCESS;
41 
42 // Prints the exception call stack.
43 // This is the unit tests exception filter.
44 long WINAPI StackDumpExceptionFilter(EXCEPTION_POINTERS* info) { // NOLINT
45  if (g_dump_stack_in_signal_handler) {
46  debug::StackTrace(info).Print();
47  }
48  if (g_previous_filter) return g_previous_filter(info);
49  return EXCEPTION_CONTINUE_SEARCH;
50 }
51 
52 void GetExePath(wchar_t* path_out) {
53  GetModuleFileName(nullptr, path_out, MAX_PATH);
54  path_out[MAX_PATH - 1] = L'\0';
55  PathRemoveFileSpec(path_out);
56 }
57 
58 bool InitializeSymbols() {
59  if (g_initialized_symbols) return g_init_error == ERROR_SUCCESS;
60  g_initialized_symbols = true;
61  // Defer symbol load until they're needed, use undecorated names, and get line
62  // numbers.
63  SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES);
64  if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE)) {
65  g_init_error = GetLastError();
66  // TODO(awong): Handle error: SymInitialize can fail with
67  // ERROR_INVALID_PARAMETER.
68  // When it fails, we should not call debugbreak since it kills the current
69  // process (prevents future tests from running or kills the browser
70  // process).
71  return false;
72  }
73 
74  // When transferring the binaries e.g. between bots, path put
75  // into the executable will get off. To still retrieve symbols correctly,
76  // add the directory of the executable to symbol search path.
77  // All following errors are non-fatal.
78  const size_t kSymbolsArraySize = 1024;
79  std::unique_ptr<wchar_t[]> symbols_path(new wchar_t[kSymbolsArraySize]);
80 
81  // Note: The below function takes buffer size as number of characters,
82  // not number of bytes!
83  if (!SymGetSearchPathW(GetCurrentProcess(), symbols_path.get(),
84  kSymbolsArraySize)) {
85  g_init_error = GetLastError();
86  return false;
87  }
88 
89  wchar_t exe_path[MAX_PATH];
90  GetExePath(exe_path);
91  std::wstring new_path(std::wstring(symbols_path.get()) + L";" +
92  std::wstring(exe_path));
93  if (!SymSetSearchPathW(GetCurrentProcess(), new_path.c_str())) {
94  g_init_error = GetLastError();
95  return false;
96  }
97 
98  g_init_error = ERROR_SUCCESS;
99  return true;
100 }
101 
102 // For the given trace, attempts to resolve the symbols, and output a trace
103 // to the ostream os. The format for each line of the backtrace is:
104 //
105 // <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
106 //
107 // This function should only be called if Init() has been called. We do not
108 // LOG(FATAL) here because this code is called might be triggered by a
109 // LOG(FATAL) itself. Also, it should not be calling complex code that is
110 // extensible like PathService since that can in turn fire CHECKs.
111 void OutputTraceToStream(const void* const* trace, size_t count,
112  std::ostream* os) {
113  for (size_t i = 0; (i < count) && os->good(); ++i) {
114  const int kMaxNameLength = 256;
115  DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
116 
117  // Code adapted from MSDN example:
118  // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
119  ULONG64 buffer[(sizeof(SYMBOL_INFO) + kMaxNameLength * sizeof(wchar_t) +
120  sizeof(ULONG64) - 1) /
121  sizeof(ULONG64)];
122  memset(buffer, 0, sizeof(buffer));
123 
124  // Initialize symbol information retrieval structures.
125  DWORD64 sym_displacement = 0;
126  PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
127  symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
128  symbol->MaxNameLen = kMaxNameLength - 1;
129  BOOL has_symbol =
130  SymFromAddr(GetCurrentProcess(), frame, &sym_displacement, symbol);
131 
132  // Attempt to retrieve line number information.
133  DWORD line_displacement = 0;
134  IMAGEHLP_LINE64 line = {};
135  line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
136  BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
137  &line_displacement, &line);
138 
139  // Output the backtrace line.
140  (*os) << "\t";
141  if (has_symbol) {
142  (*os) << symbol->Name << " [0x" << trace[i] << "+" << sym_displacement
143  << "]";
144  } else {
145  // If there is no symbol information, add a spacer.
146  (*os) << "(No symbol) [0x" << trace[i] << "]";
147  }
148  if (has_line) {
149  (*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
150  }
151  (*os) << "\n";
152  }
153 }
154 
155 } // namespace
156 
157 bool EnableInProcessStackDumping() {
158  // Add stack dumping support on exception on windows. Similar to OS_POSIX
159  // signal() handling in process_util_posix.cc.
160  g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter);
161  g_dump_stack_in_signal_handler = true;
162 
163  // Need to initialize symbols early in the process or else this fails on
164  // swarming (since symbols are in different directory than in the exes) and
165  // also release x64.
166  return InitializeSymbols();
167 }
168 
169 void DisableSignalStackDump() {
170  g_dump_stack_in_signal_handler = false;
171 }
172 
173 StackTrace::StackTrace() {
174  // When walking our own stack, use CaptureStackBackTrace().
175  count_ = CaptureStackBackTrace(0, arraysize(trace_), trace_, nullptr);
176 }
177 
178 StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) {
179  InitTrace(exception_pointers->ContextRecord);
180 }
181 
182 StackTrace::StackTrace(const CONTEXT* context) { InitTrace(context); }
183 
184 void StackTrace::InitTrace(const CONTEXT* context_record) {
185  // StackWalk64 modifies the register context in place, so we have to copy it
186  // so that downstream exception handlers get the right context. The incoming
187  // context may have had more register state (YMM, etc) than we need to unwind
188  // the stack. Typically StackWalk64 only needs integer and control registers.
189  CONTEXT context_copy;
190  memcpy(&context_copy, context_record, sizeof(context_copy));
191  context_copy.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
192 
193  // When walking an exception stack, we need to use StackWalk64().
194  count_ = 0;
195  // Initialize stack walking.
196  STACKFRAME64 stack_frame;
197  memset(&stack_frame, 0, sizeof(stack_frame));
198 #if defined(_WIN64)
199 #if defined(_M_X64)
200  int machine_type = IMAGE_FILE_MACHINE_AMD64;
201  stack_frame.AddrPC.Offset = context_record->Rip;
202  stack_frame.AddrFrame.Offset = context_record->Rbp;
203  stack_frame.AddrStack.Offset = context_record->Rsp;
204 #elif defined(_M_ARM64)
205  int machine_type = IMAGE_FILE_MACHINE_ARM64;
206  stack_frame.AddrPC.Offset = context_record->Pc;
207  stack_frame.AddrFrame.Offset = context_record->Fp;
208  stack_frame.AddrStack.Offset = context_record->Sp;
209 #else
210 #error Unsupported Arch
211 #endif
212 #else
213  int machine_type = IMAGE_FILE_MACHINE_I386;
214  stack_frame.AddrPC.Offset = context_record->Eip;
215  stack_frame.AddrFrame.Offset = context_record->Ebp;
216  stack_frame.AddrStack.Offset = context_record->Esp;
217 #endif
218  stack_frame.AddrPC.Mode = AddrModeFlat;
219  stack_frame.AddrFrame.Mode = AddrModeFlat;
220  stack_frame.AddrStack.Mode = AddrModeFlat;
221  while (StackWalk64(machine_type, GetCurrentProcess(), GetCurrentThread(),
222  &stack_frame, &context_copy, nullptr,
223  &SymFunctionTableAccess64, &SymGetModuleBase64, nullptr) &&
224  count_ < arraysize(trace_)) {
225  trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
226  }
227 
228  for (size_t i = count_; i < arraysize(trace_); ++i) trace_[i] = nullptr;
229 }
230 
231 void StackTrace::Print() const { OutputToStream(&std::cerr); }
232 
233 void StackTrace::OutputToStream(std::ostream* os) const {
234  InitializeSymbols();
235  if (g_init_error != ERROR_SUCCESS) {
236  (*os) << "Error initializing symbols (" << g_init_error
237  << "). Dumping unresolved backtrace:\n";
238  for (size_t i = 0; (i < count_) && os->good(); ++i) {
239  (*os) << "\t" << trace_[i] << "\n";
240  }
241  } else {
242  (*os) << "\n";
243  (*os) << "==== C stack trace ===============================\n";
244  (*os) << "\n";
245  OutputTraceToStream(trace_, count_, os);
246  }
247 }
248 
249 } // namespace debug
250 } // namespace base
251 } // namespace v8
Definition: libplatform.h:13