blob: 8a349d2319928f9d7462c7c220ac34065105e7b1 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file backtrace.cc
* \brief Backtrace implementation on non-windows platforms
* \note We use the term "backtrace" to be consistent with python naming convention.
*/
#ifndef _MSC_VER
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/error.h>
#include "./backtrace_utils.h"
#if TVM_FFI_USE_LIBBACKTRACE
#include <backtrace.h>
#include <cxxabi.h>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <mutex>
#if TVM_FFI_BACKTRACE_ON_SEGFAULT
#include <csignal>
#endif
namespace tvm {
namespace ffi {
namespace {
void BacktraceCreateErrorCallback(void*, const char* msg, int) {
std::cerr << "Could not initialize backtrace state: " << msg << std::endl;
}
backtrace_state* BacktraceCreate() {
return backtrace_create_state(nullptr, 1, BacktraceCreateErrorCallback, nullptr);
}
static backtrace_state* _bt_state = BacktraceCreate();
std::string DemangleName(std::string name) {
int status = 0;
size_t length = name.size();
char* demangled_name = abi::__cxa_demangle(name.c_str(), nullptr, &length, &status);
if (demangled_name && status == 0 && length > 0) {
name = demangled_name;
}
if (demangled_name) {
std::free(demangled_name);
}
return name;
}
void BacktraceErrorCallback(void*, const char*, int) {
// do nothing
}
void BacktraceSyminfoCallback(void* data, uintptr_t pc, const char* symname, uintptr_t, uintptr_t) {
auto str = reinterpret_cast<std::string*>(data);
if (symname != nullptr) {
*str = DemangleName(symname);
} else {
std::ostringstream s;
s << "0x" << std::setfill('0') << std::setw(sizeof(uintptr_t) * 2) << std::hex << pc;
*str = s.str();
}
}
int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno,
const char* symbol) {
auto stack_trace = reinterpret_cast<BacktraceStorage*>(data);
std::string symbol_str = "<unknown>";
if (symbol) {
symbol_str = DemangleName(symbol);
} else {
// see if syminfo gives anything
backtrace_syminfo(_bt_state, pc, BacktraceSyminfoCallback, BacktraceErrorCallback, &symbol_str);
}
symbol = symbol_str.data();
if (stack_trace->ExceedBacktraceLimit()) {
return 1;
}
if (stack_trace->stop_at_boundary && DetectFFIBoundary(filename, symbol)) {
return 1;
}
// skip extra frames
if (stack_trace->skip_frame_count > 0) {
stack_trace->skip_frame_count--;
return 0;
}
if (ShouldExcludeFrame(filename, symbol)) {
return 0;
}
stack_trace->Append(filename, symbol, lineno);
return 0;
}
} // namespace
} // namespace ffi
} // namespace tvm
const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno, const char* func,
int cross_ffi_boundary) {
// We collapse the backtrace into a single function
// to simplify the backtrace detection handling (since we need to detect TVMFFIBacktrace)
static thread_local std::string backtrace_str;
static thread_local TVMFFIByteArray backtrace_array;
// pass in current line as here so last line of backtrace is always accurate
tvm::ffi::BacktraceStorage backtrace;
backtrace.stop_at_boundary = cross_ffi_boundary == 0;
if (filename != nullptr && func != nullptr) {
// need to skip TVMFFIBacktrace and the caller function
// which is already included in filename and func
backtrace.skip_frame_count = 2;
if (!tvm::ffi::ShouldExcludeFrame(filename, func)) {
backtrace.Append(filename, func, lineno);
}
}
// libbacktrace eats memory if run on multiple threads at the same time, so we guard against it
if (tvm::ffi::_bt_state != nullptr) {
static std::mutex m;
std::scoped_lock<std::mutex> lock(m);
backtrace_full(tvm::ffi::_bt_state, 0, tvm::ffi::BacktraceFullCallback,
tvm::ffi::BacktraceErrorCallback, &backtrace);
}
backtrace_str = backtrace.GetBacktrace();
backtrace_array.data = backtrace_str.data();
backtrace_array.size = backtrace_str.size();
return &backtrace_array;
}
#if TVM_FFI_BACKTRACE_ON_SEGFAULT
TVM_FFI_COLD_CODE
void TVMFFISegFaultHandler(int sig) {
// Technically we shouldn't do any allocation in a signal handler, but
// Backtrace may allocate. What's the worst it could do? We're already
// crashing.
const TVMFFIByteArray* backtrace = TVMFFIBacktrace(nullptr, 0, nullptr, 1);
std::cerr << "!!!!!!! Segfault encountered !!!!!!!\n"
<< std::string(backtrace->data, backtrace->size) << std::endl;
// Re-raise signal with default handler
struct sigaction act;
std::memset(&act, 0, sizeof(struct sigaction));
act.sa_flags = SA_RESETHAND;
act.sa_handler = SIG_DFL;
sigaction(sig, &act, nullptr);
raise(sig);
}
TVM_FFI_COLD_CODE
__attribute__((constructor)) void TVMFFIInstallSignalHandler() {
// this may override already installed signal handlers
std::signal(SIGSEGV, TVMFFISegFaultHandler);
}
#endif // TVM_FFI_BACKTRACE_ON_SEGFAULT
#else
// fallback implementation simply print out the last trace
const TVMFFIByteArray* TVMFFIBacktrace(const char* filename, int lineno, const char* func,
int cross_ffi_boundary) {
static thread_local std::string backtrace_str;
static thread_local TVMFFIByteArray backtrace_array;
std::ostringstream backtrace_stream;
if (filename != nullptr && func != nullptr) {
// python style backtrace
backtrace_stream << " File \"" << filename << "\", line " << lineno << ", in " << func
<< std::endl;
}
backtrace_str = backtrace_stream.str();
backtrace_array.data = backtrace_str.data();
backtrace_array.size = backtrace_str.size();
return &backtrace_array;
}
#endif // TVM_FFI_USE_LIBBACKTRACE
#endif // _MSC_VER