Stack Tracer function


#1

This function can come in handy when trapping segfaults and general uncaught exceptions even in release mode (by not stripping the symbols from the executable). I’m actually using this to allow a user to submit a stack trace of some piece of code when something bad happens, so to ease the bug squashing.

It is fairly portable among posix OSes, but i have a win32 version too (for mingw and msvc) that makes use of imagehlp.dll and the StackWalk function to achieve the same result. I will clean up the code and keep this thread updated when i think it’s ready for the juce inclusion.

Other things that i’m working on that will be available shortly:

  • cross platform signal/exception handlers
  • fast debug allocators with memleaks reporting on posix systems

The stack tracer itself:

juce_Process.h

    //==============================================================================
    class StackTraceInfo
    {
    public:
        
        String callAddress;
        String executableName;
        String functionName;
        
        String fullStack;
    };
    
    /** Returns information on the current executing stack
    
        It tries to obtain information from the current running process. Not all
        the variables in StackTraceInfo class can be filled if the binary itself
        cannot inspect the symbols correctly. Typically you can use this without
        stripping symbols from your binary executable.
        
        With GCC you should compile your application with -g and also use -rdynamic
        while not stripping symbols (with -s) when linking. If you are using MINGW on
        windows then you should also avoid optimizing the code with -O1 -O2 -O3 (only
        -O0 will work here), and also using optimize for size (-Os) would not work.

        On Mac OS X should possible to use the posix (backtrace) version from the
        latest version of Leopard (first appeared in Mac OS X 10.5).

        On MS VisualStudio todo.

        @code

            OwnedArray<Process::StackTraceInfo> stack;
            Process::inspectStack (stack);
            
            for (int i = 0; i < stack.size(); i++)
                std::cout << (const char*) stack[i]->fullStack << std::endl;
        
        @endcode

        @param stackInfo                    the array with traced function calls
                                            that will be filled
    */
    static void inspectStack (OwnedArray<StackTraceInfo>& stackInfo);

juce_posix_SharedCode.h

#include <execinfo.h>

#include <cxxabi.h>

//==============================================================================
void Process::inspectStack (OwnedArray<Process::StackTraceInfo>& stackInfo)
{
    void* backtraceArray [100];

    const int size = backtrace (backtraceArray, 100);
    char** strings = backtrace_symbols (backtraceArray, size);

    if (strings != NULL)
    {
        for (int i = 0; i < size; i++)
        {
            Process::StackTraceInfo* stack = new Process::StackTraceInfo();

            String allInfo (strings[i]);
            
            int pos = allInfo.indexOf(T("("));
            if (pos >= 0 && pos < allInfo.length())
            {
                stack->executableName = allInfo.substring (0, pos);
                allInfo = allInfo.substring (pos + 1);
                
                pos = allInfo.indexOf(T(")"));
                if (pos >= 0 && pos < allInfo.length())
                {
                    stack->callAddress = allInfo.substring (pos + 1).removeCharacters(T("[]"));
                    allInfo = allInfo.substring (0, pos);
                }
                
                pos = allInfo.indexOf(T("+"));
                if (pos >= 0 && pos < allInfo.length())
                {
                    allInfo = allInfo.substring (0, pos);
                }
            }

            int status = 0;
            char demangledName [1024];
            size_t demangledSize = 1024;
            
            abi::__cxa_demangle ((const char*) allInfo, demangledName, &demangledSize, &status);            
                                 
            stack->functionName = status ? allInfo : String((char*)demangledName);
            stack->fullStack = strings[i];
            
            stackInfo.add(stack);
        }
        
        free(strings);
    }
}

juce_win32_Threads.cpp


#2

I guess you haven’t googled enough:
http://code.google.com/p/google-breakpad/

That’s a crossplatform minidump generator and analyzer that works pretty well.


#3

what i don’t understand in google-breakpad, if it clearly state it’s cross platform, why i have to take care of writing a handler for each different platform ? that is not cross platform, it is written for the different platform with a different interface on each one.


#4

Of course there is a handler for each platform as each platform use different system call/stack convention. But you don’t have to write a gbp driver for the platform, it’s already written.
You must write the exception handler however for each platform where you’ll call gbp code.
In Juce, you’ve the unhandledException static method for this, so it’s quite simple
If you want more informations, in POSIX based system, it might even be better if you could install your own signal handler with the full parameters (not the basic void signal() function) .

I’ve seen you’ve started making an interface for Stack Trace processing in Juce (glibc only), I thought it would be a nice time to:

  1. Avoid you to (re)write what is already done in gbp
  2. Reuse the stack / dump analysis tools in gbp
  3. Finally link the gbp tools with Juce coding styles so it’s quite easy to reuse for us

#5

ok i can try it and see what i came up with.

anyway my version works anywhere in the code, i don’t need to install any exception handler code (you plug my stack tracer when you need it, even if you don’t have a real exception)


#6

I’d be interested in seeing the win32 code for sure! Thanks for this :smiley:


#7

Following the ehrm… wonderful guide (mmmh. which wonderful guide ?) the program produces some stack dump that cannot be analyzed.

$ minidump_stackwalk -m 3e5eb270-1729-c792-2c2cabdc-682b6f6e.dmp syms

2010-02-24 12:51:44: minidump_processor.cc:238: INFO: Processing minidump in file 3e5eb270-1729-c792-2c2cabdc-682b6f6e.dmp
2010-02-24 12:51:44: minidump.cc:3494: INFO: Minidump opened minidump 3e5eb270-1729-c792-2c2cabdc-682b6f6e.dmp
2010-02-24 12:51:44: minidump.cc:3539: INFO: Minidump not byte-swapping minidump
2010-02-24 12:51:44: minidump.cc:3896: INFO: GetStream: type 1197932545 not present
2010-02-24 12:51:44: minidump.cc:3896: INFO: GetStream: type 1197932546 not present
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /home/kraken/Projects/break/break_test
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /lib/ld-2.11.so
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /lib/libc-2.11.so
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /lib/libm-2.11.so
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /lib/librt-2.11.so
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /usr/lib/gcc/i686-pc-linux-gnu/4.3.3/libgcc_s.so.1
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for /usr/lib/gcc/i686-pc-linux-gnu/4.3.3/libstdc++.so.6.0.10
2010-02-24 12:51:44: minidump.cc:1942: INFO: MinidumpModule could not determine version for linux-gate.so
2010-02-24 12:51:44: minidump_processor.cc:106: INFO: Minidump 3e5eb270-1729-c792-2c2cabdc-682b6f6e.dmp has CPU info, OS info, no Breakpad info, exception, module list, thread list, no dump thread, and requesting thread
2010-02-24 12:51:44: minidump_processor.cc:140: INFO: Looking at thread 3e5eb270-1729-c792-2c2cabdc-682b6f6e.dmp:0/1 id 0x46a4
2010-02-24 12:51:44: basic_source_line_resolver.cc:230: INFO: Loading symbols for module /home/kraken/Projects/break/break_test from buffer
Segmentation fault

a wonderful piece of code :slight_smile:


#8

Yes that’s very embarassing.
The minidump processor doesn’t like something in the minidump generated.
However, it seems that it processed the minidump correctly, but fails at mapping the source code line to the symbols, which suggests me that the processor/minidump doesn’t find the source code correctly (or the source code changed ?)
Anyway, a gdb/valgrind attached to the processor would stop immediately on the minidump processor code pointing, hopefully, to the reason why it failed.

I’ve used backtrace/backtrace symbols for a while now (back in 2003, on both x86 and ARM), and most of the time the backtrace function itself crashes like a rotted pig, mainly because of FPO/inlined-assembler.
Sometimes the backtrace simply loops infinitely, sometimes it’s missing some fundamental functions, sometimes it’s wrong for any reason.

By that time, I learned that producing a map file at linking time is usually the best and safe method (because before the crashing point, you’ll almost locate the function with the mapfile).

I’ve used gbp in a previous project (using Windows by that time), and it worked well. Never tried on linux yet, but I wonder how they could avoid using glibc “backtrace” function internally anyway.


#9

yes, i tried everything, double checked the sym table and the files are stored absolutely so i don’t know why it fails there.

the minidump itself is not a problem, as i can look at it by converting to a human readable file. the problem is when trying to map the symbols created previously with the stack trace in the minidump. the breakpad breaks (!?!) when looping lines in source files (and i didnt change those).

probably i should open a issue to them.