From 602e135f84beebcf3ebf1108ddad703409143b3e Mon Sep 17 00:00:00 2001 From: Kjell Hedstrom Date: Thu, 10 Sep 2015 06:20:26 -0600 Subject: [PATCH] Added resotre and override signal handler --- example/main_fatal_choice.cpp | 18 +++- src/crashhandler_unix.cpp | 168 +++++++++++++++------------------- src/g3log/crashhandler.hpp | 16 ++++ 3 files changed, 108 insertions(+), 94 deletions(-) diff --git a/example/main_fatal_choice.cpp b/example/main_fatal_choice.cpp index 004532f..dc53a93 100644 --- a/example/main_fatal_choice.cpp +++ b/example/main_fatal_choice.cpp @@ -147,7 +147,8 @@ namespace void SegFaultAttempt_x10000() NOEXCEPT { - deathfunc f = []{Throw(); *(char*)0 = 0; char* ptr = 0; *ptr = 1; AccessViolation();}; + + deathfunc f = []{char* ptr = 0; *ptr = 1; }; Death_x10000(f, "throw uncaught exception... and then some sigsegv calls"); } @@ -293,6 +294,21 @@ int main(int argc, char **argv) g3::initializeLogging(worker.get()); g3::setFatalPreLoggingHook(&breakHere); + + // : Not yet added example of fatal signals that can be overridden + // Example: Running this with SIGTERM turned off, and choosing to "die by SIGTERM" + // will exit the process without catching the signal + // + // Enable the code below will override the signal setup, all signals active except SIGTERM + // g3::overrideSetupSignals({ {SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"},{SIGILL, "SIGILL"}, + // {SIGSEGV, "SIGSEGV"},}); + // + // the code below.... can be used to restore the signal handler again + // In truth: I don't really see how this can be used + // ...... maybe a separate signal handler for sigterm.... and running this in a unit test? + //g3::restoreSignalHandlerToDefault(); + don't compile on purpose + std::future log_file_name = handle->call(&g3::FileSink::fileName); std::cout << "**** G3LOG FATAL EXAMPLE ***\n\n" diff --git a/src/crashhandler_unix.cpp b/src/crashhandler_unix.cpp index 26f648e..d2a525e 100644 --- a/src/crashhandler_unix.cpp +++ b/src/crashhandler_unix.cpp @@ -11,7 +11,7 @@ #include "g3log/logcapture.hpp" #include "g3log/loglevels.hpp" -#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) && !defined(__GNUC__)) // windows and not mingw +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) && !defined(__GNUC__)) #error "crashhandler_unix.cpp used but it's a windows system" #endif @@ -40,10 +40,10 @@ namespace { const std::map kSignals = { {SIGABRT, "SIGABRT"}, - {SIGFPE,"SIGFPE"}, - {SIGILL,"SIGILL"}, - {SIGSEGV,"SIGSEGV"}, - {SIGTERM,"SIGTERM"}, + {SIGFPE, "SIGFPE"}, + {SIGILL, "SIGILL"}, + {SIGSEGV, "SIGSEGV"}, + {SIGTERM, "SIGTERM"}, }; std::map gSignals = kSignals; @@ -56,7 +56,6 @@ namespace { } void restoreSignalHandler(int signal_number) { - std::cerr << "\n\n" << __FUNCTION__ << " " << signal_number << " threadID: " << std::this_thread::get_id() << std::endl; #if !(defined(DISABLE_FATAL_SIGNALHANDLING)) struct sigaction action; memset(&action, 0, sizeof (action)); // @@ -66,22 +65,11 @@ namespace { #endif } - void overrideSetupSignals(const std::map overrideSignals) { - static std::mutex signalLock; - std::lock_guard guard(signalLock); - gSignals = overrideSignals; - for(const auto& sig: kSignals) { - restoreSignalHandler(sig.first); - } - g3::installCrashHandler(); - } - - // Dump of stack,. then exit through g3log background worker // ALL thanks to this thread at StackOverflow. Pretty much borrowed from: // Ref: http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes - void signalHandler(int signal_number, siginfo_t *info, void *unused_context) { + void signalHandler(int signal_number, siginfo_t* info, void* unused_context) { // Only one signal will be allowed past this point if (false == shouldDoExit()) { @@ -103,6 +91,33 @@ namespace { } // message sent to g3LogWorker // wait to die } + + + + // + // Installs FATAL signal handler that is enough to handle most fatal events + // on *NIX systems + void installSignalHandler() { +#if !(defined(DISABLE_FATAL_SIGNALHANDLING)) + struct sigaction action; + memset(&action, 0, sizeof (action)); + sigemptyset(&action.sa_mask); + action.sa_sigaction = &signalHandler; // callback to crashHandler for fatal signals + // sigaction to use sa_sigaction file. ref: http://www.linuxprogrammingblog.com/code-examples/sigaction + action.sa_flags = SA_SIGINFO; + + // do it verbose style - install all signal actions + for (const auto& sig_pair : gSignals) { + if (sigaction(sig_pair.first, &action, nullptr) < 0) { + const std::string error = "sigaction - " + sig_pair.second; + perror(error.c_str()); + } + } +#endif + } + + + } // end anonymous namespace @@ -133,22 +148,22 @@ namespace g3 { /// Generate stackdump. Or in case a stackdump was pre-generated and non-empty just use that one /// i.e. the latter case is only for Windows and test purposes - std::string stackdump(const char *rawdump) { + std::string stackdump(const char* rawdump) { if (nullptr != rawdump && !std::string(rawdump).empty()) { return {rawdump}; } const size_t max_dump_size = 50; - void *dump[max_dump_size]; + void* dump[max_dump_size]; size_t size = backtrace(dump, max_dump_size); - char **messages = backtrace_symbols(dump, size); // overwrite sigaction with caller's address + char** messages = backtrace_symbols(dump, size); // overwrite sigaction with caller's address // dump stack: skip first frame, since that is here std::ostringstream oss; for (size_t idx = 1; idx < size && messages != nullptr; ++idx) { - char *mangled_name = 0, *offset_begin = 0, *offset_end = 0; + char* mangled_name = 0, *offset_begin = 0, *offset_end = 0; // find parantheses and +address offset surrounding mangled name - for (char *p = messages[idx]; *p; ++p) { + for (char* p = messages[idx]; *p; ++p) { if (*p == '(') { mangled_name = p; } else if (*p == '+') { @@ -167,7 +182,7 @@ namespace g3 { *offset_end++ = '\0'; int status; - char *real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status); + char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status); // if demangling is successful, output the demangled function name if (status == 0) { oss << "\n\tstack dump [" << idx << "] " << messages[idx] << " : " << real_name << "+"; @@ -190,102 +205,69 @@ namespace g3 { /// string representation of signal ID - std::string exitReasonName(const LEVELS &level, g3::SignalType fatal_id) { + std::string exitReasonName(const LEVELS& level, g3::SignalType fatal_id) { int signal_number = static_cast(fatal_id); switch (signal_number) { - case SIGABRT: return "SIGABRT"; - break; - case SIGFPE: return "SIGFPE"; - break; - case SIGSEGV: return "SIGSEGV"; - break; - case SIGILL: return "SIGILL"; - break; - case SIGTERM: return "SIGTERM"; - break; - default: - std::ostringstream oss; - oss << "UNKNOWN SIGNAL(" << signal_number << ") for " << level.text; - return oss.str(); + case SIGABRT: return "SIGABRT"; + break; + case SIGFPE: return "SIGFPE"; + break; + case SIGSEGV: return "SIGSEGV"; + break; + case SIGILL: return "SIGILL"; + break; + case SIGTERM: return "SIGTERM"; + break; + default: + std::ostringstream oss; + oss << "UNKNOWN SIGNAL(" << signal_number << ") for " << level.text; + return oss.str(); } } - // KJELL : TODO. The Fatal Message can contain a callback function that depending on OS and test scenario does - // different things. - // exitWithDefaultSignalHandler is called from g3logworke::bgFatal AFTER all the logging sinks have been cleared - // I.e. saving a function that has the value already encapsulated within. - // FatalMessagePtr msgPtr - // Linux/OSX --> msgPtr.get()->ContinueWithFatalExit(); --> exitWithDefaultSignalHandler(int signal_number); - // Windows ..... (if signal) --> exitWithDefaultSignalHandler(int signal_number); - // (if exception) .... - // the calling thread that is in a never-ending loop should break out of that loop - // i.e. an atomic flag should be set - // the next step should then be to re-throw the same exception - // i.e. just call the next exception handler - // we should make sure that 1) g3log exception handler is called BEFORE widows - // it should continue and then be caught in Visual Studios exception handler - // - // - // Triggered by g3log->g3LogWorker after receiving a FATAL trigger // which is LOG(FATAL), CHECK(false) or a fatal signal our signalhandler caught. // --- If LOG(FATAL) or CHECK(false) the signal_number will be SIGABRT - void exitWithDefaultSignalHandler(const LEVELS &level, g3::SignalType fatal_signal_id) { + void exitWithDefaultSignalHandler(const LEVELS& level, g3::SignalType fatal_signal_id) { const int signal_number = static_cast(fatal_signal_id); restoreSignalHandler(signal_number); std::cerr << "\n\n" << __FUNCTION__ << ":" << __LINE__ << ". Exiting due to " << level.text << ", " << signal_number << " \n\n" << std::flush; kill(getpid(), signal_number); - exit(signal_number); - + exit(signal_number); + } } // end g3::internal - // - // Installs FATAL signal handler that is enough to handle most fatal events - // on *NIX systems - void installSignalHandler() { -#if !(defined(DISABLE_FATAL_SIGNALHANDLING)) - struct sigaction action; - memset(&action, 0, sizeof (action)); - sigemptyset(&action.sa_mask); - action.sa_sigaction = &signalHandler; // callback to crashHandler for fatal signals - // sigaction to use sa_sigaction file. ref: http://www.linuxprogrammingblog.com/code-examples/sigaction - action.sa_flags = SA_SIGINFO; - // do it verbose style - install all signal actions - - for (const auto& sig_pair : gSignals) { - if (sigaction(sig_pair.first, &action, nullptr) < 0) { - const std::string error = "sigaction - " + sig_pair.second; - perror(error.c_str()); - } + // This will override the default signal handler setup and instead + // install a custom set of signals to handle + void overrideSetupSignals(const std::map overrideSignals) { + static std::mutex signalLock; + std::lock_guard guard(signalLock); + for (const auto& sig : gSignals) { + restoreSignalHandler(sig.first); } - // if (sigaction(SIGABRT, &action, NULL) < 0) - // perror("sigaction - SIGABRT"); - // if (sigaction(SIGFPE, &action, NULL) < 0) - // perror("sigaction - SIGFPE"); - // if (sigaction(SIGILL, &action, NULL) < 0) - // perror("sigaction - SIGILL"); - // if (sigaction(SIGSEGV, &action, NULL) < 0) - // perror("sigaction - SIGSEGV"); - // if (sigaction(SIGTERM, &action, NULL) < 0) - // perror("sigaction - SIGTERM"); -#endif + gSignals = overrideSignals; + installCrashHandler(); // installs all the signal handling for gSignals + } + + // restores the signal handler back to default + void restoreSignalHandlerToDefault() { + overrideSetupSignals(kSignals); } - - + // installs the signal handling for whatever signal set that is currently active + // If you want to setup your own signal handling then + // You should instead call overrideSetupSignals() void installCrashHandler() { installSignalHandler(); - } // namespace g3::internal - - + } } // end namespace g3 diff --git a/src/g3log/crashhandler.hpp b/src/g3log/crashhandler.hpp index af891b2..1b7995a 100644 --- a/src/g3log/crashhandler.hpp +++ b/src/g3log/crashhandler.hpp @@ -9,6 +9,7 @@ * ============================================================================*/ #include #include +#include #include "g3log/loglevels.hpp" #include "g3log/generated_definitions.hpp" @@ -37,6 +38,21 @@ namespace g3 { void installSignalHandlerForThread(); #else typedef int SignalType; + + /// Probably only needed for unit testing. Resets the signal handling back to default + /// which might be needed in case it was previously overridden + /// The default signals are: SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGTERM + void restoreSignalHandlerToDefault(); + + /// Overrides the existing signal handling for custom signals + /// For example: usage of zcmq relies on its own signal handler for SIGTERM + /// so users of g3log with zcmq should then use the @ref overrideSetupSignals + /// , likely with the original set of signals but with SIGTERM removed + /// + /// call example: + /// g3::overrideSetupSignals({ {SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"},{SIGILL, "SIGILL"}, + // {SIGSEGV, "SIGSEGV"},}); + void overrideSetupSignals(const std::map overrideSignals); #endif