Linux C/C + + implementation of crash exception capture for SIGBUS, SIGSEGV, etc

Posted by sneakyimp on Fri, 28 Jan 2022 17:15:13 +0100

Some time ago, we published a similar try catch implementation, but the implementation itself has limitations, because they can't ignore SIGSEGV, SIGSYS, sigrap and other crash signals, and can't process some 3RD codes at all (these codes have no source code, only libraries, and it's always impossible to statically decompile and reassemble) or memory bugs that can't be solved in a short time For example, the structure memory alignment problem (SIGBUS) of the code without problems will occur on the ARM platform, resulting in forced crash (only limited to malicious crash, that is, the SIGBUS prompt memory alignment fault will occur after running the same code + structure for tens of thousands of times). Don't think it's impossible, which may occur on the ARM, The solution is to check the assembly code, check the alignment of all structures, and so on.

The code in this article is an extension of the following link:

C / C + + 11 try catch finally extension (functional)_ Liullittle blog - CSDN blog_ c++ finally

Suppose We hope that the following code will not cause the program to stop working, so what should we do?

int64_t llPtr = 0;

char* byPtr = (char*)llPtr;

*byPtr = rand() & 0xff;

... ...

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

We know that the above code must crash due to "SIGSEGV" and generate dump file / information (if set), so what should we do if we don't want a failure here?

SIGSEGV can be considered to be captured. There is a definition of lazy macro called SIG_IGN (ignore signal) but note that it does ignore the signal to ensure that the program will not stop working due to the crash signal, but this is limited. Signals such as SIGSEGV and SIGBUS are invalid. Don't consider such meaningless things. Even if you want to give up a signal, it shouldn't be such extensive operation, SIG_IGN is only applicable to the crash signal of the active raise of the program, such as SIG_PIPE.

OK, then you can apply the latest improvement: "try catch finally" for processing, but exceptions may potentially lead to memory leakage.

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!");

The above code can be executed correctly to OK! Part, and there will be no memory leakage problem, but once we operate something related to memory in lambda, exceptions may occur, such as copying a shared in the function_ PTR and an exception occurs before destruct, then shared_ptr references cannot be reduced, resulting in memory resource leakage, so a similar good way is not to use shared_ptr, or use it when you can be sure that no abnormality will occur. If you're really not sure you have to use it, there's a way: weak_ptr may be right for you.

The lambda function with potential exceptions can capture shared_ptr and other things, they do not cause the capture shared cannot be called_ PTR has a destructor, so it is online memory safe, but lambda internal operation is not memory safe. This must be made clear!

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
}

Topics: C C++ Linux