views:

159

answers:

5

I've got a c++ app that wraps large parts of code in try blocks. When I catch exceptions I can return the user to a stable state, which is nice. But I'm not longer receiving crash dumps. I'd really like to figure out where in the code the exception is taking place, so I can log it and fix it.

Being able to get a dump without halting the application would be ideal, but I'm not sure that's possible.

Is there some way I can figure out where the exception was thrown from within the catch block? If it's useful, I'm using native msvc++ on windows xp and higher. My plan is to simply log the crashes to a file on the various users' machines, and then upload the crashlogs once they get to a certain size.

+1  A: 

What you need is to analyze the stack to figure out where the exception came from. For msvc there is a lib called dbghelp.dll that can help you log out the exceptions. In general what I do is to log out a minidump file and use this to replay the issue beside using the right program database (pdb file). This works on a customer systems that do not come with source code or to whom you won't want to give pdbs.

jdehaan
+1  A: 

You can write dumps using MiniDumpWriteDump function.

If you're using C++ exceptions, then you can simply include file/line/function information (so you'll see it as text if you call std::exception.what()) in any place where you throw that exception (using _FUNCTION_, _FILE_ and _LINE_ macros).

If you're trying to catch OS exceptions, then crashing the application will be probably a better choice.

SigTerm
+3  A: 

This is possible with using SEH (structured exception handling). The point is that MSVC implements C++ exceptions via SEH. On the other hand the pure SEH is much more powerful and flexible.

That's what you should do. Instead of using pure C++ try/catch blocks like this:

try
{
    DoSomething();
} catch(MyExc& exc)
{
    // process the exception
}

You should wrap the inner code block DoSomething with the SEH block:

void DoSomething()
{
    __try {
        DoSomethingInner();
    __except (DumpExc(GetExceptionInformation(), EXCEPTION_CONTINUE_SEARCH) {
        // never get there
    }
}

void DumpEx(EXCEPTION_POINTERS* pExc)
{
    // Call MiniDumpWriteDump to produce the needed dump file
}

That is, inside the C++ try/catch block we place another raw SEH block, which only dumps all the exceptions without catching them.

valdo
Using SEH directly is OK if you don't care about portability. Standard C++ exceptions are more portable (well, except for several compilers for embedded systems that don't support exceptions).
George
This is an effective way to handle C++ exceptions. If you want to get useful information on OS exceptions, such as stack overflow or access violation, you should arrange for `MiniDumpWriteDump` to be called from another process.
Ben Voigt
@George: There is currently no portable way to get stack traces in C++. I seem to recall that this is a situation that the new C++0x standard tries to improve.
Ben Voigt
Not necessarily. Well, if you talk about stack overflow, and you handle this exception before the stack unwind - you should call 'MiniDumpWriteDump' from another thread (since our thread has almost no stack). But there's no need to do things from another process. Unless you consider the exception as a bug, and don't rely on that process
valdo
George
@valdo: `MiniDumpWriteDump` needs to load other DLLs, so if you encountered an exception while holding the loader lock, you'd better call it from out of process. Also, if you have memory corruption (a common cause of bugs you'd want to get a dump of), you again should call `MiniDumpWriteDump` out-of-process.
Ben Voigt
+1  A: 

One trick that is compiler independent is to wrap the throw statement in a function. The function can perform other duties before throwing the exception, such as recording to a log file. It also makes a handy place to put a breakpoint. If you create a macro to call the function you can automatically include the __FILE__ and __LINE__ where the throw occurred.

Mark Ransom
+1  A: 

It's possible to design your exceptions to include source file names & line numbers. In order to do so, you need to create a class derived from std::exception to contain the information. In the example below, I have a library of exceptions for my application including my_exception. I also have a traced_error which is a template exception class derived from my application-level exceptions. The traced_error exception holds information about the filename & line number, and calls the application-level exception class' what() method to get detailed error information.

#include <cstdlib>
#include <string>
#include <stdexcept>
#include <iostream>
using namespace std;


template<class EX>
class traced_error : virtual public std::exception, virtual public EX
{
public:
    traced_error(const std::string& file, int line, const EX& ex)
    :   EX(ex),
        line_(line),
        file_(file)
    {       
    }

    const char* what() const
    {
        std::stringstream ss;
        static std::string msg;
        ss << "File: " << file_ << " Line: " << line_ << " Error: " << EX::what();
        msg = ss.str().c_str();
        return msg.c_str();
    }

    int line_;
    std::string file_;
};

template<class EX> traced_error<EX> make_traced_error(const std::string& file, int line, const EX& ex)
{
    return traced_error<EX>(file, line, ex);
}


class my_exception : virtual public std::exception
{
public:
    my_exception() {};

    const char* what() const
    {
        return "my_exception's what";
    }
};

#define throwx(EX) (throw make_traced_error(__FILE__,__LINE__,EX))


int main()
{
    try
    {
        throwx(my_exception());
    }
    catch( const std::exception& ex )
    {
        cout << ex.what();
    }
    return 0;
}

The output of this program is:

File: .\main.cpp Line: 57 Error: my_exception's what

You could also redesign this so that the application-level exceptions derive from traced_error instead of the other way round, in case you would rather catch specific application-level exceptions. In your catch, you can log the error to a log file & create a dump file using MiniDumpWriteDump().

John Dibling