mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
Follows #137983 Pull Request resolved: https://github.com/pytorch/pytorch/pull/138403 Approved by: https://github.com/ezyang
480 lines
14 KiB
C++
480 lines
14 KiB
C++
#include <c10/macros/Macros.h>
|
|
#include <c10/util/Backtrace.h>
|
|
#include <c10/util/Type.h>
|
|
#include <c10/util/irange.h>
|
|
#include <optional>
|
|
|
|
#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
|
|
C10_CLANG_DIAGNOSTIC_PUSH()
|
|
C10_CLANG_DIAGNOSTIC_IGNORE("-Wdeprecated-dynamic-exception-spec")
|
|
#include <cxxabi.h>
|
|
C10_CLANG_DIAGNOSTIC_POP()
|
|
#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 {
|
|
|
|
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<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;
|
|
}
|
|
|
|
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<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 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
|
|
// `<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 std::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;
|
|
}
|
|
|
|
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<size_t>(
|
|
::backtrace(callstack_.data(), static_cast<int>(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<ssize_t>(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<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();
|
|
}
|
|
|
|
private:
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
|
|
const bool skip_python_frames_;
|
|
std::vector<void*> 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<DWORD>(frames_to_skip),
|
|
static_cast<DWORD>(maximum_number_of_frames),
|
|
back_trace_.get(),
|
|
NULL);
|
|
}
|
|
|
|
std::string symbolize() const {
|
|
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;
|
|
|
|
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
|
|
// `<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();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<void*[]> 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<std::string> {
|
|
public:
|
|
LazyBacktrace(GetBacktraceImpl&& impl) : impl_(std::move(impl)) {}
|
|
|
|
private:
|
|
std::string compute() const override {
|
|
return impl_.symbolize();
|
|
}
|
|
|
|
GetBacktraceImpl impl_;
|
|
};
|
|
|
|
return std::make_shared<LazyBacktrace>(GetBacktraceImpl{
|
|
frames_to_skip, maximum_number_of_frames, skip_python_frames});
|
|
}
|
|
|
|
} // namespace c10
|