Portable C++ Stack Trace on Exception

后端 未结 7 1333
轮回少年
轮回少年 2020-12-15 08:53

I am writing a library that I would like to be portable. Thus, it should not depend on glibc or Microsoft extensions or anything else that is not in the standard. I have a

相关标签:
7条回答
  • 2020-12-15 09:17

    Stack managing is one of those simple things that get complicated very quickly. Better leave it for specialized libraries. Have you tried libunwind? Works great and AFAIK it's portable, though I've never tried it on Windows.

    0 讨论(0)
  • 2020-12-15 09:24

    This will be slower but looks like it should work.

    From what I understand the problem in making a fast, portable, stack trace is that the stack implementation is both OS and CPU specific, so it is implicitly a platform specific problem. An alternative would be to use the MS/glibc functions and to use #ifdef and appropriate preprocessor defines (e.g. _WIN32) to implement the platform specific solutions in different builds.

    0 讨论(0)
  • 2020-12-15 09:31

    I think this is a really bad idea.

    Portability is a very worthy goal, but not when it results in a solution that is intrusive, performance-sapping, and an inferior implementation.

    Every platform (Windows/Linux/PS2/iPhone/etc) I've worked on has offered a way to walk the stack when an exception occurs and match addresses to function names. Yes, none of these are portable but the reporting framework can be and it usually takes less than a day or two to write a platform-specific version of stack walking code.

    Not only is this less time than it'd take creating/maintaining a cross-platform solution, but the results are far better;

    • No need to modify functions
    • Traps crashes in standard or third party libraries
    • No need for a try/catch in every function (slow and memory intensive)
    0 讨论(0)
  • 2020-12-15 09:35

    Look up Nested Diagnostic Context once. Here is a little hint:

    class NDC {
    public:
        static NDC* getContextForCurrentThread();
        int addEntry(char const* file, unsigned lineNo);
        void removeEntry(int key);
        void dump(std::ostream& os);
        void clear();
    };
    
    class Scope {
    public:
        Scope(char const *file, unsigned lineNo) {
           NDC *ctx = NDC::getContextForCurrentThread();
           myKey = ctx->addEntry(file,lineNo);
        }
        ~Scope() {
           if (!std::uncaught_exception()) {
               NDC *ctx = NDC::getContextForCurrentThread();
               ctx->removeEntry(myKey);
           }
        }
    private:
        int myKey;
    };
    #define DECLARE_NDC() Scope s__(__FILE__,__LINE__)
    
    void f() {
        DECLARE_NDC(); // always declare the scope
        // only use try/catch when you want to handle an exception
        // and dump the stack
        try {
           // do stuff in here
        } catch (...) {
           NDC* ctx = NDC::getContextForCurrentThread();
           ctx->dump(std::cerr);
           ctx->clear();
        }
    }
    

    The overhead is in the implementation of the NDC. I was playing with a lazily evaluated version as well as one that only kept a fixed number of entries as well. The key point is that if you use constructors and destructors to handle the stack so that you don't need all of those nasty try/catch blocks and explicit manipulation everywhere.

    The only platform specific headache is the getContextForCurrentThread() method. You can use a platform specific implementation using thread local storage to handle the job in most if not all cases.

    If you are more performance oriented and live in the world of log files, then change the scope to hold a pointer to the file name and line number and omit the NDC thing altogether:

    class Scope {
    public:
        Scope(char const* f, unsigned l): fileName(f), lineNo(l) {}
        ~Scope() {
            if (std::uncaught_exception()) {
                log_error("%s(%u): stack unwind due to exception\n",
                          fileName, lineNo);
            }
        }
    private:
        char const* fileName;
        unsigned lineNo;
    };
    

    This will give you a nice stack trace in your log file when an exception is thrown. No need for any real stack walking, just a little log message when an exception is being thrown ;)

    0 讨论(0)
  • 2020-12-15 09:38

    In the debugger:

    To get the stack trace of where an exception is throw from I just stcik the break point in std::exception constructor.

    Thus when the exception is created the debugger stops and you can then see the stack trace at that point. Not perfect but it works most of the time.

    0 讨论(0)
  • 2020-12-15 09:39

    I don't think there's a "platform independent" way to do this - after all, if there was, there wouldn't be a need for StackWalk or the special gcc stack tracing features you mention.

    It would be a bit messy, but the way I would implement this would be to create a class that offers a consistent interface for accessing the stack trace, then have #ifdefs in the implementation that use the appropriate platform-specific methods to actually put the stack trace together.

    That way your usage of the class is platform independent, and just that class would need to be modified if you wanted to target some other platform.

    0 讨论(0)
提交回复
热议问题