| /** ========================================================================== |
| * Original code made by Robert Engeln. Given as a PUBLIC DOMAIN dedication for |
| * the benefit of g3log. It was originally published at: |
| * http://code-freeze.blogspot.com/2012/01/generating-stack-traces-from-c.html |
| |
| * 2014-2015: adapted for g3log by Kjell Hedstrom (KjellKod). |
| * |
| * This is PUBLIC DOMAIN to use at your own risk and comes |
| * with no warranties. This code is yours to share, use and modify with no |
| * strings attached and no restrictions or obligations. |
| * |
| * For more information see g3log/LICENSE or refer refer to http://unlicense.org |
| * ============================================================================*/ |
| |
| #include "g3log/stacktrace_windows.hpp" |
| |
| #include <windows.h> |
| #include <dbghelp.h> |
| #include <map> |
| #include <memory> |
| #include <cassert> |
| #include <vector> |
| #include <mutex> |
| #include <g3log/g3log.hpp> |
| |
| #pragma comment(lib, "dbghelp.lib") |
| |
| |
| #if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) |
| #error "stacktrace_win.cpp used but not on a windows system" |
| #endif |
| |
| |
| |
| #define g3_MAP_PAIR_STRINGIFY(x) {x, #x} |
| |
| namespace { |
| thread_local size_t g_thread_local_recursive_crash_check = 0; |
| |
| const std::map<g3::SignalType, std::string> kExceptionsAsText = { |
| g3_MAP_PAIR_STRINGIFY(EXCEPTION_ACCESS_VIOLATION) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_DATATYPE_MISALIGNMENT) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DENORMAL_OPERAND) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DIVIDE_BY_ZERO) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INVALID_OPERATION) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_OVERFLOW) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_STACK_CHECK) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_UNDERFLOW) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_ILLEGAL_INSTRUCTION) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_IN_PAGE_ERROR) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_INT_DIVIDE_BY_ZERO) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_INT_OVERFLOW) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_INVALID_DISPOSITION) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_NONCONTINUABLE_EXCEPTION) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_PRIV_INSTRUCTION) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_STACK_OVERFLOW) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_BREAKPOINT) |
| , g3_MAP_PAIR_STRINGIFY(EXCEPTION_SINGLE_STEP) |
| |
| }; |
| |
| |
| // Using the given context, fill in all the stack frames. |
| // Which then later can be interpreted to human readable text |
| void captureStackTrace(CONTEXT *context, std::vector<uint64_t> &frame_pointers) { |
| DWORD machine_type = 0; |
| STACKFRAME64 frame = {}; // force zeroeing |
| frame.AddrPC.Mode = AddrModeFlat; |
| frame.AddrFrame.Mode = AddrModeFlat; |
| frame.AddrStack.Mode = AddrModeFlat; |
| #ifdef _M_X64 |
| frame.AddrPC.Offset = context->Rip; |
| frame.AddrFrame.Offset = context->Rbp; |
| frame.AddrStack.Offset = context->Rsp; |
| machine_type = IMAGE_FILE_MACHINE_AMD64; |
| #else |
| frame.AddrPC.Offset = context->Eip; |
| frame.AddrPC.Offset = context->Ebp; |
| frame.AddrPC.Offset = context->Esp; |
| machine_type = IMAGE_FILE_MACHINE_I386; |
| #endif |
| for (size_t index = 0; index < frame_pointers.size(); ++index) |
| { |
| if (StackWalk64(machine_type, |
| GetCurrentProcess(), |
| GetCurrentThread(), |
| &frame, |
| context, |
| NULL, |
| SymFunctionTableAccess64, |
| SymGetModuleBase64, |
| NULL)) { |
| frame_pointers[index] = frame.AddrPC.Offset; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| |
| |
| // extract readable text from a given stack frame. All thanks to |
| // using SymFromAddr and SymGetLineFromAddr64 with the stack pointer |
| std::string getSymbolInformation(const size_t index, const std::vector<uint64_t> &frame_pointers) { |
| auto addr = frame_pointers[index]; |
| std::string frame_dump = "stack dump [" + std::to_string(index) + "]\t"; |
| |
| DWORD64 displacement64; |
| DWORD displacement; |
| char symbol_buffer[sizeof(SYMBOL_INFO) + 256]; |
| SYMBOL_INFO *symbol = reinterpret_cast<SYMBOL_INFO *>(symbol_buffer); |
| symbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
| symbol->MaxNameLen = MAX_SYM_NAME; |
| |
| IMAGEHLP_LINE64 line; |
| line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| std::string lineInformation; |
| std::string callInformation; |
| if (SymFromAddr(GetCurrentProcess(), addr, &displacement64, symbol)) { |
| callInformation.append(" ").append({symbol->Name, symbol->NameLen}); |
| if (SymGetLineFromAddr64(GetCurrentProcess(), addr, &displacement, &line)) { |
| lineInformation.append("\t").append(line.FileName).append(" L: "); |
| lineInformation.append(std::to_string(line.LineNumber)); |
| } |
| } |
| frame_dump.append(lineInformation).append(callInformation); |
| return frame_dump; |
| } |
| |
| |
| // Retrieves all the symbols for the stack frames, fills them witin a text representation and returns it |
| std::string convertFramesToText(std::vector<uint64_t> &frame_pointers) { |
| std::string dump; // slightly more efficient than ostringstream |
| const size_t kSize = frame_pointers.size(); |
| for (size_t index = 0; index < kSize && frame_pointers[index]; ++index) { |
| dump += getSymbolInformation(index, frame_pointers); |
| dump += "\n"; |
| } |
| return dump; |
| } |
| } // anonymous |
| |
| |
| |
| |
| namespace stacktrace { |
| const std::string kUnknown = {"UNKNOWN EXCEPTION"}; |
| /// return the text description of a Windows exception code |
| /// From MSDN GetExceptionCode http://msdn.microsoft.com/en-us/library/windows/desktop/ms679356(v=vs.85).aspx |
| std::string exceptionIdToText(g3::SignalType id) { |
| const auto iter = kExceptionsAsText.find(id); |
| if ( iter == kExceptionsAsText.end()) { |
| std::string unknown = {kUnknown + ":" + std::to_string(id)}; |
| return unknown; |
| } |
| return iter->second; |
| } |
| |
| /// Yes a double lookup: first for isKnownException and then exceptionIdToText |
| /// for vectored exceptions we only deal with known exceptions so this tiny |
| /// overhead we can live with |
| bool isKnownException(g3::SignalType id) { |
| return (kExceptionsAsText.end() != kExceptionsAsText.find(id)); |
| } |
| |
| /// helper function: retrieve stackdump from no excisting exception pointer |
| std::string stackdump() { |
| CONTEXT current_context; |
| memset(¤t_context, 0, sizeof(CONTEXT)); |
| RtlCaptureContext(¤t_context); |
| return stackdump(¤t_context); |
| } |
| |
| /// helper function: retrieve stackdump, starting from an exception pointer |
| std::string stackdump(EXCEPTION_POINTERS *info) { |
| auto context = info->ContextRecord; |
| return stackdump(context); |
| |
| } |
| |
| |
| /// main stackdump function. retrieve stackdump, from the given context |
| std::string stackdump(CONTEXT *context) { |
| |
| if (g_thread_local_recursive_crash_check >= 2) { // In Debug scenarious we allow one extra pass |
| std::string recursive_crash = {"\n\n\n***** Recursive crash detected"}; |
| recursive_crash.append(", cannot continue stackdump traversal. *****\n\n\n"); |
| return recursive_crash; |
| } |
| ++g_thread_local_recursive_crash_check; |
| |
| static std::mutex m; |
| std::lock_guard<std::mutex> lock(m); |
| { |
| const BOOL kLoadSymModules = TRUE; |
| const auto initialized = SymInitialize(GetCurrentProcess(), nullptr, kLoadSymModules); |
| if (TRUE != initialized) { |
| return { "Error: Cannot call SymInitialize(...) for retrieving symbols in stack" }; |
| } |
| |
| std::shared_ptr<void> RaiiSymCleaner(nullptr, [&](void *) { |
| SymCleanup(GetCurrentProcess()); |
| }); // Raii sym cleanup |
| |
| |
| const size_t kmax_frame_dump_size = 64; |
| std::vector<uint64_t> frame_pointers(kmax_frame_dump_size); |
| // C++11: size set and values are zeroed |
| |
| assert(frame_pointers.size() == kmax_frame_dump_size); |
| captureStackTrace(context, frame_pointers); |
| return convertFramesToText(frame_pointers); |
| } |
| } |
| |
| } // stacktrace |
| |
| |
| |