#include #include #include #include #include #include #include #include #include #ifdef _MSC_VER #include #include #pragma comment(lib, "Dbghelp.lib") #endif #if SUPPORTS_BACKTRACE #include #ifdef C10_ANDROID #include #include #else #include #endif #endif #ifdef FBCODE_CAFFE2 #include #endif namespace c10 { namespace { #ifdef FBCODE_CAFFE2 // For some reason, the stacktrace implementation in fbcode is better than ours, // see https://github.com/pytorch/pytorch/issues/56399 When it's available, just // use that. class GetBacktraceImpl { public: C10_ALWAYS_INLINE GetBacktraceImpl( size_t frames_to_skip, size_t /* maximum_number_of_frames */, bool /* skip_python_frames */) : st_(/*skipFrames=*/frames_to_skip) {} std::string symbolize() const { return st_.toString(); } private: facebook::process::StackTrace st_; }; #elif SUPPORTS_BACKTRACE && defined(C10_ANDROID) struct AndroidBacktraceState { std::vector buffer; }; _Unwind_Reason_Code android_unwind_callback( struct _Unwind_Context* context, void* arg) { AndroidBacktraceState* state = (AndroidBacktraceState*)arg; uintptr_t pc = _Unwind_GetIP(context); if (pc) { state->buffer.emplace_back(reinterpret_cast(pc)); } return _URC_NO_REASON; } class GetBacktraceImpl { public: C10_ALWAYS_INLINE GetBacktraceImpl( size_t /* frames_to_skip */, size_t /* maximum_number_of_frames */, bool /* skip_python_frames */) { _Unwind_Backtrace(android_unwind_callback, &state_); } std::string symbolize() const { std::ostringstream os; int idx = 0; char* demangled = nullptr; size_t length = 0; for (const void* addr : state_.buffer) { const char* symbol = ""; Dl_info info; if (dladdr(addr, &info) && info.dli_sname) { symbol = info.dli_sname; } int status = 0; demangled = __cxxabiv1::__cxa_demangle( /*mangled_name*/ symbol, /*output_buffer*/ demangled, /*length*/ &length, /*status*/ &status); os << " frame #" << idx++ << "\t" << ((demangled != NULL && status == 0) ? demangled : symbol) << "[" << addr << "]\t" << std::endl; } free(demangled); return os.str(); } private: AndroidBacktraceState state_; }; #elif SUPPORTS_BACKTRACE // !defined(C10_ANDROID) struct FrameInformation { /// If available, the demangled name of the function at this frame, else /// whatever (possibly mangled) name we got from `backtrace()`. std::string function_name; /// This is a number in hexadecimal form (e.g. "0xdead") representing the /// offset into the function's machine code at which the function's body /// starts, i.e. skipping the "prologue" that handles stack manipulation and /// other calling convention things. std::string offset_into_function; /// NOTE: In debugger parlance, the "object file" refers to the ELF file that /// the symbol originates from, i.e. either an executable or a library. std::string object_file; }; bool is_python_frame(const FrameInformation& frame) { return frame.object_file == "python" || frame.object_file == "python3" || (frame.object_file.find("libpython") != std::string::npos); } std::optional parse_frame_information( const std::string& frame_string) { FrameInformation frame; // This is the function name in the CXX ABI mangled format, e.g. something // like _Z1gv. Reference: // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling std::string mangled_function_name; #if defined(__GLIBCXX__) // In GLIBCXX, `frame_string` follows the pattern // `(+) // []` auto function_name_start = frame_string.find('('); if (function_name_start == std::string::npos) { return std::nullopt; } function_name_start += 1; auto offset_start = frame_string.find('+', function_name_start); if (offset_start == std::string::npos) { return std::nullopt; } offset_start += 1; const auto offset_end = frame_string.find(')', offset_start); if (offset_end == std::string::npos) { return std::nullopt; } frame.object_file = frame_string.substr(0, function_name_start - 1); frame.offset_into_function = frame_string.substr(offset_start, offset_end - offset_start); // NOTE: We don't need to parse the return address because // we already have it from the call to `backtrace()`. mangled_function_name = frame_string.substr( function_name_start, (offset_start - 1) - function_name_start); #elif defined(_LIBCPP_VERSION) // In LIBCXX, The pattern is // ` + // ` std::string skip; std::istringstream input_stream(frame_string); // operator>>() does not fail -- if the input stream is corrupted, the // strings will simply be empty. input_stream >> skip >> frame.object_file >> skip >> mangled_function_name >> skip >> frame.offset_into_function; #else #warning Unknown standard library, backtraces may have incomplete debug information return std::nullopt; #endif // defined(__GLIBCXX__) // Some system-level functions don't have sufficient debug information, so // we'll display them as "". They'll still have a return // address and other pieces of information. if (mangled_function_name.empty()) { frame.function_name = ""; return frame; } frame.function_name = demangle(mangled_function_name.c_str()); return frame; } class GetBacktraceImpl { public: C10_ALWAYS_INLINE GetBacktraceImpl( size_t frames_to_skip, size_t maximum_number_of_frames, bool skip_python_frames) : skip_python_frames_(skip_python_frames), callstack_(frames_to_skip + maximum_number_of_frames, nullptr) { // We always skip this frame (backtrace). frames_to_skip += 1; // backtrace() gives us a list of return addresses in the current call // stack. NOTE: As per man (3) backtrace it can never fail // (http://man7.org/linux/man-pages/man3/backtrace.3.html). auto number_of_frames = static_cast( ::backtrace(callstack_.data(), static_cast(callstack_.size()))); // Skip as many frames as requested. frames_to_skip = std::min(frames_to_skip, number_of_frames); number_of_frames -= frames_to_skip; callstack_.erase( callstack_.begin(), callstack_.begin() + static_cast(frames_to_skip)); callstack_.resize(number_of_frames); } std::string symbolize() const { // `backtrace_symbols` takes the return addresses obtained from // `backtrace()` and fetches string representations of each stack. // Unfortunately it doesn't return a struct of individual pieces of // information but a concatenated string, so we'll have to parse the string // after. NOTE: The array returned by `backtrace_symbols` is malloc'd and // must be manually freed, but not the strings inside the array. std::unique_ptr> raw_symbols( ::backtrace_symbols( callstack_.data(), static_cast(callstack_.size())), /*deleter=*/free); const std::vector symbols( raw_symbols.get(), raw_symbols.get() + callstack_.size()); // The backtrace string goes into here. std::ostringstream stream; // Toggles to true after the first skipped python frame. bool has_skipped_python_frames = false; for (const auto frame_number : c10::irange(callstack_.size())) { const auto frame = parse_frame_information(symbols[frame_number]); if (skip_python_frames_ && frame && is_python_frame(*frame)) { if (!has_skipped_python_frames) { stream << "\n"; has_skipped_python_frames = true; } continue; } // frame #: stream << "frame #" << frame_number << ": "; if (frame) { // + ( in ) stream << frame->function_name << " + " << frame->offset_into_function << " (" << callstack_[frame_number] << " in " << frame->object_file << ")\n"; } else { // In the edge-case where we couldn't parse the frame string, we can // just use it directly (it may have a different format). stream << symbols[frame_number] << "\n"; } } return stream.str(); } private: const bool skip_python_frames_; std::vector callstack_; }; #elif defined(_MSC_VER) // !SUPPORTS_BACKTRACE const int max_name_len = 256; std::string get_module_base_name(void* addr) { HMODULE h_module; char module[max_name_len]; strcpy(module, ""); GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)addr, &h_module); if (h_module != NULL) { GetModuleFileNameA(h_module, module, max_name_len); } char* last_slash_pos = strrchr(module, '\\'); if (last_slash_pos) { std::string module_base_name(last_slash_pos + 1); return module_base_name; } else { std::string module_base_name(module); return module_base_name; } } class SymbolHelper { public: static SymbolHelper& getInstance() { static SymbolHelper instance; return instance; } bool inited = false; HANDLE process; private: SymbolHelper() { process = GetCurrentProcess(); DWORD flags = SymGetOptions(); SymSetOptions(flags | SYMOPT_DEFERRED_LOADS); inited = SymInitialize(process, NULL, TRUE); } ~SymbolHelper() { if (inited) { SymCleanup(process); } } public: SymbolHelper(SymbolHelper const&) = delete; void operator=(SymbolHelper const&) = delete; }; // This backtrace retrieval is implemented on Windows via the Windows API using // `CaptureStackBackTrace`, `SymFromAddr` and `SymGetLineFromAddr64`. // https://stackoverflow.com/questions/5693192/win32-backtrace-from-c-code // https://stackoverflow.com/questions/26398064/counterpart-to-glibcs-backtrace-and-backtrace-symbols-on-windows // https://docs.microsoft.com/en-us/windows/win32/debug/capturestackbacktrace // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symfromaddr // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symgetlinefromaddr64 // TODO: Support skipping python frames class GetBacktraceImpl { public: C10_ALWAYS_INLINE GetBacktraceImpl( size_t frames_to_skip, size_t maximum_number_of_frames, bool /* skip_python_frames */) : back_trace_(new void*[maximum_number_of_frames]) { // We always skip this frame (backtrace). frames_to_skip += 1; // Get the frames n_frame_ = CaptureStackBackTrace( static_cast(frames_to_skip), static_cast(maximum_number_of_frames), back_trace_.get(), NULL); } std::string symbolize() const { DWORD64 displacement; DWORD disp; std::unique_ptr line; char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; PSYMBOL_INFO p_symbol = (PSYMBOL_INFO)buffer; bool with_symbol = false; bool with_line = false; // The backtrace string goes into here. std::ostringstream stream; // Initialize symbols if necessary SymbolHelper& sh = SymbolHelper::getInstance(); for (USHORT i_frame = 0; i_frame < n_frame_; ++i_frame) { // Get the address and the name of the symbol if (sh.inited) { p_symbol->SizeOfStruct = sizeof(SYMBOL_INFO); p_symbol->MaxNameLen = MAX_SYM_NAME; with_symbol = SymFromAddr( sh.process, (ULONG64)back_trace_[i_frame], &displacement, p_symbol); } // Get the line number and the module if (sh.inited) { line.reset(new IMAGEHLP_LINE64()); line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); with_line = SymGetLineFromAddr64( sh.process, (ULONG64)back_trace_[i_frame], &disp, line.get()); } // Get the module basename std::string module = get_module_base_name(back_trace_[i_frame]); // The pattern on Windows is // ` // ! [ @ ] stream << std::setfill('0') << std::setw(16) << std::uppercase << std::hex << back_trace_[i_frame] << std::dec; if (with_symbol) { stream << std::setfill('0') << std::setw(16) << std::uppercase << std::hex << p_symbol->Address << std::dec << " " << module << "!" << p_symbol->Name; } else { stream << " " << module << "!"; } stream << " ["; if (with_line) { stream << line->FileName << " @ " << line->LineNumber; } else { stream << " @ "; } stream << "]" << std::endl; } return stream.str(); } private: std::unique_ptr back_trace_; USHORT n_frame_; }; #else class GetBacktraceImpl { public: C10_ALWAYS_INLINE GetBacktraceImpl( size_t /* frames_to_skip */, size_t /* maximum_number_of_frames */, bool /* skip_python_frames */) {} std::string symbolize() const { return "(no backtrace available)"; } }; #endif } // namespace std::string get_backtrace( size_t frames_to_skip, size_t maximum_number_of_frames, bool skip_python_frames) { return GetBacktraceImpl{ frames_to_skip, maximum_number_of_frames, skip_python_frames} .symbolize(); } Backtrace get_lazy_backtrace( size_t frames_to_skip, size_t maximum_number_of_frames, bool skip_python_frames) { class LazyBacktrace : public OptimisticLazyValue { public: LazyBacktrace(GetBacktraceImpl&& impl) : impl_(std::move(impl)) {} private: std::string compute() const override { return impl_.symbolize(); } GetBacktraceImpl impl_; }; return std::make_shared(GetBacktraceImpl{ frames_to_skip, maximum_number_of_frames, skip_python_frames}); } } // namespace c10