1 #include <c10/util/Backtrace.h>
2 #include <c10/util/Type.h>
3 #include <c10/util/irange.h>
4 #include <optional>
5
6 #include <functional>
7 #include <memory>
8 #include <sstream>
9 #include <string>
10 #include <vector>
11
12 #ifdef _MSC_VER
13 #include <c10/util/win32-headers.h>
14 #include <iomanip>
15 #pragma comment(lib, "Dbghelp.lib")
16 #endif
17
18 #if SUPPORTS_BACKTRACE
19 #include <cxxabi.h>
20 #ifdef C10_ANDROID
21 #include <dlfcn.h>
22 #include <unwind.h>
23 #else
24 #include <execinfo.h>
25 #endif
26 #endif
27
28 #ifdef FBCODE_CAFFE2
29 #include <common/process/StackTrace.h>
30 #endif
31
32 namespace c10 {
33
34 namespace {
35
36 #ifdef FBCODE_CAFFE2
37
38 // For some reason, the stacktrace implementation in fbcode is better than ours,
39 // see https://github.com/pytorch/pytorch/issues/56399 When it's available, just
40 // use that.
41 class GetBacktraceImpl {
42 public:
GetBacktraceImpl(size_t frames_to_skip,size_t,bool)43 C10_ALWAYS_INLINE GetBacktraceImpl(
44 size_t frames_to_skip,
45 size_t /* maximum_number_of_frames */,
46 bool /* skip_python_frames */)
47 : st_(/*skipFrames=*/frames_to_skip) {}
48
symbolize() const49 std::string symbolize() const {
50 return st_.toString();
51 }
52
53 private:
54 facebook::process::StackTrace st_;
55 };
56
57 #elif SUPPORTS_BACKTRACE && defined(C10_ANDROID)
58
59 struct AndroidBacktraceState {
60 std::vector<void*> buffer;
61 };
62
63 _Unwind_Reason_Code android_unwind_callback(
64 struct _Unwind_Context* context,
65 void* arg) {
66 AndroidBacktraceState* state = (AndroidBacktraceState*)arg;
67 uintptr_t pc = _Unwind_GetIP(context);
68 if (pc) {
69 state->buffer.emplace_back(reinterpret_cast<void*>(pc));
70 }
71 return _URC_NO_REASON;
72 }
73
74 class GetBacktraceImpl {
75 public:
76 C10_ALWAYS_INLINE GetBacktraceImpl(
77 size_t /* frames_to_skip */,
78 size_t /* maximum_number_of_frames */,
79 bool /* skip_python_frames */) {
80 _Unwind_Backtrace(android_unwind_callback, &state_);
81 }
82
83 std::string symbolize() const {
84 std::ostringstream os;
85 int idx = 0;
86 char* demangled = nullptr;
87 size_t length = 0;
88
89 for (const void* addr : state_.buffer) {
90 const char* symbol = "";
91
92 Dl_info info;
93 if (dladdr(addr, &info) && info.dli_sname) {
94 symbol = info.dli_sname;
95 }
96
97 int status = 0;
98 demangled = __cxxabiv1::__cxa_demangle(
99 /*mangled_name*/ symbol,
100 /*output_buffer*/ demangled,
101 /*length*/ &length,
102 /*status*/ &status);
103
104 os << " frame #" << idx++ << "\t"
105 << ((demangled != NULL && status == 0) ? demangled : symbol) << "["
106 << addr << "]\t" << std::endl;
107 }
108 free(demangled);
109 return os.str();
110 }
111
112 private:
113 AndroidBacktraceState state_;
114 };
115
116 #elif SUPPORTS_BACKTRACE // !defined(C10_ANDROID)
117
118 struct FrameInformation {
119 /// If available, the demangled name of the function at this frame, else
120 /// whatever (possibly mangled) name we got from `backtrace()`.
121 std::string function_name;
122 /// This is a number in hexadecimal form (e.g. "0xdead") representing the
123 /// offset into the function's machine code at which the function's body
124 /// starts, i.e. skipping the "prologue" that handles stack manipulation and
125 /// other calling convention things.
126 std::string offset_into_function;
127 /// NOTE: In debugger parlance, the "object file" refers to the ELF file that
128 /// the symbol originates from, i.e. either an executable or a library.
129 std::string object_file;
130 };
131
132 bool is_python_frame(const FrameInformation& frame) {
133 return frame.object_file == "python" || frame.object_file == "python3" ||
134 (frame.object_file.find("libpython") != std::string::npos);
135 }
136
137 std::optional<FrameInformation> parse_frame_information(
138 const std::string& frame_string) {
139 FrameInformation frame;
140
141 // This is the function name in the CXX ABI mangled format, e.g. something
142 // like _Z1gv. Reference:
143 // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
144 std::string mangled_function_name;
145
146 #if defined(__GLIBCXX__)
147 // In GLIBCXX, `frame_string` follows the pattern
148 // `<object-file>(<mangled-function-name>+<offset-into-function>)
149 // [<return-address>]`
150
151 auto function_name_start = frame_string.find('(');
152 if (function_name_start == std::string::npos) {
153 return std::nullopt;
154 }
155 function_name_start += 1;
156
157 auto offset_start = frame_string.find('+', function_name_start);
158 if (offset_start == std::string::npos) {
159 return std::nullopt;
160 }
161 offset_start += 1;
162
163 const auto offset_end = frame_string.find(')', offset_start);
164 if (offset_end == std::string::npos) {
165 return std::nullopt;
166 }
167
168 frame.object_file = frame_string.substr(0, function_name_start - 1);
169 frame.offset_into_function =
170 frame_string.substr(offset_start, offset_end - offset_start);
171
172 // NOTE: We don't need to parse the return address because
173 // we already have it from the call to `backtrace()`.
174
175 mangled_function_name = frame_string.substr(
176 function_name_start, (offset_start - 1) - function_name_start);
177 #elif defined(_LIBCPP_VERSION)
178 // In LIBCXX, The pattern is
179 // `<frame number> <object-file> <return-address> <mangled-function-name> +
180 // <offset-into-function>`
181 std::string skip;
182 std::istringstream input_stream(frame_string);
183 // operator>>() does not fail -- if the input stream is corrupted, the
184 // strings will simply be empty.
185 input_stream >> skip >> frame.object_file >> skip >> mangled_function_name >>
186 skip >> frame.offset_into_function;
187 #else
188 #warning Unknown standard library, backtraces may have incomplete debug information
189 return std::nullopt;
190 #endif // defined(__GLIBCXX__)
191
192 // Some system-level functions don't have sufficient debug information, so
193 // we'll display them as "<unknown function>". They'll still have a return
194 // address and other pieces of information.
195 if (mangled_function_name.empty()) {
196 frame.function_name = "<unknown function>";
197 return frame;
198 }
199
200 frame.function_name = demangle(mangled_function_name.c_str());
201 return frame;
202 }
203
204 class GetBacktraceImpl {
205 public:
206 C10_ALWAYS_INLINE GetBacktraceImpl(
207 size_t frames_to_skip,
208 size_t maximum_number_of_frames,
209 bool skip_python_frames)
210 : skip_python_frames_(skip_python_frames),
211 callstack_(frames_to_skip + maximum_number_of_frames, nullptr) {
212 // We always skip this frame (backtrace).
213 frames_to_skip += 1;
214
215 // backtrace() gives us a list of return addresses in the current call
216 // stack. NOTE: As per man (3) backtrace it can never fail
217 // (http://man7.org/linux/man-pages/man3/backtrace.3.html).
218 auto number_of_frames = static_cast<size_t>(
219 ::backtrace(callstack_.data(), static_cast<int>(callstack_.size())));
220
221 // Skip as many frames as requested.
222 frames_to_skip = std::min(frames_to_skip, number_of_frames);
223 number_of_frames -= frames_to_skip;
224 callstack_.erase(
225 callstack_.begin(),
226 callstack_.begin() + static_cast<ssize_t>(frames_to_skip));
227 callstack_.resize(number_of_frames);
228 }
229
230 std::string symbolize() const {
231 // `backtrace_symbols` takes the return addresses obtained from
232 // `backtrace()` and fetches string representations of each stack.
233 // Unfortunately it doesn't return a struct of individual pieces of
234 // information but a concatenated string, so we'll have to parse the string
235 // after. NOTE: The array returned by `backtrace_symbols` is malloc'd and
236 // must be manually freed, but not the strings inside the array.
237 std::unique_ptr<char*, std::function<void(char**)>> raw_symbols(
238 ::backtrace_symbols(
239 callstack_.data(), static_cast<int>(callstack_.size())),
240 /*deleter=*/free);
241 const std::vector<std::string> symbols(
242 raw_symbols.get(), raw_symbols.get() + callstack_.size());
243
244 // The backtrace string goes into here.
245 std::ostringstream stream;
246
247 // Toggles to true after the first skipped python frame.
248 bool has_skipped_python_frames = false;
249
250 for (const auto frame_number : c10::irange(callstack_.size())) {
251 const auto frame = parse_frame_information(symbols[frame_number]);
252
253 if (skip_python_frames_ && frame && is_python_frame(*frame)) {
254 if (!has_skipped_python_frames) {
255 stream << "<omitting python frames>\n";
256 has_skipped_python_frames = true;
257 }
258 continue;
259 }
260
261 // frame #<number>:
262 stream << "frame #" << frame_number << ": ";
263
264 if (frame) {
265 // <function_name> + <offset> (<return-address> in <object-file>)
266 stream << frame->function_name << " + " << frame->offset_into_function
267 << " (" << callstack_[frame_number] << " in "
268 << frame->object_file << ")\n";
269 } else {
270 // In the edge-case where we couldn't parse the frame string, we can
271 // just use it directly (it may have a different format).
272 stream << symbols[frame_number] << "\n";
273 }
274 }
275
276 return stream.str();
277 }
278
279 private:
280 const bool skip_python_frames_;
281 std::vector<void*> callstack_;
282 };
283
284 #elif defined(_MSC_VER) // !SUPPORTS_BACKTRACE
285
286 const int max_name_len = 256;
287 std::string get_module_base_name(void* addr) {
288 HMODULE h_module;
289 char module[max_name_len];
290 strcpy(module, "");
291 GetModuleHandleEx(
292 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
293 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
294 (LPCTSTR)addr,
295 &h_module);
296 if (h_module != NULL) {
297 GetModuleFileNameA(h_module, module, max_name_len);
298 }
299 char* last_slash_pos = strrchr(module, '\\');
300 if (last_slash_pos) {
301 std::string module_base_name(last_slash_pos + 1);
302 return module_base_name;
303 } else {
304 std::string module_base_name(module);
305 return module_base_name;
306 }
307 }
308 class SymbolHelper {
309 public:
310 static SymbolHelper& getInstance() {
311 static SymbolHelper instance;
312 return instance;
313 }
314 bool inited = false;
315 HANDLE process;
316
317 private:
318 SymbolHelper() {
319 process = GetCurrentProcess();
320 DWORD flags = SymGetOptions();
321 SymSetOptions(flags | SYMOPT_DEFERRED_LOADS);
322 inited = SymInitialize(process, NULL, TRUE);
323 }
324 ~SymbolHelper() {
325 if (inited) {
326 SymCleanup(process);
327 }
328 }
329
330 public:
331 SymbolHelper(SymbolHelper const&) = delete;
332 void operator=(SymbolHelper const&) = delete;
333 };
334
335 // This backtrace retrieval is implemented on Windows via the Windows API using
336 // `CaptureStackBackTrace`, `SymFromAddr` and `SymGetLineFromAddr64`.
337 // https://stackoverflow.com/questions/5693192/win32-backtrace-from-c-code
338 // https://stackoverflow.com/questions/26398064/counterpart-to-glibcs-backtrace-and-backtrace-symbols-on-windows
339 // https://docs.microsoft.com/en-us/windows/win32/debug/capturestackbacktrace
340 // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symfromaddr
341 // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symgetlinefromaddr64
342 // TODO: Support skipping python frames
343 class GetBacktraceImpl {
344 public:
345 C10_ALWAYS_INLINE GetBacktraceImpl(
346 size_t frames_to_skip,
347 size_t maximum_number_of_frames,
348 bool /* skip_python_frames */)
349 : back_trace_(new void*[maximum_number_of_frames]) {
350 // We always skip this frame (backtrace).
351 frames_to_skip += 1;
352
353 // Get the frames
354 n_frame_ = CaptureStackBackTrace(
355 static_cast<DWORD>(frames_to_skip),
356 static_cast<DWORD>(maximum_number_of_frames),
357 back_trace_.get(),
358 NULL);
359 }
360
361 std::string symbolize() const {
362 DWORD64 displacement;
363 DWORD disp;
364 std::unique_ptr<IMAGEHLP_LINE64> line;
365
366 char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
367 PSYMBOL_INFO p_symbol = (PSYMBOL_INFO)buffer;
368
369 bool with_symbol = false;
370 bool with_line = false;
371
372 // The backtrace string goes into here.
373 std::ostringstream stream;
374
375 // Initialize symbols if necessary
376 SymbolHelper& sh = SymbolHelper::getInstance();
377
378 for (USHORT i_frame = 0; i_frame < n_frame_; ++i_frame) {
379 // Get the address and the name of the symbol
380 if (sh.inited) {
381 p_symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
382 p_symbol->MaxNameLen = MAX_SYM_NAME;
383 with_symbol = SymFromAddr(
384 sh.process, (ULONG64)back_trace_[i_frame], &displacement, p_symbol);
385 }
386
387 // Get the line number and the module
388 if (sh.inited) {
389 line.reset(new IMAGEHLP_LINE64());
390 line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
391 with_line = SymGetLineFromAddr64(
392 sh.process, (ULONG64)back_trace_[i_frame], &disp, line.get());
393 }
394
395 // Get the module basename
396 std::string module = get_module_base_name(back_trace_[i_frame]);
397
398 // The pattern on Windows is
399 // `<return-address> <symbol-address>
400 // <module-name>!<demangled-function-name> [<file-name> @ <line-number>]
401 stream << std::setfill('0') << std::setw(16) << std::uppercase << std::hex
402 << back_trace_[i_frame] << std::dec;
403 if (with_symbol) {
404 stream << std::setfill('0') << std::setw(16) << std::uppercase
405 << std::hex << p_symbol->Address << std::dec << " " << module
406 << "!" << p_symbol->Name;
407 } else {
408 stream << " <unknown symbol address> " << module << "!<unknown symbol>";
409 }
410 stream << " [";
411 if (with_line) {
412 stream << line->FileName << " @ " << line->LineNumber;
413 } else {
414 stream << "<unknown file> @ <unknown line number>";
415 }
416 stream << "]" << std::endl;
417 }
418
419 return stream.str();
420 }
421
422 private:
423 std::unique_ptr<void*[]> back_trace_;
424 USHORT n_frame_;
425 };
426
427 #else
428
429 class GetBacktraceImpl {
430 public:
431 C10_ALWAYS_INLINE GetBacktraceImpl(
432 size_t /* frames_to_skip */,
433 size_t /* maximum_number_of_frames */,
434 bool /* skip_python_frames */) {}
435
436 std::string symbolize() const {
437 return "(no backtrace available)";
438 }
439 };
440
441 #endif
442
443 } // namespace
444
get_backtrace(size_t frames_to_skip,size_t maximum_number_of_frames,bool skip_python_frames)445 std::string get_backtrace(
446 size_t frames_to_skip,
447 size_t maximum_number_of_frames,
448 bool skip_python_frames) {
449 return GetBacktraceImpl{
450 frames_to_skip, maximum_number_of_frames, skip_python_frames}
451 .symbolize();
452 }
453
get_lazy_backtrace(size_t frames_to_skip,size_t maximum_number_of_frames,bool skip_python_frames)454 Backtrace get_lazy_backtrace(
455 size_t frames_to_skip,
456 size_t maximum_number_of_frames,
457 bool skip_python_frames) {
458 class LazyBacktrace : public OptimisticLazyValue<std::string> {
459 public:
460 LazyBacktrace(GetBacktraceImpl&& impl) : impl_(std::move(impl)) {}
461
462 private:
463 std::string compute() const override {
464 return impl_.symbolize();
465 }
466
467 GetBacktraceImpl impl_;
468 };
469
470 return std::make_shared<LazyBacktrace>(GetBacktraceImpl{
471 frames_to_skip, maximum_number_of_frames, skip_python_frames});
472 }
473
474 } // namespace c10
475