| #include <c10/util/Backtrace.h> |
| #include <c10/util/Optional.h> |
| #include <c10/util/Type.h> |
| #include <c10/util/irange.h> |
| |
| #include <functional> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #ifdef _MSC_VER |
| #include <c10/util/win32-headers.h> |
| #include <iomanip> |
| #pragma comment(lib, "Dbghelp.lib") |
| #endif |
| |
| #if SUPPORTS_BACKTRACE |
| #include <cxxabi.h> |
| #ifdef C10_ANDROID |
| #include <dlfcn.h> |
| #include <unwind.h> |
| #else |
| #include <execinfo.h> |
| #endif |
| #endif |
| |
| #ifdef FBCODE_CAFFE2 |
| #include <common/process/StackTrace.h> |
| #endif |
| |
| namespace c10 { |
| |
| #if SUPPORTS_BACKTRACE && defined(C10_ANDROID) |
| |
| struct AndroidBacktraceState { |
| std::vector<void*> 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<void*>(pc)); |
| } |
| return _URC_NO_REASON; |
| } |
| |
| void dump_stack( |
| std::ostream& os, |
| size_t frames_to_skip, |
| size_t maximum_number_of_frames) { |
| AndroidBacktraceState state; |
| |
| _Unwind_Backtrace(android_unwind_callback, &state); |
| |
| 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); |
| } |
| |
| #endif /* SUPPORTS_BACKTRACE && defined(C10_ANDROID) */ |
| |
| #if SUPPORTS_BACKTRACE |
| namespace { |
| |
| 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; |
| }; |
| |
| #ifndef C10_ANDROID |
| bool is_python_frame(const FrameInformation& frame) { |
| return frame.object_file == "python" || frame.object_file == "python3" || |
| (frame.object_file.find("libpython") != std::string::npos); |
| } |
| |
| c10::optional<FrameInformation> 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 |
| // `<object-file>(<mangled-function-name>+<offset-into-function>) |
| // [<return-address>]` |
| |
| auto function_name_start = frame_string.find("("); |
| if (function_name_start == std::string::npos) { |
| return c10::nullopt; |
| } |
| function_name_start += 1; |
| |
| auto offset_start = frame_string.find('+', function_name_start); |
| if (offset_start == std::string::npos) { |
| return c10::nullopt; |
| } |
| offset_start += 1; |
| |
| const auto offset_end = frame_string.find(')', offset_start); |
| if (offset_end == std::string::npos) { |
| return c10::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 |
| // `<frame number> <object-file> <return-address> <mangled-function-name> + |
| // <offset-into-function>` |
| 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 c10::nullopt; |
| #endif // defined(__GLIBCXX__) |
| |
| // Some system-level functions don't have sufficient debug information, so |
| // we'll display them as "<unknown function>". They'll still have a return |
| // address and other pieces of information. |
| if (mangled_function_name.empty()) { |
| frame.function_name = "<unknown function>"; |
| return frame; |
| } |
| |
| frame.function_name = demangle(mangled_function_name.c_str()); |
| return frame; |
| } |
| #endif /* !defined(C10_ANDROID) */ |
| } // anonymous namespace |
| #elif defined(_MSC_VER) |
| namespace { |
| 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; |
| }; |
| } // anonymous namespace |
| #endif // SUPPORTS_BACKTRACE |
| |
| std::string get_backtrace( |
| size_t frames_to_skip, |
| size_t maximum_number_of_frames, |
| bool skip_python_frames) { |
| #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. |
| facebook::process::StackTrace st; |
| return st.toString(); |
| |
| #elif SUPPORTS_BACKTRACE && !defined(C10_ANDROID) |
| |
| // We always skip this frame (backtrace). |
| frames_to_skip += 1; |
| |
| std::vector<void*> callstack( |
| frames_to_skip + maximum_number_of_frames, nullptr); |
| // 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 = |
| ::backtrace(callstack.data(), static_cast<int>(callstack.size())); |
| |
| // Skip as many frames as requested. This is not efficient, but the sizes here |
| // are small and it makes the code nicer and safer. |
| for (; frames_to_skip > 0 && number_of_frames > 0; |
| --frames_to_skip, --number_of_frames) { |
| callstack.erase(callstack.begin()); |
| } |
| |
| // `number_of_frames` is strictly less than the current capacity of |
| // `callstack`, so this is just a pointer subtraction and makes the subsequent |
| // code safer. |
| callstack.resize(static_cast<size_t>(number_of_frames)); |
| |
| // `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<char*, std::function<void(char**)>> raw_symbols( |
| ::backtrace_symbols(callstack.data(), static_cast<int>(callstack.size())), |
| /*deleter=*/free); |
| const std::vector<std::string> 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 << "<omitting python frames>\n"; |
| has_skipped_python_frames = true; |
| } |
| continue; |
| } |
| |
| // frame #<number>: |
| stream << "frame #" << frame_number << ": "; |
| |
| if (frame) { |
| // <function_name> + <offset> (<return-address> in <object-file>) |
| 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(); |
| |
| #elif SUPPORTS_BACKTRACE && defined(C10_ANDROID) |
| |
| std::ostringstream oss; |
| dump_stack(oss, frames_to_skip, maximum_number_of_frames); |
| return oss.str().c_str(); |
| |
| #elif defined(_MSC_VER) // !SUPPORTS_BACKTRACE |
| // 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 |
| |
| // We always skip this frame (backtrace). |
| frames_to_skip += 1; |
| |
| DWORD64 displacement; |
| DWORD disp; |
| std::unique_ptr<IMAGEHLP_LINE64> line; |
| |
| char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; |
| PSYMBOL_INFO p_symbol = (PSYMBOL_INFO)buffer; |
| |
| std::unique_ptr<void*[]> back_trace(new void*[maximum_number_of_frames]); |
| bool with_symbol = false; |
| bool with_line = false; |
| |
| // The backtrace string goes into here. |
| std::ostringstream stream; |
| |
| // Get the frames |
| const USHORT n_frame = CaptureStackBackTrace( |
| static_cast<DWORD>(frames_to_skip), |
| static_cast<DWORD>(maximum_number_of_frames), |
| back_trace.get(), |
| NULL); |
| |
| // 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 |
| // `<return-address> <symbol-address> |
| // <module-name>!<demangled-function-name> [<file-name> @ <line-number>] |
| 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 << " <unknown symbol address> " << module << "!<unknown symbol>"; |
| } |
| stream << " ["; |
| if (with_line) { |
| stream << line->FileName << " @ " << line->LineNumber; |
| } else { |
| stream << "<unknown file> @ <unknown line number>"; |
| } |
| stream << "]" << std::endl; |
| } |
| |
| return stream.str(); |
| #else // !SUPPORTS_BACKTRACE && !_WIN32 |
| return "(no backtrace available)"; |
| #endif // SUPPORTS_BACKTRACE |
| } |
| |
| } // namespace c10 |