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