easylogging++的那些事(四)源码分析(八)崩溃处理相关
在上一篇我们分析了 性能跟踪 的实现,今天我们来看看崩溃处理相关的一些内容。
在 easylogging++的 功能介绍 中我们简要介绍过崩溃处理相关的内容。
easylogging++中崩溃处理相关的主要有两块: 1) 系统信号处理器 2) 堆栈跟踪( 仅仅支持 GCC
)
系统信号处理器
easylogging++支持的信号如下:
1)SIGABRT
信号 (启用ELPP_HANDLE_SIGABRT
宏)
2)SIGFPE
信号
3)SIGILL
信号
4)SIGSEGV
信号
5)SIGINT
信号系统信号处理器的设置主要是通过
CrashHandler
类来实现的:
CrashHandler
类的实现如下:#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) class CrashHandler : base::NoCopy { public: typedef void (*Handler)(int); explicit CrashHandler(bool useDefault); explicit CrashHandler(const Handler &cHandler) { setHandler(cHandler); } void setHandler(const Handler &cHandler); private: Handler m_handler; }; #else class CrashHandler { public: explicit CrashHandler(bool) {} }; #endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)
未启用崩溃处理(未定义
ELPP_FEATURE_ALL
宏或者ELPP_FEATURE_CRASH_LOG
宏)的情况这个类什么也没干。
启用启用崩溃处理的情况:setHandler 接口
// 循环设置信号处理器 void CrashHandler::setHandler(const Handler &cHandler) { m_handler = cHandler; #if defined(ELPP_HANDLE_SIGABRT) int i = 0; // SIGABRT is at base::consts::kCrashSignals[0] #else int i = 1; #endif // defined(ELPP_HANDLE_SIGABRT) for (; i < base::consts::kCrashSignalsCount; ++i) { m_handler = signal(base::consts::kCrashSignals[i].numb, cHandler); } }
base::consts::kCrashSignals
的定义如下:const struct { int numb; const char *name; const char *brief; const char *detail; } kCrashSignals[] = { // NOTE: Do not re-order, if you do please check CrashHandler(bool) constructor and CrashHandler::setHandler(..) {SIGABRT, "SIGABRT", "Abnormal termination", "Program was abnormally terminated."}, {SIGFPE, "SIGFPE", "Erroneous arithmetic operation", "Arithmetic operation issue such as division by zero or operation resulting in overflow."}, {SIGILL, "SIGILL", "Illegal instruction", "Generally due to a corruption in the code or to an attempt to execute data."}, {SIGSEGV, "SIGSEGV", "Invalid access to memory", "Program is trying to read an invalid (unallocated, deleted or corrupted) or inaccessible memory."}, {SIGINT, "SIGINT", "Interactive attention signal", "Interruption generated (generally) by user or operating system."}, }; static const int kCrashSignalsCount = sizeof(kCrashSignals) / sizeof(kCrashSignals[0]);
构造函数
CrashHandler::CrashHandler(bool useDefault) { if (useDefault) { setHandler(defaultCrashHandler); } }
setHandler
接口在前面已经介绍过了,这里就不多说了。
defaultCrashHandler
的定义如下:// 默认的信号处理器 static inline void defaultCrashHandler(int sig) { // 输出堆栈和崩溃信息 base::debug::logCrashReason(sig, true, Level::Fatal, base::consts::kDefaultLoggerId); // 终止程序的运行 base::debug::crashAbort(sig); }
base::debug::logCrashReason
的定义如下:// 输出堆栈和崩溃信息 /// @brief Logs reason of crash from sig static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char *logger) { if (sig == SIGINT && ELPP->hasFlag(el::LoggingFlag::IgnoreSigInt)) { return; } std::stringstream ss; ss << "CRASH HANDLED; "; // 返回指定崩溃信号的详细信息 ss << crashReason(sig); #if ELPP_STACKTRACE // 输出堆栈信息 if (stackTraceIfAvailable) { ss << std::endl << " ======= Backtrace: =========" << std::endl << base::debug::StackTrace(); } #else ELPP_UNUSED(stackTraceIfAvailable); #endif // ELPP_STACKTRACE ELPP_WRITE_LOG(el::base::Writer, level, base::DispatchAction::NormalLog, logger) << ss.str(); }
base::debug::crashAbort
的定义如下:static inline void crashAbort(int sig) { // 终止程序的运行 base::utils::abort(sig, std::string()); }
crashReason
的定义如下:// 返回指定崩溃信号的详细信息 static std::string crashReason(int sig) { std::stringstream ss; bool foundReason = false; for (int i = 0; i < base::consts::kCrashSignalsCount; ++i) { if (base::consts::kCrashSignals[i].numb == sig) { ss << "Application has crashed due to [" << base::consts::kCrashSignals[i].name << "] signal"; if (ELPP->hasFlag(el::LoggingFlag::LogDetailedCrashReason)) { ss << std::endl << " " << base::consts::kCrashSignals[i].brief << std::endl << " " << base::consts::kCrashSignals[i].detail; } foundReason = true; } } if (!foundReason) { ss << "Application has crashed due to unknown signal [" << sig << "]"; } return ss.str(); }
上面的这些接口的实现不复杂,这里就不多说了。
堆栈信息跟踪
堆栈信息跟踪是通过
StackTrace
类实现的。
StackTrace
类的实现如下:class StackTrace : base::NoCopy { public: static const unsigned int kMaxStack = 64; // 堆栈的最多条数 static const unsigned int kStackStart = 2; // 堆栈的起始统计位置 We want to skip c'tor and StackTrace::generateNew() // 堆栈的每一条 class StackTraceEntry { public: StackTraceEntry(std::size_t index, const std::string &loc, const std::string &demang, const std::string &hex, const std::string &addr); StackTraceEntry(std::size_t index, const std::string &loc) : m_index(index), m_location(loc) { } std::size_t m_index; std::string m_location; std::string m_demangled; std::string m_hex; std::string m_addr; friend std::ostream &operator<<(std::ostream &ss, const StackTraceEntry &si); private: StackTraceEntry(void); }; StackTrace(void) { generateNew(); } virtual ~StackTrace(void) { } inline std::vector<StackTraceEntry> &getLatestStack(void) { return m_stack; } friend std::ostream &operator<<(std::ostream &os, const StackTrace &st); private: std::vector<StackTraceEntry> m_stack; // 存放堆栈信息 // 构建堆栈信息 void generateNew(void); }; StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const std::string &loc, const std::string &demang, const std::string &hex, const std::string &addr) : m_index(index), m_location(loc), m_demangled(demang), m_hex(hex), m_addr(addr) { } // 输出运算符用于支持StackTraceEntry类进行日志输出 std::ostream &operator<<(std::ostream &ss, const StackTrace::StackTraceEntry &si) { ss << "[" << si.m_index << "] " << si.m_location << (si.m_hex.empty() ? "" : "+") << si.m_hex << " " << si.m_addr << (si.m_demangled.empty() ? "" : ":") << si.m_demangled; return ss; } // 输出运算符用于支持StackTrace类进行日志输出 std::ostream &operator<<(std::ostream &os, const StackTrace &st) { std::vector<StackTrace::StackTraceEntry>::const_iterator it = st.m_stack.begin(); while (it != st.m_stack.end()) { os << " " << *it++ << "\n"; } return os; }
生成堆栈信息
void StackTrace::generateNew(void) { // glibc头文件"execinfo.h"定义了HAVE_EXECINFO宏以及堆栈相关的接口backtrace和backtrace_symbols #ifdef HAVE_EXECINFO m_stack.clear(); void *stack[kMaxStack]; // backtrace获取当前线程的调用堆栈,获取的信息将会被存放在stack中,返回堆栈的总条数 unsigned int size = backtrace(stack, kMaxStack); // backtrace_symbols将从backtrace函数获取的信息stack转化为一个字符串数组. // backtrace_symbols函数返回值strings是一个指向字符串数组的指针,它的大小同stack相同.每个字符串包含了一个相对于stack中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址 char **strings = backtrace_symbols(stack, size); if (size > kStackStart) { // Skip StackTrace c'tor and generateNew // 依次遍历从kStackStart索引处开始的每条堆栈信息 for (std::size_t i = kStackStart; i < size; ++i) { std::string mangName; std::string location; std::string hex; std::string addr; // entry: 2 crash.cpp.bin 0x0000000101552be5 _ZN2el4base5debug10StackTraceC1Ev + 21 const std::string line(strings[i]); auto p = line.find("_"); if (p != std::string::npos) { mangName = line.substr(p); mangName = mangName.substr(0, mangName.find(" +")); } p = line.find("0x"); if (p != std::string::npos) { addr = line.substr(p); addr = addr.substr(0, addr.find("_")); } // 函数名的处理 // Perform demangling if parsed properly if (!mangName.empty()) { int status = 0; // abi::__cxa_demangle接口在http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html 有详细介绍 char *demangName = abi::__cxa_demangle(mangName.data(), 0, 0, &status); // if demangling is successful, output the demangled function name if (status == 0) { // Success StackTraceEntry entry(i - 1, location, demangName, hex, addr); m_stack.push_back(entry); } else { // Not successful - we will use mangled name StackTraceEntry entry(i - 1, location, mangName, hex, addr); m_stack.push_back(entry); } free(demangName); } else { StackTraceEntry entry(i - 1, line); m_stack.push_back(entry); } } } free(strings); #else ELPP_INTERNAL_INFO(1, "Stacktrace generation not supported for selected compiler"); #endif // ELPP_STACKTRACE }
generateNew
都是对底层一些堆栈信息接口的调用得到的信息逐步进行解析,这块我研究的不多,就不做过多解释了。感兴趣的可以参考相关文档进行进一步的了解。
对外提供的接口
对外提供的设置接口主要是有
el::Helpers
实现的:static inline void setCrashHandler(const el::base::debug::CrashHandler::Handler &crashHandler) { el::elCrashHandler.setHandler(crashHandler); } void Helpers::crashAbort(int sig, const char *sourceFile, unsigned int long line) { std::stringstream ss; ss << base::debug::crashReason(sig).c_str(); ss << " - [Called el::Helpers::crashAbort(" << sig << ")]"; if (sourceFile != nullptr && strlen(sourceFile) > 0) { ss << " - Source: " << sourceFile; if (line > 0) ss << ":" << line; else ss << " (line number not specified)"; } base::utils::abort(sig, ss.str()); } void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char *logger) { el::base::debug::logCrashReason(sig, stackTraceIfAvailable, level, logger); }
上面的这些接口的实现不复杂,这里就不多说了。
至此,崩溃处理相关的内容就介绍完了,下一篇我们开始介绍异步日志的实现。
本文来自博客园,作者:节奏自由,转载请注明原文链接:https://www.cnblogs.com/DesignLife/p/16963562.html