1 | /******************************************************************************* |
2 | Copyright (c) The Taichi Authors (2016- ). All Rights Reserved. |
3 | The use of this software is governed by the LICENSE file. |
4 | *******************************************************************************/ |
5 | |
6 | #include "taichi/system/traceback.h" |
7 | |
8 | #include <vector> |
9 | #include <iostream> |
10 | #include <string> |
11 | #include <sstream> |
12 | #include <cstdio> |
13 | #include <algorithm> |
14 | #include <memory> |
15 | #include <mutex> |
16 | #include "spdlog/fmt/bundled/color.h" |
17 | |
18 | #if defined(__APPLE__) || \ |
19 | (defined(__unix__) && !defined(__linux__)) && !defined(ANDROID) |
20 | #include <execinfo.h> |
21 | #include <cxxabi.h> |
22 | #endif |
23 | #ifdef _WIN64 |
24 | #include <intrin.h> |
25 | #include <dbghelp.h> |
26 | |
27 | #include "taichi/platform/windows/windows.h" |
28 | |
29 | #pragma comment(lib, "dbghelp.lib") |
30 | // https://gist.github.com/rioki/85ca8295d51a5e0b7c56e5005b0ba8b4 |
31 | // |
32 | // Debug Helpers |
33 | // |
34 | // Copyright (c) 2015 - 2017 Sean Farrell <[email protected]> |
35 | // |
36 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
37 | // of this software and associated documentation files (the "Software"), to deal |
38 | // in the Software without restriction, including without limitation the rights |
39 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
40 | // copies of the Software, and to permit persons to whom the Software is |
41 | // furnished to do so, subject to the following conditions: |
42 | // |
43 | // The above copyright notice and this permission notice shall be included in |
44 | // all copies or substantial portions of the Software. |
45 | // |
46 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
47 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
48 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
49 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
50 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
51 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
52 | // THE SOFTWARE. |
53 | // |
54 | |
55 | namespace dbg { |
56 | void trace(const char *msg, ...) { |
57 | char buff[1024]; |
58 | |
59 | va_list args; |
60 | va_start(args, msg); |
61 | vsnprintf(buff, 1024, msg, args); |
62 | |
63 | OutputDebugStringA(buff); |
64 | |
65 | va_end(args); |
66 | } |
67 | |
68 | std::string basename(const std::string &file) { |
69 | unsigned int i = file.find_last_of("\\/" ); |
70 | if (i == std::string::npos) { |
71 | return file; |
72 | } else { |
73 | return file.substr(i + 1); |
74 | } |
75 | } |
76 | |
77 | struct StackFrame { |
78 | DWORD64 address; |
79 | std::string name; |
80 | std::string module; |
81 | unsigned int line; |
82 | std::string file; |
83 | }; |
84 | |
85 | inline std::vector<StackFrame> stack_trace() { |
86 | #if _WIN64 |
87 | DWORD machine = IMAGE_FILE_MACHINE_AMD64; |
88 | #else |
89 | DWORD machine = IMAGE_FILE_MACHINE_I386; |
90 | #endif |
91 | HANDLE process = GetCurrentProcess(); |
92 | HANDLE thread = GetCurrentThread(); |
93 | |
94 | if (SymInitialize(process, NULL, TRUE) == FALSE) { |
95 | trace("Failed to call SymInitialize." ); |
96 | return std::vector<StackFrame>(); |
97 | } |
98 | |
99 | SymSetOptions(SYMOPT_LOAD_LINES); |
100 | |
101 | CONTEXT context = {}; |
102 | context.ContextFlags = CONTEXT_FULL; |
103 | RtlCaptureContext(&context); |
104 | |
105 | #if _WIN64 |
106 | STACKFRAME frame = {}; |
107 | frame.AddrPC.Offset = context.Rip; |
108 | frame.AddrPC.Mode = AddrModeFlat; |
109 | frame.AddrFrame.Offset = context.Rbp; |
110 | frame.AddrFrame.Mode = AddrModeFlat; |
111 | frame.AddrStack.Offset = context.Rsp; |
112 | frame.AddrStack.Mode = AddrModeFlat; |
113 | #else |
114 | STACKFRAME frame = {}; |
115 | frame.AddrPC.Offset = context.Eip; |
116 | frame.AddrPC.Mode = AddrModeFlat; |
117 | frame.AddrFrame.Offset = context.Ebp; |
118 | frame.AddrFrame.Mode = AddrModeFlat; |
119 | frame.AddrStack.Offset = context.Esp; |
120 | frame.AddrStack.Mode = AddrModeFlat; |
121 | #endif |
122 | |
123 | bool first = true; |
124 | |
125 | std::vector<StackFrame> frames; |
126 | while (StackWalk(machine, process, thread, &frame, &context, NULL, |
127 | SymFunctionTableAccess, SymGetModuleBase, NULL)) { |
128 | StackFrame f = {}; |
129 | f.address = frame.AddrPC.Offset; |
130 | |
131 | #if _WIN64 |
132 | DWORD64 moduleBase = 0; |
133 | #else |
134 | DWORD moduleBase = 0; |
135 | #endif |
136 | |
137 | moduleBase = SymGetModuleBase(process, frame.AddrPC.Offset); |
138 | |
139 | char moduelBuff[MAX_PATH]; |
140 | if (moduleBase && |
141 | GetModuleFileNameA((HINSTANCE)moduleBase, moduelBuff, MAX_PATH)) { |
142 | f.module = basename(moduelBuff); |
143 | } else { |
144 | f.module = "Unknown Module" ; |
145 | } |
146 | #if _WIN64 |
147 | DWORD64 offset = 0; |
148 | #else |
149 | DWORD offset = 0; |
150 | #endif |
151 | char symbolBuffer[sizeof(IMAGEHLP_SYMBOL) + 255]; |
152 | PIMAGEHLP_SYMBOL symbol = (PIMAGEHLP_SYMBOL)symbolBuffer; |
153 | symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL) + 255; |
154 | symbol->MaxNameLength = 254; |
155 | |
156 | if (SymGetSymFromAddr(process, frame.AddrPC.Offset, &offset, symbol)) { |
157 | f.name = symbol->Name; |
158 | } else { |
159 | DWORD error = GetLastError(); |
160 | trace("Failed to resolve address 0x%X: %u\n" , frame.AddrPC.Offset, error); |
161 | f.name = "Unknown Function" ; |
162 | } |
163 | |
164 | IMAGEHLP_LINE line; |
165 | line.SizeOfStruct = sizeof(IMAGEHLP_LINE); |
166 | |
167 | DWORD offset_ln = 0; |
168 | if (SymGetLineFromAddr(process, frame.AddrPC.Offset, &offset_ln, &line)) { |
169 | f.file = line.FileName; |
170 | f.line = line.LineNumber; |
171 | } else { |
172 | DWORD error = GetLastError(); |
173 | trace("Failed to resolve line for 0x%X: %u\n" , frame.AddrPC.Offset, |
174 | error); |
175 | f.line = 0; |
176 | } |
177 | |
178 | if (!first) { |
179 | frames.push_back(f); |
180 | } |
181 | first = false; |
182 | } |
183 | |
184 | SymCleanup(process); |
185 | |
186 | return frames; |
187 | } |
188 | } // namespace dbg |
189 | #endif |
190 | #if defined(__linux__) && !defined(ANDROID) |
191 | #include <execinfo.h> |
192 | #include <signal.h> |
193 | #include <ucontext.h> |
194 | #include <unistd.h> |
195 | #include <cxxabi.h> |
196 | #endif |
197 | |
198 | namespace taichi { |
199 | |
200 | void print_traceback() { |
201 | #ifdef __APPLE__ |
202 | static std::mutex traceback_printer_mutex; |
203 | // Modified based on |
204 | // http://www.nullptr.me/2013/04/14/generating-stack-trace-on-os-x/ |
205 | // TODO: print line number instead of offset |
206 | // (https://stackoverflow.com/questions/8278691/how-to-fix-backtrace-line-number-error-in-c) |
207 | |
208 | // record stack trace up to 128 frames |
209 | void *callstack[128] = {}; |
210 | // collect stack frames |
211 | int frames = backtrace((void **)callstack, 128); |
212 | // get the human-readable symbols (mangled) |
213 | char **strs = backtrace_symbols((void **)callstack, frames); |
214 | std::vector<std::string> stack_frames; |
215 | for (int i = 0; i < frames; i++) { |
216 | char function_symbol[1024] = {}; |
217 | char module_name[1024] = {}; |
218 | int offset = 0; |
219 | char addr[48] = {}; |
220 | // split the string, take out chunks out of stack trace |
221 | // we are primarily interested in module, function and address |
222 | sscanf(strs[i], "%*s %s %s %s %*s %d" , module_name, addr, function_symbol, |
223 | &offset); |
224 | |
225 | int valid_cpp_name = 0; |
226 | // if this is a C++ library, symbol will be demangled |
227 | // on success function returns 0 |
228 | char *function_name = |
229 | abi::__cxa_demangle(function_symbol, NULL, 0, &valid_cpp_name); |
230 | |
231 | char stack_frame[4096] = {}; |
232 | sprintf(stack_frame, "* %28s | %7d | %s" , module_name, offset, |
233 | function_name); |
234 | if (function_name != nullptr) |
235 | free(function_name); |
236 | |
237 | std::string frameStr(stack_frame); |
238 | stack_frames.push_back(frameStr); |
239 | } |
240 | free(strs); |
241 | |
242 | // Pretty print the traceback table |
243 | // Exclude this function itself |
244 | stack_frames.erase(stack_frames.begin()); |
245 | std::reverse(stack_frames.begin(), stack_frames.end()); |
246 | std::lock_guard<std::mutex> guard(traceback_printer_mutex); |
247 | printf("\n" ); |
248 | printf( |
249 | " * Taichi Core - Stack Traceback * " |
250 | " \n" ); |
251 | printf( |
252 | "========================================================================" |
253 | "==================\n" ); |
254 | printf( |
255 | "| Module | Offset | Function " |
256 | " |\n" ); |
257 | printf( |
258 | "|-----------------------------------------------------------------------" |
259 | "-----------------|\n" ); |
260 | std::reverse(stack_frames.begin(), stack_frames.end()); |
261 | for (auto trace : stack_frames) { |
262 | const int function_start = 39; |
263 | const int line_width = 86; |
264 | const int function_width = line_width - function_start - 2; |
265 | int i; |
266 | for (i = 0; i < (int)trace.size(); i++) { |
267 | std::cout << trace[i]; |
268 | if (i > function_start + 3 && |
269 | (i - 3 - function_start) % function_width == 0) { |
270 | std::cout << " |" << std::endl << " " ; |
271 | for (int j = 0; j < function_start; j++) { |
272 | std::cout << " " ; |
273 | } |
274 | std::cout << " | " ; |
275 | } |
276 | } |
277 | for (int j = 0; |
278 | j < function_width + 2 - (i - 3 - function_start) % function_width; |
279 | j++) { |
280 | std::cout << " " ; |
281 | } |
282 | std::cout << "|" << std::endl; |
283 | } |
284 | printf( |
285 | "========================================================================" |
286 | "==================\n" ); |
287 | printf("\n" ); |
288 | #elif defined(_WIN64) |
289 | // Windows |
290 | fmt::print(fg(fmt::color::magenta), "***********************************\n" ); |
291 | fmt::print(fg(fmt::color::magenta), "* Taichi Compiler Stack Traceback *\n" ); |
292 | fmt::print(fg(fmt::color::magenta), "***********************************\n" ); |
293 | |
294 | std::vector<dbg::StackFrame> stack = dbg::stack_trace(); |
295 | for (unsigned int i = 0; i < stack.size(); i++) { |
296 | fmt::print(fg(fmt::color::magenta), |
297 | fmt::format("0x{:x}: " , stack[i].address)); |
298 | fmt::print(fg(fmt::color::red), stack[i].name); |
299 | if (stack[i].file != std::string("" )) |
300 | fmt::print(fg(fmt::color::magenta), |
301 | fmt::format("(line {} in {})" , stack[i].line, stack[i].file)); |
302 | fmt::print(fg(fmt::color::magenta), |
303 | fmt::format(" in {}\n" , stack[i].module)); |
304 | } |
305 | #elif defined(ANDROID) |
306 | // Not supported |
307 | fmt::print(fg(fmt::color::magenta), "***********************************\n" ); |
308 | fmt::print(fg(fmt::color::magenta), "* Taichi Compiler Stack Traceback *\n" ); |
309 | fmt::print(fg(fmt::color::magenta), "***********************************\n" ); |
310 | fmt::print(fg(fmt::color::magenta), "NOT SUPPORTED ON ANDROID\n" ); |
311 | #else |
312 | // Based on http://man7.org/linux/man-pages/man3/backtrace.3.html |
313 | constexpr int BT_BUF_SIZE = 1024; |
314 | int nptrs; |
315 | void *buffer[BT_BUF_SIZE]; |
316 | char **strings; |
317 | |
318 | nptrs = backtrace(buffer, BT_BUF_SIZE); |
319 | strings = backtrace_symbols(buffer, nptrs); |
320 | |
321 | if (strings == nullptr) { |
322 | perror("backtrace_symbols" ); |
323 | exit(EXIT_FAILURE); |
324 | } |
325 | |
326 | fmt::print(fg(fmt::color::magenta), "***********************************\n" ); |
327 | fmt::print(fg(fmt::color::magenta), "* Taichi Compiler Stack Traceback *\n" ); |
328 | fmt::print(fg(fmt::color::magenta), "***********************************\n" ); |
329 | |
330 | // j = 0: taichi::print_traceback |
331 | for (int j = 1; j < nptrs; j++) { |
332 | std::string s(strings[j]); |
333 | std::size_t slash = s.find("/" ); |
334 | std::size_t start = s.find("(" ); |
335 | std::size_t end = s.rfind("+" ); |
336 | |
337 | std::string line; |
338 | |
339 | if (slash == 0 && start < end && s[start + 1] != '+') { |
340 | std::string name = s.substr(start + 1, end - start - 1); |
341 | |
342 | char *demangled_name_; |
343 | |
344 | int status = -1; |
345 | |
346 | demangled_name_ = |
347 | abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status); |
348 | |
349 | if (demangled_name_) { |
350 | name = std::string(demangled_name_); |
351 | } |
352 | |
353 | std::string prefix = s.substr(0, start); |
354 | |
355 | line = fmt::format("{}: {}" , prefix, name); |
356 | free(demangled_name_); |
357 | } else { |
358 | line = s; |
359 | } |
360 | fmt::print(fg(fmt::color::magenta), "{}\n" , line); |
361 | } |
362 | std::free(strings); |
363 | #endif |
364 | |
365 | fmt::print( |
366 | fg(fmt::color::orange), |
367 | "\nInternal error occurred. Check out this page for possible solutions:\n" |
368 | "https://docs.taichi-lang.org/docs/install\n" ); |
369 | } |
370 | |
371 | } // namespace taichi |
372 | |