// // ******************************************************************** // * License and Disclaimer * // * * // * The Geant4 software is copyright of the Copyright Holders of * // * the Geant4 Collaboration. It is provided under the terms and * // * conditions of the Geant4 Software License, included in the file * // * LICENSE and available at http://cern.ch/geant4/license . These * // * include a list of copyright holders. * // * * // * Neither the authors of this software system, nor their employing * // * institutes,nor the agencies providing financial support for this * // * work make any representation or warranty, express or implied, * // * regarding this software system or assume any liability for its * // * use. Please see the license in the file LICENSE and URL above * // * for the full disclaimer and the limitation of liability. * // * * // * This code implementation is the result of the scientific and * // * technical work of the GEANT4 collaboration. * // * By using, copying, modifying or distributing the software (or * // * any work based on the software) you agree to acknowledge its * // * use in resulting scientific publications, and indicate your * // * acceptance of all terms of the Geant4 Software license. * // ******************************************************************** // // G4Backtrace // // Description: // // Prints backtraces after signals are caught. Available on Unix. // // Usage: // A standard set of signals are enabled by default: // // SIGQUIT, SIGILL, SIGABRT, SIGKILL, SIGBUS, SIGSEGV // // These should not interfere with debuggers and/or G4FPEDetection. // In order to turn off handling for one or more signals, one can do: // // G4Backtrace::DefaultSignals() = std::set{}; // G4Backtrace::DefaultSignals() = std::set{ SIGSEGV }; // // and so on, *before* creating the run-manager. After the run-manager // has been created, one should disable the signals: // // G4Backtrace::Disable(G4Backtrace::DefaultSignals()); // // Additionally, at runtime, the environment variable "G4BACKTRACE" can // be set to select a specific set of signals or none, e.g. in bash: // // export G4BACKTRACE="SIGQUIT,SIGSEGV" // export G4BACKTRACE="none" // // The environment variable is case-insensitive and can use any of the // following delimiters: space, comma, semi-colon, colon // // Author: J.Madsen, 19 October 2020 // -------------------------------------------------------------------- #ifndef G4Backtrace_hh #define G4Backtrace_hh 1 #include "G4Types.hh" #include "G4String.hh" #include "G4Threading.hh" #if defined(__APPLE__) || defined(__MACH__) # if !defined(G4MACOS) # define G4MACOS # endif # if !defined(G4UNIX) # define G4UNIX # endif #elif defined(__linux__) || defined(__linux) || defined(linux) || \ defined(__gnu_linux__) # if !defined(G4LINUX) # define G4LINUX # endif # if !defined(G4UNIX) # define G4UNIX # endif #elif defined(__unix__) || defined(__unix) || defined(unix) # if !defined(G4UNIX) # define G4UNIX # endif #endif #if defined(G4UNIX) && !defined(WIN32) # include # include # include #endif #if defined(G4LINUX) # include #endif #include #include #include template using G4ResultOf_t = std::invoke_result_t; // compatible OS and compiler #if defined(G4UNIX) && \ (defined(__GNUC__) || defined(__clang__) || defined(_INTEL_COMPILER)) # if !defined(G4SIGNAL_AVAILABLE) # define G4SIGNAL_AVAILABLE # endif # if !defined(G4DEMANGLE_AVAILABLE) # define G4DEMANGLE_AVAILABLE # endif #endif #if !defined(G4PSIGINFO_AVAILABLE) # if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L # define G4PSIGINFO_AVAILABLE 1 # else # define G4PSIGINFO_AVAILABLE 0 # endif #endif //----------------------------------------------------------------------------// inline G4String G4Demangle(const char* _str) { #if defined(G4DEMANGLE_AVAILABLE) // demangling a string when delimiting G4int _status = 0; char* _ret = ::abi::__cxa_demangle(_str, nullptr, nullptr, &_status); if((_ret != nullptr) && _status == 0) return G4String(const_cast(_ret)); return _str; #else return _str; #endif } //----------------------------------------------------------------------------// inline G4String G4Demangle(const G4String& _str) { return G4Demangle(_str.c_str()); } //----------------------------------------------------------------------------// template inline G4String G4Demangle() { return G4Demangle(typeid(Tp).name()); } //----------------------------------------------------------------------------// // // ONLY IF G4SIGNAL_AVAILABLE // //----------------------------------------------------------------------------// // #if defined(G4SIGNAL_AVAILABLE) // // these are not in the original POSIX.1-1990 standard so we are defining // them in case the OS hasn't // POSIX-1.2001 # ifndef SIGTRAP # define SIGTRAP 5 # endif // not specified in POSIX.1-2001, but nevertheless appears on most other // UNIX systems, where its default action is typically to terminate the // process with a core dump. # ifndef SIGEMT # define SIGEMT 7 # endif // POSIX-1.2001 # ifndef SIGURG # define SIGURG 16 # endif // POSIX-1.2001 # ifndef SIGXCPU # define SIGXCPU 24 # endif // POSIX-1.2001 # ifndef SIGXFSZ # define SIGXFSZ 25 # endif // POSIX-1.2001 # ifndef SIGVTALRM # define SIGVTALRM 26 # endif // POSIX-1.2001 # ifndef SIGPROF # define SIGPROF 27 # endif // POSIX-1.2001 # ifndef SIGINFO # define SIGINFO 29 # endif //----------------------------------------------------------------------------// # include # include # include # include # include # include # include # include # include # include # include # include # include # include //----------------------------------------------------------------------------// class G4Backtrace { public: using sigaction_t = struct sigaction; using exit_action_t = std::function; using frame_func_t = std::function; using signal_set_t = std::set; public: struct actions { using id_entry_t = std::tuple; using id_list_t = std::vector; std::map is_active = {}; std::map current = {}; std::map previous = {}; std::vector exit_actions = {}; const id_list_t identifiers = { id_entry_t("SIGHUP", SIGHUP, "terminal line hangup"), id_entry_t("SIGINT", SIGINT, "interrupt program"), id_entry_t("SIGQUIT", SIGQUIT, "quit program"), id_entry_t("SIGILL", SIGILL, "illegal instruction"), id_entry_t("SIGTRAP", SIGTRAP, "trace trap"), id_entry_t("SIGABRT", SIGABRT, "abort program (formerly SIGIOT)"), id_entry_t("SIGEMT", SIGEMT, "emulate instruction executed"), id_entry_t("SIGFPE", SIGFPE, "floating-point exception"), id_entry_t("SIGKILL", SIGKILL, "kill program"), id_entry_t("SIGBUS", SIGBUS, "bus error"), id_entry_t("SIGSEGV", SIGSEGV, "segmentation violation"), id_entry_t("SIGSYS", SIGSYS, "non-existent system call invoked"), id_entry_t("SIGPIPE", SIGPIPE, "write on a pipe with no reader"), id_entry_t("SIGALRM", SIGALRM, "real-time timer expired"), id_entry_t("SIGTERM", SIGTERM, "software termination signal"), id_entry_t("SIGURG", SIGURG, "urgent condition present on socket"), id_entry_t("SIGSTOP", SIGSTOP, "stop (cannot be caught or ignored)"), id_entry_t("SIGTSTP", SIGTSTP, "stop signal generated from keyboard"), id_entry_t("SIGCONT", SIGCONT, "continue after stop"), id_entry_t("SIGCHLD", SIGCHLD, "child status has changed"), id_entry_t("SIGTTIN", SIGTTIN, "background read attempted from control terminal"), id_entry_t("SIGTTOU", SIGTTOU, "background write attempted to control terminal"), id_entry_t("SIGIO ", SIGIO, "I/O is possible on a descriptor"), id_entry_t("SIGXCPU", SIGXCPU, "cpu time limit exceeded"), id_entry_t("SIGXFSZ", SIGXFSZ, "file size limit exceeded"), id_entry_t("SIGVTALRM", SIGVTALRM, "virtual time alarm"), id_entry_t("SIGPROF", SIGPROF, "profiling timer alarm"), id_entry_t("SIGWINCH", SIGWINCH, "Window size change"), id_entry_t("SIGINFO", SIGINFO, "status request from keyboard"), id_entry_t("SIGUSR1", SIGUSR1, "User defined signal 1"), id_entry_t("SIGUSR2", SIGUSR2, "User defined signal 2") }; }; public: // a functor called for each frame in the backtrace static frame_func_t& FrameFunctor(); // default set of signals static signal_set_t& DefaultSignals(); // the signal handler static void Handler(G4int sig, siginfo_t* sinfo, void* context); // information message about the signal, performs exit-actions // and prints back-trace static void Message(G4int sig, siginfo_t* sinfo, std::ostream&); // calls user-provided functions after signal is caught but before abort static void ExitAction(G4int sig); // enable signals via a string (which is tokenized) static G4int Enable(const std::string&); // enable signals via set of integers, anything less than zero is ignored static G4int Enable(const signal_set_t& _signals = DefaultSignals()); // disable signals static G4int Disable(signal_set_t _signals = {}); // gets the numeric value for a signal name static G4int GetSignal(const std::string&); // provides a description of the signal static std::string Description(G4int sig); // adds an exit action template static void AddExitAction(FuncT&& func); // gets a backtrace of "Depth" frames. The offset parameter is used // to ignore initial frames (such as this function). A callback // can be provided to inspect and/or tweak the frame string template static std::array, Depth> GetMangled( FuncT&& func = FrameFunctor()); // gets a demangled backtrace of "Depth" frames. The offset parameter is // used to ignore initial frames (such as this function). A callback // can be provided to inspect and/or tweak the frame string template static std::array, Depth> GetDemangled( FuncT&& func = FrameFunctor()); private: static actions& GetData() { static auto _instance = actions{}; return _instance; } }; //----------------------------------------------------------------------------// // a functor called for each frame in the backtrace inline G4Backtrace::frame_func_t& G4Backtrace::FrameFunctor() { static frame_func_t _instance = [](const char* inp) { return G4String(inp); }; return _instance; } //----------------------------------------------------------------------------// // default set of signals inline G4Backtrace::signal_set_t& G4Backtrace::DefaultSignals() { static signal_set_t _instance = { SIGQUIT, SIGILL, SIGABRT, SIGKILL, SIGBUS, SIGSEGV }; return _instance; } //----------------------------------------------------------------------------// template inline void G4Backtrace::AddExitAction(FuncT&& func) { GetData().exit_actions.emplace_back(std::forward(func)); } //----------------------------------------------------------------------------// inline void G4Backtrace::ExitAction(G4int sig) { for(auto& itr : GetData().exit_actions) itr(sig); } //----------------------------------------------------------------------------// template inline std::array, Depth> G4Backtrace::GetMangled(FuncT&& func) { static_assert((Depth - Offset) >= 1, "Error Depth - Offset should be >= 1"); using type = G4ResultOf_t; // destination std::array btrace; btrace.fill((std::is_pointer::value) ? nullptr : type{}); // plus one for this stack-frame std::array buffer; // size of returned buffer auto sz = backtrace(buffer.data(), Depth + Offset); // size of relevant data auto n = sz - Offset; // skip ahead (Offset + 1) stack frames char** bsym = backtrace_symbols(buffer.data() + Offset, (G4int)n); // report errors if(bsym == nullptr) perror("backtrace_symbols"); else { for(decltype(n) i = 0; i < n; ++i) btrace[i] = func(bsym[i]); free(bsym); } return btrace; } //----------------------------------------------------------------------------// template inline std::array, Depth> G4Backtrace::GetDemangled(FuncT&& func) { auto demangle_bt = [&](const char* cstr) { auto _trim = [](std::string& _sub, std::size_t& _len) { std::size_t _pos = 0; while((_pos = _sub.find_first_of(' ')) == 0) { _sub = _sub.erase(_pos, 1); --_len; } while((_pos = _sub.find_last_of(' ')) == _sub.length() - 1) { _sub = _sub.substr(0, _sub.length() - 1); --_len; } return _sub; }; auto str = G4Demangle(std::string(cstr)); auto beg = str.find('('); if(beg == std::string::npos) { beg = str.find("_Z"); if(beg != std::string::npos) beg -= 1; } auto end = str.find('+', beg); if(beg != std::string::npos && end != std::string::npos) { auto len = end - (beg + 1); auto sub = str.substr(beg + 1, len); auto dem = G4Demangle(_trim(sub, len)); str = str.replace(beg + 1, len, dem); } else if(beg != std::string::npos) { auto len = str.length() - (beg + 1); auto sub = str.substr(beg + 1, len); auto dem = G4Demangle(_trim(sub, len)); str = str.replace(beg + 1, len, dem); } else if(end != std::string::npos) { auto len = end; auto sub = str.substr(beg, len); auto dem = G4Demangle(_trim(sub, len)); str = str.replace(beg, len, dem); } return func(str.c_str()); }; return GetMangled(demangle_bt); } //----------------------------------------------------------------------------// inline void G4Backtrace::Message(G4int sig, siginfo_t* sinfo, std::ostream& os) { // try to avoid as many dynamic allocations as possible here to avoid // overflowing the signal stack // ignore future signals of this type signal(sig, SIG_IGN); os << "\n### CAUGHT SIGNAL: " << sig << " ### "; if(sinfo != nullptr) os << "address: " << sinfo->si_addr << ", "; os << Description(sig) << ". "; if(sig == SIGSEGV) { if(sinfo != nullptr) { switch(sinfo->si_code) { case SEGV_MAPERR: os << "Address not mapped to object."; break; case SEGV_ACCERR: os << "Invalid permissions for mapped object."; break; default: os << "Unknown segmentation fault error: " << sinfo->si_code << "."; break; } } else { os << "Segmentation fault (unknown)."; } } else if(sig == SIGFPE) { if(sinfo != nullptr) { switch(sinfo->si_code) { case FE_DIVBYZERO: os << "Floating point divide by zero."; break; case FE_OVERFLOW: os << "Floating point overflow."; break; case FE_UNDERFLOW: os << "Floating point underflow."; break; case FE_INEXACT: os << "Floating point inexact result."; break; case FE_INVALID: os << "Floating point invalid operation."; break; default: os << "Unknown floating point exception error: " << sinfo->si_code << "."; break; } } else { os << "Unknown floating point exception"; if(sinfo != nullptr) os << ": " << sinfo->si_code; os << ". "; } } os << '\n'; auto bt = GetMangled<256, 3>([](const char* _s) { return _s; }); char prefix[64]; snprintf(prefix, 64, "[PID=%i, TID=%i]", (G4int) getpid(), (G4int) G4Threading::G4GetThreadId()); std::size_t sz = 0; for(auto& itr : bt) { if(itr == nullptr) break; if(strlen(itr) == 0) break; ++sz; } os << "\nBacktrace:\n"; auto _w = std::log10(sz) + 1; for(std::size_t i = 0; i < sz; ++i) { os << prefix << "[" << std::setw(_w) << std::right << i << '/' << std::setw(_w) << std::right << sz << "]> " << std::left << bt.at(i) << '\n'; } os << std::flush; // exit action could cause more signals to be raise so make sure this is done // after the message has been printed try { ExitAction(sig); } catch(std::exception& e) { std::cerr << "ExitAction(" << sig << ") threw an exception" << std::endl; std::cerr << e.what() << std::endl; } } //----------------------------------------------------------------------------// inline void G4Backtrace::Handler(G4int sig, siginfo_t* sinfo, void*) { Message(sig, sinfo, std::cerr); char msg[1024]; snprintf(msg, 1024, "%s", "\n"); if((sinfo != nullptr) && G4PSIGINFO_AVAILABLE > 0) { # if G4PSIGINFO_AVAILABLE > 0 psiginfo(sinfo, msg); fflush(stdout); fflush(stderr); # endif } else { std::cerr << msg << std::flush; } // ignore any termination signals signal(SIGKILL, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGABRT, SIG_IGN); abort(); } //----------------------------------------------------------------------------// inline G4int G4Backtrace::Enable(const signal_set_t& _signals) { static G4bool _first = true; if(_first) { std::string _msg = "!!! G4Backtrace is activated !!!"; std::stringstream _filler; std::stringstream _spacer; _filler.fill('#'); _filler << std::setw((G4int)_msg.length()) << ""; _spacer << std::setw(10) << ""; std::cout << "\n\n" << _spacer.str() << _filler.str() << "\n" << _spacer.str() << _msg << "\n" << _spacer.str() << _filler.str() << "\n\n" << std::flush; } _first = false; G4int cnt = 0; for(auto& itr : _signals) { if(itr < 0) continue; if(GetData().is_active[itr]) continue; ++cnt; sigfillset(&(GetData().current[itr].sa_mask)); sigdelset(&(GetData().current[itr].sa_mask), itr); GetData().current[itr].sa_sigaction = &Handler; GetData().current[itr].sa_flags = SA_SIGINFO; sigaction(itr, &(GetData().current[itr]), &(GetData().previous[itr])); } return cnt; } //----------------------------------------------------------------------------// inline G4int G4Backtrace::Enable(const std::string& _signals) { if(_signals.empty()) return 0; auto _add_signal = [](std::string sig, signal_set_t& _targ) { if(!sig.empty()) { for(auto& itr : sig) itr = (char)std::toupper(itr); _targ.insert(G4Backtrace::GetSignal(sig)); } }; const std::regex wsp_re("[ ,;:\t\n]+"); auto _maxid = GetData().identifiers.size(); auto _result = std::vector(_maxid, ""); std::copy( std::sregex_token_iterator(_signals.begin(), _signals.end(), wsp_re, -1), std::sregex_token_iterator(), _result.begin()); signal_set_t _sigset{}; for(auto& itr : _result) _add_signal(itr, _sigset); return Enable(_sigset); } //----------------------------------------------------------------------------// inline G4int G4Backtrace::Disable(signal_set_t _signals) { if(_signals.empty()) { for(auto& itr : GetData().is_active) _signals.insert(itr.first); } G4int cnt = 0; for(auto& itr : _signals) { if(itr < 0) continue; if(!GetData().is_active[itr]) continue; ++cnt; sigaction(itr, &(GetData().previous[itr]), nullptr); GetData().current.erase(itr); GetData().is_active[itr] = false; } return cnt; } //----------------------------------------------------------------------------// inline G4int G4Backtrace::GetSignal(const std::string& sid) { for(auto&& itr : GetData().identifiers) { if(std::get<0>(itr) == sid) return std::get<1>(itr); } return -1; } //----------------------------------------------------------------------------// inline std::string G4Backtrace::Description(G4int sig) { for(auto&& itr : GetData().identifiers) { if(std::get<1>(itr) == sig) { std::stringstream ss; ss << " signal = " << std::setw(8) << std::get<0>(itr) << ", value = " << std::setw(4) << std::get<1>(itr) << ", description = " << std::get<2>(itr); return ss.str(); } } std::stringstream ss; ss << " signal = " << std::setw(8) << "unknown" << ", value = " << std::setw(4) << sig; return ss.str(); } //----------------------------------------------------------------------------// #else # include # include # include # include # include # include # include // dummy implementation class G4Backtrace { public: struct fake_siginfo {}; struct fake_sigaction {}; using siginfo_t = fake_siginfo; using sigaction_t = fake_sigaction; using exit_action_t = std::function; using frame_func_t = std::function; using signal_set_t = std::set; public: struct actions { using id_entry_t = std::tuple; using id_list_t = std::vector; std::map is_active = {}; std::map current = {}; std::map previous = {}; std::vector exit_actions = {}; const id_list_t identifiers = {}; }; public: static void Handler(G4int, siginfo_t*, void*) {} static void Message(G4int, siginfo_t*, std::ostream&) {} static void ExitAction(G4int) {} static G4int Enable(const std::string&) { return 0; } static G4int Enable(const signal_set_t& = DefaultSignals()) { return 0; } static G4int Disable(signal_set_t = {}) { return 0; } static G4int GetSignal(const std::string&) { return -1; } static std::string Description(G4int) { return std::string{}; } template static void AddExitAction(FuncT&&) {} template static std::array, Depth> GetMangled( FuncT&& func = FrameFunctor()) { using type = G4ResultOf_t; auto ret = std::array{}; ret.fill(func("")); return ret; } template static std::array, Depth> GetDemangled( FuncT&& func = FrameFunctor()) { using type = G4ResultOf_t; auto ret = std::array{}; ret.fill(func("")); return ret; } // a functor called for each frame in the backtrace static frame_func_t& FrameFunctor() { static frame_func_t _instance = [](const char* _s) { return G4String(_s); }; return _instance; } // default set of signals static signal_set_t& DefaultSignals() { static signal_set_t _instance = {}; return _instance; } static actions& GetData() { static auto _instance = actions{}; return _instance; } }; //----------------------------------------------------------------------------// #endif // G4SIGNAL_AVAILABLE #endif // G4Backtrace_hh