Linux C/C++ 对于SIGBUS、SIGSEGV等崩溃异常捕获实现

前段时间我们发表过类似得 Try-Catch 实现,但是该实现本身是存在局限性的,因为它们不能无视 SIGSEGV,SIGSYS,SIGTRAP等崩溃信号,对于某些3RD代码根本无法处理(这些代码没有源代码只有库,总不可能去静态反编译在改汇编)或者是短时间无法解决得内存BUG、例如毫无问题得代码在ARM平台会发生结构内存对齐问题(SIGBUS)导致强行崩溃(仅限于恶意崩溃,即运行相同代码+结构上万次以后才会发生SIGBUS提示内存对齐故障)别认为不可能,这在ARM上面存在潜在发生得可能性,解决办法查汇编查代码对所有的结构进行对齐检查,等等。

本文代码对于下文链接得扩展:

C/C++ 11 try-catch-finally 扩展(函数式)_liulilittle的博客-CSDN博客_c++ finally

假设...我们希望:类似以下的代码不要导致程序停止工作那么应该怎么处理?

int64_t llPtr = 0;

char* byPtr = (char*)llPtr;

*byPtr = rand() & 0xff;

... ...

printf("%s\n", "OK!");

我们知道上述代码务必会遭遇 “SIGSEGV” 导致崩溃并且产生 dump 文件/信息(若设置)那么我们不希望这里出现故障应该怎么做?

SIGSEGV 是可以被认为捕获得,有一个偷懒得宏定义叫做 SIG_IGN(忽略信号)但要注意,它的确忽略信号,确保程序不会因为崩溃信号停止工作,但这是有限度的,类似 SIGSEGV、SIGBUS 等信号是无效的,不要考虑这种没有意义得东西,就算要放弃某个信号也不应该是这么粗放式得操作,SIG_IGN 这类型仅适用于程序主动得 raise 得崩溃信号,比如:SIG_PIPE。

OK,那么可以适用最新改进得:“Try-Catch-Finally” 进行处理,但发生异常时可能潜在导致内存泄漏问题。

Try([] {

int64_t llPtr = 0;

char* byPtr = (char*)llPtr;

*byPtr = rand() & 0xff;

}).Catch<std::exception>([] (std::exception& e) {

printf("%s\n", "Error!");

});

printf("%s\n", "OK!");

以上代码是可以正确执行到 OK!部分,并且不会发生内存泄漏问题,但一旦我们在 lambda 内操作关于内存相关的东西就有可能发生异常,比如在函数内复制了一个 shared_ptr 并且在析构以前出现异常,那么 shared_ptr 得引用就无法在减少导致内存资源泄露,那么类似得好办法是不要使用 shared_ptr,或者在可以确定不会发生异常得情况下使用,如果你真不确定还非得头铁用得话,有个办法:weak_ptr 可能很适合您。

潜在发生异常得 lambda 函数是可以捕获类似 shared_ptr 之类的东西,它们并不会引起不能调用捕获 shared_ptr 得析构函数,所以是是线上内存安全的,但是 lambda 内部操作不是内存安全的,这个必须要说清楚!

Try.h

 #pragma once 

#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <setjmp.h>  
#include <unistd.h> 
#include <memory>
#include <exception>
#include <mutex>
#include <unordered_map>

#ifndef MAX_STACK_FRAME
#define MAX_STACK_FRAME 10
#endif

namespace My {
    class Try {
    private:
        typedef sigjmp_buf                                      Context;
        struct Stack {
        private:
            mutable std::shared_ptr<Context>                    rbp;
            mutable Context*                                    rsp;

        public:
            Stack();
            void                                                New() const;
            void                                                Release() const;
            inline Context*                                     Ptr() const {
                return this->rbp.get();
            }
            inline Context*                                     Push() const {
                Context* max = this->rbp.get() + MAX_STACK_FRAME;
                if (this->rsp >= max) {
                    return NULL;
                }
                return this->rsp++;
            }
            inline Context*                                     Pop() const {
                if (this->rsp <= this->rbp.get()) {
                    return NULL;
                }
                return --this->rsp;
            }
            inline Context*                                     Peek() const {
                Context* max = this->rbp.get() + MAX_STACK_FRAME;
                if (this->rsp > max || this->rsp <= this->rbp.get()) {
                    return NULL;
                }
                return this->rsp - 1;
            }
        };
        typedef std::unordered_map<int, Stack>                  StackTable;
        friend class                                            Hosting;
        friend class                                            __TRY_SEH_SIGNAL__;

    public:
        inline Try(const std::function<void()>& block)
            : ___try(block) {
            if (!block) {
                throw std::runtime_error("The block part of the try is not allowed to be Null References");
            }
        }
        inline ~Try() {
            ___try = NULL;
            ___catch = NULL;
            ___finally = NULL;
        }

    public:
        template<typename TException>
        inline Try&                                             Catch() const {
            return Catch<TException>([](TException& e) {});
        }
        template<typename TException>
        inline Try&                                             Catch(const std::function<void(TException& e)>& block) const {
            return Catch<TException>(block, NULL);
        }
        template<typename TException>
        inline Try&                                             Catch(const std::function<void(TException& e)>& block, const std::function<bool(TException& e)>& when) const {
            if (!block) {
                throw std::runtime_error("The block part of the try-catch is not allowed to be Null References");
            }
            std::function<bool(std::exception*)> previous = ___catch;
            ___catch = [block, previous, when](std::exception* e) {
                if (previous) {
                    if (previous(e)) {
                        return true;
                    }
                }
                TException* exception = dynamic_cast<TException*>(e);
                if (!exception) {
                    return false;
                }
                if (when) {
                    if (!when(*exception)) {
                        return false;
                    }
                }
                if (block) {
                    block(*exception);
                }
                return true;
            };
            return const_cast<Try&>(*this);
        }
        inline Try&                                             Finally(const std::function<void()>& block) const {
            ___finally = block;
            return const_cast<Try&>(*this);
        }
        inline void                                             Invoke() const {
            Context* stack_context = Try::PushContext();
            std::exception* stack_exception = NULL;
            try {
                if (!stack_context) {
                    ___try();
                }
                else {
                    if (sigsetjmp(*stack_context, 1) == 0) {
                        ___try();
                    }
                    else {
                        stack_exception = &Try::___exception;
                        if (___catch) {
                            if (___catch(stack_exception)) {
                                stack_exception = NULL;
                            }
                        }
                    }
                }
            }
            catch (std::exception& throwable) {
                stack_exception = &throwable;
                if (___catch) {
                    if (___catch(stack_exception)) {
                        stack_exception = NULL;
                    }
                }
            }
            catch (...) {
                stack_exception = &Try::___exception;
                if (___catch) {
                    if (___catch(stack_exception)) {
                        stack_exception = NULL;
                    }
                }
            }
            if (___finally) {
                ___finally();
            }
            if (stack_context) {
                Try::PopContext();
            }
            if (stack_exception) {
                throw *stack_exception;
            }
        }
        #ifndef ANDROID
        static void                                             PrintStackTrace();
        #endif

    private:
        static Context*                                         PushContext();
        static Context*                                         PopContext();
        static Context*                                         PeekContext();
        static void                                             AllocStack(bool important);
        static void                                             ReleaseStack();

    private:
        mutable std::function<void()>                           ___try;
        mutable std::function<bool(std::exception*)>            ___catch;
        mutable std::function<void()>                           ___finally;
        static std::runtime_error                               ___exception;
        static std::mutex                                       ___syncobj;
        static StackTable                                       ___stack;
        static struct sigaction                                 ___osas[15]; 
    };
}

Try.cpp

#include <stdio.h>
#include <signal.h>
#include <limits.h>
#include <sys/file.h>
#ifndef ANDROID
#include <unwind.h>
#include <execinfo.h>
#include <sys/resource.h>
#endif
#include <My/Try.h>
#include <My/Environment.h>

#ifndef ANDROID
#define MAX_BACKTRACE_SIZE 100
#endif

namespace My {
    class __TRY_SEH_SIGNAL__ {
    public:
        inline __TRY_SEH_SIGNAL__() {
            memset(Try::___osas, 0, sizeof(Try::___osas));
            /*retrieve old and set new handlers*/
            /*restore prevouis signal actions*/
            #ifdef ANDROID
            Watch(35,        0); // FDSCAN(SI_QUEUE)
            #endif
            Watch(SIGBUS,    1);
            Watch(SIGPIPE,   2);
            Watch(SIGFPE,    3);
            Watch(SIGSEGV,   4);
            Watch(SIGILL,    5);
            Watch(SIGTRAP,   6);
            Watch(SIGSYS,    7);
            Watch(SIGQUIT,   8);
            Watch(SIGIOT,    9);
            Watch(SIGUSR1,   10);
            Watch(SIGUSR2,   11);
            Watch(SIGXCPU,   12);
            Watch(SIGXFSZ,   13);
            Watch(SIGSTKFLT, 14);
        }
        inline ~__TRY_SEH_SIGNAL__() {
            struct sigaction* osas = Try::___osas;

            /*restore prevouis signal actions*/
            #ifdef ANDROID
            Unwatch(35,        0); // FDSCAN(SI_QUEUE)
            #endif
            Unwatch(SIGBUS,    1);
            Unwatch(SIGPIPE,   2);
            Unwatch(SIGFPE,    3);
            Unwatch(SIGSEGV,   4);
            Unwatch(SIGILL,    5);
            Unwatch(SIGTRAP,   6);
            Unwatch(SIGSYS,    7);
            Unwatch(SIGQUIT,   8);
            Unwatch(SIGIOT,    9);
            Unwatch(SIGUSR1,   10);
            Unwatch(SIGUSR2,   11);
            Unwatch(SIGXCPU,   12);
            Unwatch(SIGXFSZ,   13);
            Unwatch(SIGSTKFLT, 14);
        }
        
    public:
        inline int                      Watch(int signo, int index) {
            struct sigaction sa;
            struct sigaction* osas = Try::___osas;
            memset(&sa, 0, sizeof(sa));

            /*init new handler struct*/  
            sa.sa_handler = [] (int signo) {
                Try::Context* context = Try::PeekContext();
                if (context) {
                    siglongjmp(*context, 1);
                } else {
                    #ifndef ANDROID
                    Try::PrintStackTrace();
                    #endif
                    signal(signo, SIG_DFL);
                    raise(signo);
                }
            };
            sigemptyset(&sa.sa_mask);  
            sa.sa_flags = 0;
            
            return sigaction(signo, &sa, &osas[index]);
        }
        inline int                      Unwatch(int signo, int index) {
            struct sigaction* osas = Try::___osas;
            return sigaction(signo, &osas[index], NULL);
        }
    }                                   g__TRY_SEH_SIGNAL__;
    std::unordered_map<int, Try::Stack> Try::___stack;
    std::mutex                          Try::___syncobj;
    std::runtime_error                  Try::___exception("Received an error signal thrown by the system or application during code execution.");
    struct sigaction                    Try::___osas[15];

    Try::Stack::Stack() 
        : rbp(NULL)
        , rsp(NULL) {
        this->New();
    }

    void Try::Stack::New() const {
        this->rbp = make_shared_alloc<Context>(MAX_STACK_FRAME);
        this->rsp = this->rbp.get();
    }

    void Try::Stack::Release() const {
        this->rbp = NULL;
        this->rsp = NULL;
    }

    Try::Context* Try::PushContext() {
        std::lock_guard<std::mutex> scope(Try::___syncobj);
        return Try::___stack[Hosting::GetCurrentThreadId()].Push();
    }

    Try::Context* Try::PopContext() {
        std::lock_guard<std::mutex> scope(Try::___syncobj);
        return Try::___stack[Hosting::GetCurrentThreadId()].Pop();
    }

    Try::Context* Try::PeekContext() {
        std::lock_guard<std::mutex> scope(Try::___syncobj);
        return Try::___stack[Hosting::GetCurrentThreadId()].Peek();
    }

    void Try::AllocStack(bool important) {
        std::lock_guard<std::mutex> scope(Try::___syncobj);
        int id = Hosting::GetCurrentThreadId();

        StackTable::iterator kv = Try::___stack.find(id);
        if (kv != Try::___stack.end()) {
            const Stack& stack = kv->second;
            if (important || !stack.Ptr()) {
                stack.New();
            }
        }
        else {
            Stack stack;
            Try::___stack.insert(std::make_pair(id, stack));
        }
    }

    void Try::ReleaseStack() {
        std::lock_guard<std::mutex> scope(Try::___syncobj);
        StackTable::iterator kv = Try::___stack.find(Hosting::GetCurrentThreadId());
        if (kv != Try::___stack.end()) {
            const Stack& stack = kv->second;
            stack.Release();
            Try::___stack.erase(kv);
        }
    }

    #ifndef ANDROID
    void Try::PrintStackTrace() {
        char maps_string[1000] = "0";
        sprintf(maps_string, "cat /proc/%d/maps", getpid());
        system(maps_string); 
        {
            printf("\nStack Addresses:\n");
            _Unwind_Backtrace([](struct _Unwind_Context* context, void* arg) -> _Unwind_Reason_Code {
                uintptr_t pc = _Unwind_GetIP(context);
                if (pc) {
                    printf(" at pc:0x%llx\n", (unsigned long long)pc);
                }
                return _URC_NO_REASON;
            }, 0); // _Unwind_Reason_Code rc, rc == _URC_END_OF_STACK ? 0 : -1;
        }
        void* stack_frames[MAX_BACKTRACE_SIZE];
        int nptrs = backtrace(stack_frames, MAX_BACKTRACE_SIZE);
        {
            printf("\nStack Frame: %d\n", nptrs);
        }
        char** frame_strings = backtrace_symbols(stack_frames, nptrs);
        if (NULL != frame_strings) {
           for (int i = 0; i < nptrs; i++) {
                printf(" at [%02d] %s\n", i, frame_strings[i]);
           }
           free(frame_strings);
        }
    }
    #endif
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值