3

I am attempting to write an unhandled exception filter ( see SetUnhandledExceptionFilter()) to use with Windows SEH to report invalid floating point operations. I'd like to trap the exception, print a stack trace, then disable floating point exceptions and resume execution with the resulting non-finite or not-a-number value.

I wrote the simple program below to demonstrate the ability to catch the exception and resume execution. Note the use of #ifdef _WIN64, as the definition of the ContextRecord changes depending on the targeted architecture (WIN32 uses "FloatSave", WIN64 uses "FltSave").

#include "stdafx.h"
#include <float.h>
#include <Windows.h>

double zero = 0.0;
LONG WINAPI myfunc(EXCEPTION_POINTERS * ExceptionInfo){
    /* clear the exception */
    unsigned int stat = _clear87();

    /* disable fp exceptions*/
    unsigned int ctrl1 = _control87(_MCW_EM, _MCW_EM);

    /* Disable and clear fp exceptions in the exception context */
    #if _WIN64
        ExceptionInfo->ContextRecord->FltSave.ControlWord = ctrl1;
        ExceptionInfo->ContextRecord->FltSave.StatusWord = 0;
    #else
        ExceptionInfo->ContextRecord->FloatSave.ControlWord = ctrl1;
        ExceptionInfo->ContextRecord->FloatSave.StatusWord = 0;
    #endif

    printf("#########Caught Ya#####!\n");

    return EXCEPTION_CONTINUE_EXECUTION;
}


int _tmain(int argc, _TCHAR* argv[])
{
    double a;

    /* enable fp exceptions*/
    _controlfp(0, _MCW_EM);

    /* Setup our unhandled exception filter */
    SetUnhandledExceptionFilter(myfunc);

    /* do something bad */
    a = 5.0 / zero;

    printf("a = %f\n",a);

    return 0;
}

When run as a WIN32 .exe, the above program runs as expected, with the output:

#########Caught Ya#####!
a = -1.#IND00

However, when run as a WIN64 .exe, the program enters an infinite loop, continuously re-catching the floating point exception:

#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
#########Caught Ya#####!
...

This seems to indicate that I'm not successful at configuring the floating point unit, which I imagine is related to the different definition of ContextRecord between WIN32 and WIN64, but I haven't been able to find any good related documentation on exactly how to set the floating point context.

Any thoughts on how to correctly set the floating point context for WIN64?

Thanks in advance

jHops
  • 340
  • 1
  • 3
  • 11
  • 2
    By default, MSC compiles floating point instructions to use the SSE unit for 64-bit code, always. Depending on the CPU you are running on, the `_control87` call may not set the corresponding MXCSR flags for the SSE unit. Some more details in [this Q&A](http://stackoverflow.com/q/20045968/1889329). – IInspectable Mar 04 '17 at 00:12
  • @IInspectable, Thanks for the reply. Indeed on the 64 bit platform the MXCSR shows up underneath ContextRecord: `ContextRecord->FltSave.MxCsr`; `ContextRecord->FltSave.MxCsr_Mask`; and finally `ContextRecord->MxCsr`. I think I read that the MXCSR has the same definition as _control87, so I tried assigning various combinations of above context records to my ctrl1 variable, however no success thus far – jHops Mar 04 '17 at 01:02
  • 1
    I guess I see what's happening now: With FPU code, when an operation fails, it sets the exception flag, but a floating point exception is only raised on the **next** FPU instruction. Since your exception filter instructs control flow to return to the opcode that triggered the exception (probably an `FST`, not the actual division) with the exception bit cleared, it will continue execution. With SSE code, I believe the exception is raised by the failing opcode, the division in your case. That instruction is executed over and over again. Step through assembly to verify whether this is correct. – IInspectable Mar 05 '17 at 14:29

1 Answers1

1

I've got a working example now. Thanks to @IInspectable for pointing me in the right direction (SSE). The issue was that the MxCsr register(s?) need to be set in the ExecutionContext. Note that calling controlfp DID indeed set the MxCsr register correctly, however it appears that when the filter function returns, ALL registers are reset to the context given in ExceptionInfo. The trick is to overwrite MxCsr in that context, which the code below does.

I am still a bit confused as to the two locations of MxCsr in the context. controlfp() seems to set BOTH together and to the same value (as observed in debugger).

#include "stdafx.h"
#include <float.h>
#include <Windows.h>
#include <xmmintrin.h>

double zero = 0.0;
LONG WINAPI myfunc(EXCEPTION_POINTERS * ExceptionInfo){
    /* clear the exception */
    unsigned int stat = _clearfp();

    /* disable all fp exceptions*/
    unsigned int ctrlwrd;
    errno_t err =  _controlfp_s(&ctrlwrd, _MCW_EM, _MCW_EM);

    /* Disable and clear the exceptions in the exception context */
    #if _WIN64
        /* Get current context to get the values of MxCsr register, which was
         * set by the calls to _controlfp above, we need to copy these into
         * the exception context so that exceptions really stay disabled.
         * References:
         *    https://msdn.microsoft.com/en-us/library/yxty7t75.aspx
         *    https://software.intel.com/en-us/articles/x87-and-sse-floating-point-assists-in-ia-32-flush-to-zero-ftz-and-denormals-are-zero-daz
         */
        CONTEXT myContext;
        RtlCaptureContext(&myContext);

        ExceptionInfo->ContextRecord->FltSave.ControlWord = ctrlwrd;
        ExceptionInfo->ContextRecord->FltSave.StatusWord = 0;
        ExceptionInfo->ContextRecord->FltSave.MxCsr = myContext.FltSave.MxCsr;
        ExceptionInfo->ContextRecord->FltSave.MxCsr_Mask = myContext.FltSave.MxCsr_Mask;
        ExceptionInfo->ContextRecord->MxCsr = myContext.MxCsr;
    #else
        ExceptionInfo->ContextRecord->FloatSave.ControlWord = ctrlwrd;
        ExceptionInfo->ContextRecord->FloatSave.StatusWord = 0;
    #endif

    printf("#########Caught Ya#####!\n");

    return EXCEPTION_CONTINUE_EXECUTION;
}

int _tmain(int argc, _TCHAR* argv[])
{
    double a;

    /* Enable fp exceptions */
    _controlfp_s(0, 0, _MCW_EM);

    /* Setup our unhandled exception filter */
    SetUnhandledExceptionFilter(myfunc);

    /* do something bad */
    a = 5.0 / zero;

    printf("a = %f\n",a);
    return 0;
}
jHops
  • 340
  • 1
  • 3
  • 11