mirror of
https://github.com/KjellKod/g3log.git
synced 2025-04-21 00:11:51 +02:00
275 lines
9.8 KiB
C++
275 lines
9.8 KiB
C++
/** ==========================================================================
|
|
* 2011 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
|
|
* ============================================================================*/
|
|
|
|
#include "g3log/crashhandler.hpp"
|
|
#include "g3log/logmessage.hpp"
|
|
#include "g3log/logcapture.hpp"
|
|
#include "g3log/loglevels.hpp"
|
|
|
|
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) && !defined(__GNUC__))
|
|
#error "crashhandler_unix.cpp used but it's a windows system"
|
|
#endif
|
|
|
|
|
|
#include <csignal>
|
|
#include <cstring>
|
|
#include <unistd.h>
|
|
#include <execinfo.h>
|
|
#include <cxxabi.h>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <map>
|
|
#include <mutex>
|
|
|
|
// Linux/Clang, OSX/Clang, OSX/gcc
|
|
#if (defined(__clang__) || defined(__APPLE__))
|
|
#include <sys/ucontext.h>
|
|
#else
|
|
#include <ucontext.h>
|
|
#endif
|
|
|
|
|
|
namespace {
|
|
|
|
const std::map<int, std::string> kSignals = {
|
|
{SIGABRT, "SIGABRT"},
|
|
{SIGFPE, "SIGFPE"},
|
|
{SIGILL, "SIGILL"},
|
|
{SIGSEGV, "SIGSEGV"},
|
|
{SIGTERM, "SIGTERM"},
|
|
};
|
|
|
|
std::map<int, std::string> gSignals = kSignals;
|
|
|
|
|
|
bool shouldDoExit() {
|
|
static std::atomic<uint64_t> firstExit{0};
|
|
auto const count = firstExit.fetch_add(1, std::memory_order_relaxed);
|
|
return (0 == count);
|
|
}
|
|
|
|
void restoreSignalHandler(int signal_number) {
|
|
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
|
|
struct sigaction action;
|
|
memset(&action, 0, sizeof (action)); //
|
|
sigemptyset(&action.sa_mask);
|
|
action.sa_handler = SIG_DFL; // take default action for the signal
|
|
sigaction(signal_number, &action, NULL);
|
|
#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) {
|
|
|
|
// Only one signal will be allowed past this point
|
|
if (false == shouldDoExit()) {
|
|
while (true) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
}
|
|
}
|
|
|
|
using namespace g3::internal;
|
|
{
|
|
const auto dump = stackdump();
|
|
std::ostringstream fatal_stream;
|
|
const auto fatal_reason = exitReasonName(g3::internal::FATAL_SIGNAL, signal_number);
|
|
fatal_stream << "Received fatal signal: " << fatal_reason;
|
|
fatal_stream << "(" << signal_number << ")\tPID: " << getpid() << std::endl;
|
|
fatal_stream << "\n***** SIGNAL " << fatal_reason << "(" << signal_number << ")" << std::endl;
|
|
LogCapture trigger(FATAL_SIGNAL, static_cast<g3::SignalType>(signal_number), dump.c_str());
|
|
trigger.stream() << fatal_stream.str();
|
|
} // 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Redirecting and using signals. In case of fatal signals g3log should log the fatal signal
|
|
// and flush the log queue and then "rethrow" the signal to exit
|
|
namespace g3 {
|
|
// References:
|
|
// sigaction : change the default action if a specific signal is received
|
|
// http://linux.die.net/man/2/sigaction
|
|
// http://publib.boulder.ibm.com/infocenter/aix/v6r1/index.jsp?topic=%2Fcom.ibm.aix.basetechref%2Fdoc%2Fbasetrf2%2Fsigaction.html
|
|
//
|
|
// signal: http://linux.die.net/man/7/signal and
|
|
// http://msdn.microsoft.com/en-us/library/xdkz3x12%28vs.71%29.asp
|
|
//
|
|
// memset + sigemptyset: Maybe unnecessary to do both but there seems to be some confusion here
|
|
// ,plenty of examples when both or either are used
|
|
// http://stackoverflow.com/questions/6878546/why-doesnt-parent-process-return-to-the-exact-location-after-handling-signal_number
|
|
namespace internal {
|
|
|
|
bool shouldBlockForFatalHandling() {
|
|
return true; // For windows we will after fatal processing change it to false
|
|
}
|
|
|
|
|
|
/// 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) {
|
|
if (nullptr != rawdump && !std::string(rawdump).empty()) {
|
|
return {rawdump};
|
|
}
|
|
|
|
const size_t max_dump_size = 50;
|
|
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
|
|
|
|
// 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;
|
|
// find parantheses and +address offset surrounding mangled name
|
|
for (char* p = messages[idx]; *p; ++p) {
|
|
if (*p == '(') {
|
|
mangled_name = p;
|
|
} else if (*p == '+') {
|
|
offset_begin = p;
|
|
} else if (*p == ')') {
|
|
offset_end = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the line could be processed, attempt to demangle the symbol
|
|
if (mangled_name && offset_begin && offset_end &&
|
|
mangled_name < offset_begin) {
|
|
*mangled_name++ = '\0';
|
|
*offset_begin++ = '\0';
|
|
*offset_end++ = '\0';
|
|
|
|
int 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 << "+";
|
|
oss << offset_begin << offset_end << std::endl;
|
|
}// otherwise, output the mangled function name
|
|
else {
|
|
oss << "\tstack dump [" << idx << "] " << messages[idx] << mangled_name << "+";
|
|
oss << offset_begin << offset_end << std::endl;
|
|
}
|
|
free(real_name); // mallocated by abi::__cxa_demangle(...)
|
|
} else {
|
|
// no demangling done -- just dump the whole line
|
|
oss << "\tstack dump [" << idx << "] " << messages[idx] << std::endl;
|
|
}
|
|
} // END: for(size_t idx = 1; idx < size && messages != nullptr; ++idx)
|
|
free(messages);
|
|
return oss.str();
|
|
}
|
|
|
|
|
|
|
|
/// string representation of signal ID
|
|
std::string exitReasonName(const LEVELS& level, g3::SignalType fatal_id) {
|
|
|
|
int signal_number = static_cast<int>(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();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
const int signal_number = static_cast<int>(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);
|
|
|
|
}
|
|
} // end g3::internal
|
|
|
|
|
|
// This will override the default signal handler setup and instead
|
|
// install a custom set of signals to handle
|
|
void overrideSetupSignals(const std::map<int, std::string> overrideSignals) {
|
|
static std::mutex signalLock;
|
|
std::lock_guard<std::mutex> 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();
|
|
}
|
|
} // end namespace g3
|
|
|