Ed Tanous | 50c50c2 | 2017-05-12 16:58:06 -0700 | [diff] [blame^] | 1 | /** ========================================================================== |
| 2 | * Original code made by Robert Engeln. Given as a PUBLIC DOMAIN dedication for |
| 3 | * the benefit of g3log. It was originally published at: |
| 4 | * http://code-freeze.blogspot.com/2012/01/generating-stack-traces-from-c.html |
| 5 | |
| 6 | * 2014-2015: adapted for g3log by Kjell Hedstrom (KjellKod). |
| 7 | * |
| 8 | * This is PUBLIC DOMAIN to use at your own risk and comes |
| 9 | * with no warranties. This code is yours to share, use and modify with no |
| 10 | * strings attached and no restrictions or obligations. |
| 11 | * |
| 12 | * For more information see g3log/LICENSE or refer refer to http://unlicense.org |
| 13 | * ============================================================================*/ |
| 14 | |
| 15 | #include "g3log/stacktrace_windows.hpp" |
| 16 | |
| 17 | #include <windows.h> |
| 18 | #include <dbghelp.h> |
| 19 | #include <map> |
| 20 | #include <memory> |
| 21 | #include <cassert> |
| 22 | #include <vector> |
| 23 | #include <mutex> |
| 24 | #include <g3log/g3log.hpp> |
| 25 | |
| 26 | #pragma comment(lib, "dbghelp.lib") |
| 27 | |
| 28 | |
| 29 | #if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) |
| 30 | #error "stacktrace_win.cpp used but not on a windows system" |
| 31 | #endif |
| 32 | |
| 33 | |
| 34 | |
| 35 | #define g3_MAP_PAIR_STRINGIFY(x) {x, #x} |
| 36 | |
| 37 | namespace { |
| 38 | thread_local size_t g_thread_local_recursive_crash_check = 0; |
| 39 | |
| 40 | const std::map<g3::SignalType, std::string> kExceptionsAsText = { |
| 41 | g3_MAP_PAIR_STRINGIFY(EXCEPTION_ACCESS_VIOLATION) |
| 42 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) |
| 43 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_DATATYPE_MISALIGNMENT) |
| 44 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DENORMAL_OPERAND) |
| 45 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_DIVIDE_BY_ZERO) |
| 46 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT) |
| 47 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INEXACT_RESULT) |
| 48 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_INVALID_OPERATION) |
| 49 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_OVERFLOW) |
| 50 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_STACK_CHECK) |
| 51 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_FLT_UNDERFLOW) |
| 52 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_ILLEGAL_INSTRUCTION) |
| 53 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_IN_PAGE_ERROR) |
| 54 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_INT_DIVIDE_BY_ZERO) |
| 55 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_INT_OVERFLOW) |
| 56 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_INVALID_DISPOSITION) |
| 57 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_NONCONTINUABLE_EXCEPTION) |
| 58 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_PRIV_INSTRUCTION) |
| 59 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_STACK_OVERFLOW) |
| 60 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_BREAKPOINT) |
| 61 | , g3_MAP_PAIR_STRINGIFY(EXCEPTION_SINGLE_STEP) |
| 62 | |
| 63 | }; |
| 64 | |
| 65 | |
| 66 | // Using the given context, fill in all the stack frames. |
| 67 | // Which then later can be interpreted to human readable text |
| 68 | void captureStackTrace(CONTEXT *context, std::vector<uint64_t> &frame_pointers) { |
| 69 | DWORD machine_type = 0; |
| 70 | STACKFRAME64 frame = {}; // force zeroeing |
| 71 | frame.AddrPC.Mode = AddrModeFlat; |
| 72 | frame.AddrFrame.Mode = AddrModeFlat; |
| 73 | frame.AddrStack.Mode = AddrModeFlat; |
| 74 | #ifdef _M_X64 |
| 75 | frame.AddrPC.Offset = context->Rip; |
| 76 | frame.AddrFrame.Offset = context->Rbp; |
| 77 | frame.AddrStack.Offset = context->Rsp; |
| 78 | machine_type = IMAGE_FILE_MACHINE_AMD64; |
| 79 | #else |
| 80 | frame.AddrPC.Offset = context->Eip; |
| 81 | frame.AddrPC.Offset = context->Ebp; |
| 82 | frame.AddrPC.Offset = context->Esp; |
| 83 | machine_type = IMAGE_FILE_MACHINE_I386; |
| 84 | #endif |
| 85 | for (size_t index = 0; index < frame_pointers.size(); ++index) |
| 86 | { |
| 87 | if (StackWalk64(machine_type, |
| 88 | GetCurrentProcess(), |
| 89 | GetCurrentThread(), |
| 90 | &frame, |
| 91 | context, |
| 92 | NULL, |
| 93 | SymFunctionTableAccess64, |
| 94 | SymGetModuleBase64, |
| 95 | NULL)) { |
| 96 | frame_pointers[index] = frame.AddrPC.Offset; |
| 97 | } else { |
| 98 | break; |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | |
| 104 | |
| 105 | // extract readable text from a given stack frame. All thanks to |
| 106 | // using SymFromAddr and SymGetLineFromAddr64 with the stack pointer |
| 107 | std::string getSymbolInformation(const size_t index, const std::vector<uint64_t> &frame_pointers) { |
| 108 | auto addr = frame_pointers[index]; |
| 109 | std::string frame_dump = "stack dump [" + std::to_string(index) + "]\t"; |
| 110 | |
| 111 | DWORD64 displacement64; |
| 112 | DWORD displacement; |
| 113 | char symbol_buffer[sizeof(SYMBOL_INFO) + 256]; |
| 114 | SYMBOL_INFO *symbol = reinterpret_cast<SYMBOL_INFO *>(symbol_buffer); |
| 115 | symbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
| 116 | symbol->MaxNameLen = MAX_SYM_NAME; |
| 117 | |
| 118 | IMAGEHLP_LINE64 line; |
| 119 | line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| 120 | std::string lineInformation; |
| 121 | std::string callInformation; |
| 122 | if (SymFromAddr(GetCurrentProcess(), addr, &displacement64, symbol)) { |
| 123 | callInformation.append(" ").append({symbol->Name, symbol->NameLen}); |
| 124 | if (SymGetLineFromAddr64(GetCurrentProcess(), addr, &displacement, &line)) { |
| 125 | lineInformation.append("\t").append(line.FileName).append(" L: "); |
| 126 | lineInformation.append(std::to_string(line.LineNumber)); |
| 127 | } |
| 128 | } |
| 129 | frame_dump.append(lineInformation).append(callInformation); |
| 130 | return frame_dump; |
| 131 | } |
| 132 | |
| 133 | |
| 134 | // Retrieves all the symbols for the stack frames, fills them witin a text representation and returns it |
| 135 | std::string convertFramesToText(std::vector<uint64_t> &frame_pointers) { |
| 136 | std::string dump; // slightly more efficient than ostringstream |
| 137 | const size_t kSize = frame_pointers.size(); |
| 138 | for (size_t index = 0; index < kSize && frame_pointers[index]; ++index) { |
| 139 | dump += getSymbolInformation(index, frame_pointers); |
| 140 | dump += "\n"; |
| 141 | } |
| 142 | return dump; |
| 143 | } |
| 144 | } // anonymous |
| 145 | |
| 146 | |
| 147 | |
| 148 | |
| 149 | namespace stacktrace { |
| 150 | const std::string kUnknown = {"UNKNOWN EXCEPTION"}; |
| 151 | /// return the text description of a Windows exception code |
| 152 | /// From MSDN GetExceptionCode http://msdn.microsoft.com/en-us/library/windows/desktop/ms679356(v=vs.85).aspx |
| 153 | std::string exceptionIdToText(g3::SignalType id) { |
| 154 | const auto iter = kExceptionsAsText.find(id); |
| 155 | if ( iter == kExceptionsAsText.end()) { |
| 156 | std::string unknown = {kUnknown + ":" + std::to_string(id)}; |
| 157 | return unknown; |
| 158 | } |
| 159 | return iter->second; |
| 160 | } |
| 161 | |
| 162 | /// Yes a double lookup: first for isKnownException and then exceptionIdToText |
| 163 | /// for vectored exceptions we only deal with known exceptions so this tiny |
| 164 | /// overhead we can live with |
| 165 | bool isKnownException(g3::SignalType id) { |
| 166 | return (kExceptionsAsText.end() != kExceptionsAsText.find(id)); |
| 167 | } |
| 168 | |
| 169 | /// helper function: retrieve stackdump from no excisting exception pointer |
| 170 | std::string stackdump() { |
| 171 | CONTEXT current_context; |
| 172 | memset(¤t_context, 0, sizeof(CONTEXT)); |
| 173 | RtlCaptureContext(¤t_context); |
| 174 | return stackdump(¤t_context); |
| 175 | } |
| 176 | |
| 177 | /// helper function: retrieve stackdump, starting from an exception pointer |
| 178 | std::string stackdump(EXCEPTION_POINTERS *info) { |
| 179 | auto context = info->ContextRecord; |
| 180 | return stackdump(context); |
| 181 | |
| 182 | } |
| 183 | |
| 184 | |
| 185 | /// main stackdump function. retrieve stackdump, from the given context |
| 186 | std::string stackdump(CONTEXT *context) { |
| 187 | |
| 188 | if (g_thread_local_recursive_crash_check >= 2) { // In Debug scenarious we allow one extra pass |
| 189 | std::string recursive_crash = {"\n\n\n***** Recursive crash detected"}; |
| 190 | recursive_crash.append(", cannot continue stackdump traversal. *****\n\n\n"); |
| 191 | return recursive_crash; |
| 192 | } |
| 193 | ++g_thread_local_recursive_crash_check; |
| 194 | |
| 195 | static std::mutex m; |
| 196 | std::lock_guard<std::mutex> lock(m); |
| 197 | { |
| 198 | const BOOL kLoadSymModules = TRUE; |
| 199 | const auto initialized = SymInitialize(GetCurrentProcess(), nullptr, kLoadSymModules); |
| 200 | if (TRUE != initialized) { |
| 201 | return { "Error: Cannot call SymInitialize(...) for retrieving symbols in stack" }; |
| 202 | } |
| 203 | |
| 204 | std::shared_ptr<void> RaiiSymCleaner(nullptr, [&](void *) { |
| 205 | SymCleanup(GetCurrentProcess()); |
| 206 | }); // Raii sym cleanup |
| 207 | |
| 208 | |
| 209 | const size_t kmax_frame_dump_size = 64; |
| 210 | std::vector<uint64_t> frame_pointers(kmax_frame_dump_size); |
| 211 | // C++11: size set and values are zeroed |
| 212 | |
| 213 | assert(frame_pointers.size() == kmax_frame_dump_size); |
| 214 | captureStackTrace(context, frame_pointers); |
| 215 | return convertFramesToText(frame_pointers); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | } // stacktrace |
| 220 | |
| 221 | |
| 222 | |