blob: cfdd69f8624972ba2096b594d9453871eac41902 [file] [log] [blame]
Ed Tanous50c50c22017-05-12 16:58:06 -07001/** ==========================================================================
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
37namespace {
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
149namespace 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(&current_context, 0, sizeof(CONTEXT));
173 RtlCaptureContext(&current_context);
174 return stackdump(&current_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