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
55namespace dbg {
56void 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
68std::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
77struct StackFrame {
78 DWORD64 address;
79 std::string name;
80 std::string module;
81 unsigned int line;
82 std::string file;
83};
84
85inline 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
198namespace taichi {
199
200void 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