| #include "c10/util/Backtrace.h" |
| #include "c10/util/Optional.h" |
| #include "c10/util/Type.h" |
| |
| #include <functional> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| #if (defined(__ANDROID__)) || \ |
| (defined(__APPLE__) && \ |
| (TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR || TARGET_OS_IPHONE)) || \ |
| defined(_WIN32) || defined(__EMSCRIPTEN__) |
| // No backtrace on mobile, windows and emscripten platforms. |
| #define SUPPORTS_BACKTRACE 0 |
| #else |
| #define SUPPORTS_BACKTRACE 1 |
| #include <cxxabi.h> |
| #include <execinfo.h> |
| #endif |
| |
| namespace c10 { |
| |
| // TODO: This backtrace retrieval can be implemented on Windows via the Windows |
| // API using `CaptureStackBackTrace` and `SymFromAddr`. |
| // 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://msdn.microsoft.com/en-us/library/windows/desktop/bb204633%28v=vs.85%29.aspx. |
| #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; |
| }; |
| |
| 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; |
| } |
| |
| } // anonymous namespace |
| #endif // SUPPORTS_BACKTRACE |
| |
| std::string get_backtrace( |
| size_t frames_to_skip, |
| size_t maximum_number_of_frames, |
| bool skip_python_frames) { |
| #if SUPPORTS_BACKTRACE |
| |
| // 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 (size_t frame_number = 0; frame_number < callstack.size(); |
| ++frame_number) { |
| 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(); |
| #else // !SUPPORTS_BACKTRACE |
| return "(no backtrace available)"; |
| #endif // SUPPORTS_BACKTRACE |
| } |
| |
| } // namespace c10 |