#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/compiler_specific.h"
#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/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "build/build_config.h"
namespace base {
namespace debug {
namespace {
LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL;
bool g_initialized_symbols = false;
DWORD g_init_error = ERROR_SUCCESS;
DWORD g_status_info_length_mismatch = 0xC0000004;
bool g_in_process_stack_dumps_enabled = false;
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 << base::StringPrintf("0x%08x", 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() {
std::array<wchar_t, MAX_PATH> system_buffer;
GetModuleFileName(NULL, system_buffer.data(), system_buffer.size());
system_buffer.back() = L'\0';
return FilePath(system_buffer.data());
}
constexpr size_t kSymInitializeRetryCount = 3;
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;
}
if (g_init_error != ERROR_INVALID_PARAMETER) {
return false;
}
SymCleanup(current_process);
if (SymInitializeWrapper(current_process, TRUE)) {
return true;
}
return false;
}
bool InitializeSymbols() {
if (g_initialized_symbols) {
SymCleanup(GetCurrentProcess());
g_initialized_symbols = false;
}
g_initialized_symbols = true;
SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES);
if (!SymInitializeCurrentProc()) {
DLOG(ERROR) << "SymInitialize failed: " << g_init_error;
return false;
}
static constexpr size_t kSymbolsArraySize = 1024;
wchar_t symbols_path[kSymbolsArraySize];
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;
}
class SymbolContext {
public:
static SymbolContext* GetInstance() {
return Singleton<SymbolContext, LeakySingletonTraits<SymbolContext>>::get();
}
SymbolContext(const SymbolContext&) = delete;
SymbolContext& operator=(const SymbolContext&) = delete;
void OutputTraceToStream(base::span<const void* const> traces,
std::ostream* os,
cstring_view prefix_string) {
AutoLock lock(lock_);
for (size_t i = 0; (i < traces.size()) && os->good(); ++i) {
const int kMaxNameLength = 256;
DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(traces[i]);
ULONG64 buffer[(sizeof(SYMBOL_INFO) + kMaxNameLength * sizeof(wchar_t) +
sizeof(ULONG64) - 1) /
sizeof(ULONG64)];
UNSAFE_TODO(memset(buffer, 0, sizeof(buffer)));
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);
DWORD line_displacement = 0;
IMAGEHLP_LINE64 line = {};
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
&line_displacement, &line);
(*os) << prefix_string << "\t";
if (has_symbol) {
(*os) << symbol->Name << " "
<< base::StringPrintf("[%p+%x]", traces[i], sym_displacement);
} else {
(*os) << "(No symbol) " << base::StringPrintf("[%p]", traces[i]);
}
if (has_line) {
(*os) << " (" << line.FileName << ":"
<< base::StringPrintf("%lu", line.LineNumber) << ")";
}
(*os) << "\n";
}
}
private:
friend struct DefaultSingletonTraits<SymbolContext>;
SymbolContext() { InitializeSymbols(); }
Lock lock_;
};
void OutputAddressesWithPrefix(std::ostream* os,
cstring_view prefix_string,
span<const void* const> addresses) {
for (const void* const addr : addresses) {
(*os) << prefix_string << "\t" << base::StringPrintf("%p", addr) << "\n";
if (!os->good()) {
break;
}
}
}
}
bool EnableInProcessStackDumping() {
g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter);
g_in_process_stack_dumps_enabled = InitializeSymbols();
return g_in_process_stack_dumps_enabled;
}
bool InProcessStackDumpingEnabled() {
return g_in_process_stack_dumps_enabled;
}
bool DisableInProcessStackDumpingForTesting() {
g_previous_filter = SetUnhandledExceptionFilter(g_previous_filter);
g_in_process_stack_dumps_enabled = false;
return true;
}
NOINLINE size_t CollectStackTrace(span<const void*> trace) {
return CaptureStackBackTrace(0, trace.size(),
const_cast<void**>(trace.data()), 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) {
if (ShouldSuppressOutput()) {
CHECK_EQ(count_, 0U);
std::ranges::fill(trace_, nullptr);
return;
}
CONTEXT context_copy;
UNSAFE_TODO(memcpy(&context_copy, context_record, sizeof(context_copy)));
context_copy.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
count_ = 0;
STACKFRAME64 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);
}
std::ranges::fill(span(trace_).last(trace_.size() - count_), nullptr);
}
void StackTrace::PrintMessageWithPrefix(cstring_view prefix_string,
cstring_view message) {
std::cerr << prefix_string << message;
}
void StackTrace::PrintWithPrefixImpl(cstring_view prefix_string) const {
OutputToStreamWithPrefixImpl(&std::cerr, prefix_string);
}
void StackTrace::OutputToStreamWithPrefixImpl(
std::ostream* os,
cstring_view prefix_string) const {
if (!InProcessStackDumpingEnabled()) {
(*os) << "Symbols not available. Dumping unresolved backtrace:\n";
OutputAddressesWithPrefix(os, prefix_string, addresses());
} else {
SymbolContext* context = SymbolContext::GetInstance();
if (g_init_error != ERROR_SUCCESS) {
(*os) << "Error initializing symbols (" << g_init_error
<< "). Dumping unresolved backtrace:\n";
OutputAddressesWithPrefix(os, prefix_string, addresses());
} else {
context->OutputTraceToStream(addresses(), os, prefix_string);
}
}
}
}
}