The inside of a computer is as dumb as hell but it goes like mad!
-- Richard Feynman

Introduction

Have you seen the default crash dialog in windows? It's a dialog that's pretty much useless for the normal user (I always just click go away when it happens for other applications) but it's very useful for your own applications. For example, you are debugging some arcane crash that only happens deep inside the content pipeline. It can be tedious to make a reproduction case, that's small enough so that you can run it standalone in the debugger. The just in time debugger dialog can help you there, the application is now in the correct place and with a few judiciously sprinkled #pragma optimize("", off) and __debugbreak() you can usually narrow down and even single step through the problem area and find out just why the program crashes.

Windows standard crash dialog
Fig 1: The standard windows crash dialog (as taken from Vista)

Bad dialog box

That was the good use of the dialog box. It is however not always a good thing to have an application modal dialog box pop up and require user input (it would be much nicer if there was a timeout for the cancel part of the dialog), for example you might be running a suite of unittests on your automated test server (I bet you saw that one coming). Coming back into work and realizing that last night's tests didn't run because of a stupid dialog box needed to you to click ok because it crashed is funny the first time. The following 99 times it's a huge annoyance and then the 101:st time you might actually try to fix the problem with the dialog box. In Windows XP there is a setting to turn these off, in Windows Vista I have not found it. As I described before, you might not want to turn it off for the whole machine, it's quite useful sometimes. Not all is lost though, there are ways to silence these little pests.

MS CRT Debug assert dialog
Fig 2: An assert fired in the debug version of the MS libraries (e.g. from _ASSERT).

If you're using a decent unit test framework (hey like the excellent UnitTest++) you know that it tries to trap most errors already to avoid this exact situation. So for example while you develop your tests you accidentally follow the null pointer, you will get a helpful message on the console that this and that test failed with an exception. However, it doesn't deal with the runtime itself when it tries to invoke all sorts of nasty things like assert and runtime bounds checking. Even though you might not write code that use the standard assert (check out this article on an improved assert handler), there might be all sorts of third party code that uses it. Consider the following code:

        std::vector values;
        values.push_back(1);
        ASSERT( values.size() > 2 );
        values[2] = 1;
    
Listing 1: Some random test code that will fire dialog boxes both in release and debug mode.

Clearly this code is kind of messed up, but it reveals two points where the runtime will actually stop execution and bring up a dialog box. The first one is in debug mode, where the assert will fire. The second one is in release mode where STL under visual studio will do bounds checking if you ask for it and will also fire a dialog box. Although you might not write this kind of code when you check in, during development of tests I find myself frequently following the tenant that make the tests fail first, then write the proper implementation... and boy do I ever make things fail. I see (saw) that dialog box quite a lot. So how to get rid of it?

Urusai Urusai Urusai!

After some digging in the MSDN documentation (which almost always feels a little bit like the old trick of burying information with more information) I found an interesting little function called SetErrorMode. The description reads: Controls whether the system will handle the specified types of serious errors or whether the process will handle them. Sweet. Sounds pretty much like what I want since I've turned on structured exception handling through /EHa and wrapped my main loop like this:

    try
    {
        RunTest();
    }
    catch(...)
    {
      printf("Bad things happened!\n");
    }
    

Now this should hopefully let the system be hands off and let me catch the error myself in my catch clause and then gracefully exit with an error code. Happy and thinking that I can now move on to write some more tests without resorting to the hated mouse each time I write a crash I stick the following at the top of my main function:

        SetErrorMode(~0);
    

As you might have guessed, this didn't work out all that great. The program ran and at the point where it previously crashed and popped up a dialog box it simply terminated the process. Visual Studio didn't catch that anything bad had happened though (I have the test project hooked into the post build step of Visual Studio). Digging some more I find out that in invarg.c there is a function called _invoke_watson (oh, btw. At the bottom there is a call:

        TerminateProcess(GetCurrentProcess(), STATUS_INVALID_PARAMETER);
    

Hmmmm. Looks reasonable? Suspecting that I've ran into Visual Studio's most vexing parse, that it only handles error codes greater than zero as errors, I look up the value of the exit code:

        #define STATUS_INVALID_PARAMETER         ((NTSTATUS)0xC000000DL)
    

Ahrg! Argv! Argc! Close, so close, but no cookie. It will still produce errors on a build server where the tests might be invoked from a make-like system, but visual studio will stubbornly insist that it's all ok.

Structured Exception Handling to the rescue?

I started to read up on the SEH section of MSDN to see if I can install my own handlers and in that one terminate the program with the correct error code. Sure enough, there is a way to install your own handlers through a mechanism called Vectored Exception Handling. Unfortunately the handlers get their chance after the debugger gets a chance to be notified. The end result was that my handler never got called. Since the system helpfully tried to invoke the debugger through a dialog box.

Get a grip

So after flailing around a little bit I started to read the runtime file invarg.c. There seemed to be a some handler that you could install to handle calls to invalid arguments. Oh. It was there all the time. Doh. _set_invalid_parameter_handler allows me to install a custom handler that gets called before the debugger gets a notification (see . Smacking myself over the head a little I install a handler and Eureka, the dialog box vanished. My handler looks like this:

    void __cdecl myInvalidHandler(const wchar_t * expression_,
                                  const wchar_t * function_,
                                  const wchar_t * file_,
                                  unsigned int line,
                                  uintptr_t pReserved)
    {
        (void)pReserved;

        static char expression[4096];
        static char function[512];
        static char file[512];

        expression[0] = 0;
        function[0] = 0;
        file[0] = 0;

        if(expression_)
            wcstombs(expression, expression_, sizeof(expression));
        else
            strcpy(expression, "Invalid parameter handler called from the runtime.");

        if(function_)
            wcstombs(function, function_, sizeof(function));
        else
            strcpy(function, __FUNCTION__);

        if(file_)
            wcstombs(file, file_, sizeof(file));
        else
            strcpy(file, __FILE__);

        expression[sizeof(expression)-1] = 0;
        function[sizeof(function)-1] = 0;
        file[sizeof(file)-1] = 0;

    #ifdef AURORA_TEST_USE_EXCEPTIONS
        throw aurora::test::Exception(expression, expression, function, file, line);
    #else
        #ifdef AURORA_TEST_USE_STDIO
        fprintf(stderr, "%s(%d): Caught invalid parameter, %s\n", file, line, expression);
        fflush(stderr);
        #endif
        TerminateProcess( GetCurrentProcess(), AURORA_TEST_PANIC_EXIT_CODE );
    #endif
    }
    
Listing 2: The invalid parameter handler. Install this with _set_invalid_parameter_handler and then you can index std::vectors out of bounds to your heart's content in tests without seeing a dialog box. This will get rid of the dialog in Fig. 1.

At least the one that checked for bad bounds for vectors is gone. Rebuilding the tests in debug revealed that the debug dialog (associated with ASSERT) is still there. Fortunately the crt debug library has a multitude of hooks already and one of them happens to be the _CrtSetReportHook2. This allows you to install a handler that gets called for most user notifications. One of them is when asserts fire, where we can be sneaky and do our own thing:

    int __cdecl myReportHook( int reportType, char *message, int *returnValue )
    {
        (void)returnValue;

        switch(reportType)
        {
        case _CRT_ASSERT:
    #ifdef AURORA_TEST_USE_EXCEPTIONS
            throw aurora::test::Exception( "Run Time Assert", message, __FUNCTION__, 0, 0 );
    #else
            #ifdef AURORA_TEST_USE_STDIO
            fprintf(stderr, "%s(%d): Caught CRT Assert, %s\n", __FILE__, __LINE__, message);
            fflush(stderr);
            #endif

            TerminateProcess( GetCurrentProcess(), AURORA_TEST_PANIC_EXIT_CODE );
    #endif
        }

        return 0;
    }
    #endif
    
Listing 3: A handler for the debug runtime assert handler. Install this one with _CrtSetReportHook2 and get rid of the dialog in Fig. 2.

Tada! While doing my best impersonation of Dark Helmet and brandishing my mouse like a lightsabre I commit my new code to the repository, free of any pesky dialog boxes (and of course free of any failing tests).

In closing

So that was a lot of ado for very little code. In the end I just installed these two handlers before I ran all my tests. Crashes are now reported through the normal facilities and Visual Studio detects correctly that the code sprayed poo bits all over the place. Maybe this will save you some clicking on that hated dialog while you're engaged in unit test battle.

This is currently how my own little unit test framework handles failures. While digging around in the framework I also noticed resource leak detectors and memory leak detection, which I might bring up in future articles. Until then, happy testing.

Resources

Comments