diff --git a/CMakeLists.txt b/CMakeLists.txt index ff448b9..f4bced8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ # 2010 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes # with no warranties. This code is yours to share, use and modify with no # strings attached and no restrictions or obligations. -# +# # For more information see g3log/LICENSE or refer refer to http://unlicense.org # ============================================================================*/ - + # Below are details for compiling on Windows and Linux # by default only an example g3log binary is created # the performance and unit tests creation can be enabled by switching their diff --git a/example/main_fatal_choice.cpp b/example/main_fatal_choice.cpp index 004532f..d0101f0 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"); } @@ -292,7 +293,6 @@ int main(int argc, char **argv) auto handle= worker->addDefaultLogger(argv[0], path_to_log_file); g3::initializeLogging(worker.get()); g3::setFatalPreLoggingHook(&breakHere); - 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 4324213..b04da63 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 @@ -26,6 +26,8 @@ #include #include #include +#include +#include // Linux/Clang, OSX/Clang, OSX/gcc #if (defined(__clang__) || defined(__APPLE__)) @@ -36,6 +38,18 @@ namespace { + + const std::map kSignals = { + {SIGABRT, "SIGABRT"}, + {SIGFPE, "SIGFPE"}, + {SIGILL, "SIGILL"}, + {SIGSEGV, "SIGSEGV"}, + {SIGTERM, "SIGTERM"}, + }; + + std::map gSignals = kSignals; + + bool shouldDoExit() { static std::atomic firstExit{0}; auto const count = firstExit.fetch_add(1, std::memory_order_relaxed); @@ -43,7 +57,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)); // @@ -53,10 +66,11 @@ namespace { #endif } + // 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()) { @@ -78,6 +92,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 @@ -108,22 +149,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 == '+') { @@ -142,7 +183,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 << "+"; @@ -165,93 +206,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 - 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 + // 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); + } + + 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 diff --git a/test_unit/test_io.cpp b/test_unit/test_io.cpp index 7300b64..73de95f 100644 --- a/test_unit/test_io.cpp +++ b/test_unit/test_io.cpp @@ -312,6 +312,57 @@ TEST(LogTest, LOGF__FATAL) { } +TEST(LogTest, FatalSIGTERM__UsingDefaultHandler) { + RestoreFileLogger logger(log_directory); + g_fatal_counter.store(0); + g3::setFatalPreLoggingHook(fatalCounter); + raise(SIGTERM); + logger.reset(); + EXPECT_EQ(g_fatal_counter.load(), size_t{1}); +} + +namespace { + std::atomic customFatalCounter = {0}; + std::atomic lastEncounteredSignal = {0}; + void customSignalHandler(int signal_number, siginfo_t* info, void* unused_context) { + lastEncounteredSignal.store(signal_number); + ++customFatalCounter; + } + + + + void installCustomSIGTERM () { + struct sigaction action; + memset(&action, 0, sizeof (action)); + sigemptyset(&action.sa_mask); + action.sa_sigaction = &customSignalHandler; + action.sa_flags = SA_SIGINFO; + sigaction(SIGTERM, &action, nullptr); + } + +} // anonymous + +TEST(LogTest, FatalSIGTERM__UsingCustomHandler) { + RestoreFileLogger logger(log_directory); + g_fatal_counter.store(0); + g3::setFatalPreLoggingHook(fatalCounter); + installCustomSIGTERM(); + g3::overrideSetupSignals({ {SIGABRT, "SIGABRT"}, {SIGFPE, "SIGFPE"}, {SIGILL, "SIGILL"}}); + + installCustomSIGTERM(); + EXPECT_EQ(customFatalCounter, 0); + EXPECT_EQ(lastEncounteredSignal.load(), 0); + + + raise(SIGTERM); + logger.reset(); + EXPECT_EQ(g_fatal_counter.load(), size_t{0}); + EXPECT_EQ(lastEncounteredSignal.load(), SIGTERM); + EXPECT_EQ(customFatalCounter, 1); +} + + + TEST(LogTest, LOG_preFatalLogging_hook) { { RestoreFileLogger logger(log_directory);