1. 程式人生 > 其它 >easylogging++的那些事(四)原始碼分析(八)崩潰處理相關

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

    上面的這些介面的實現不復雜,這裡就不多說了。

至此,崩潰處理相關的內容就介紹完了,下一篇我們開始介紹非同步日誌的實現。