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);
}

    上面的这些接口的实现不复杂,这里就不多说了。

至此,崩溃处理相关的内容就介绍完了,下一篇我们开始介绍异步日志的实现。

posted @ 2022-12-07 16:50  节奏自由  阅读(172)  评论(0编辑  收藏  举报