Link to home
Start Free TrialLog in
Avatar of abancroft
abancroft

asked on

How to reset FPU in exception handler

Platform: Win32, MS VC++ 6 SP3.

By default in my app, floating point exceptions are masked and handled in hardware by the FPU. However, in certain situations the exception mask is reset by third party code - which subsequently causes software exceptions to be raised (e.g. when a NaN or Infinite value is generated).

This was causing the app to crash (unhandled exception), so I used SetUnhandledExceptionFilter() to set a top level exception handler.

In the case of a floating point exception (e.g. EXCEPTION_FLT_INVALID_OPERATION), the handler resets the FPU and returns EXCEPTION_CONTINUE_EXECUTION to re-execute the failed instruction.

Here's the handler code:
LONG __stdcall ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
  LONG lRet = EXCEPTION_CONTINUE_SEARCH;

  // By default, most floating point exceptions are handled in hardware.
  // However, some third party printer drivers fiddle with the floating point
  // control word which unmasks certain exceptions. This causes software
  // exceptions to be generated instead - which would crash the program.
  // So trap these exceptions, reset the FP package and re-execute the
  // failed instruction.
  if (pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_DENORMAL_OPERAND ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_INEXACT_RESULT ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_INVALID_OPERATION ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_OVERFLOW ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_STACK_CHECK ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_UNDERFLOW)
  {
    // Calling _fpreset() (or controlfp()) and returning EXCEPTION_CONTINUE_EXECUTION
    // WILL NOT WORK - after returning, the OS will restore the state of the FPU, including
    // the control and status words. This will overwrite the changes made by _fpreset()
    // so the exception will immediately be thrown again - putting us right back here.
    // Can you say 'stack overflow'?

    // Call this just in case there is some internal state (i.e. state not stored in
    // the FPU) to the floating point package.
    _fpreset();

    // Reset the FPU.
    // Where did I get these magic numbers? I wrote a small program that
    // just called _fpreset() and then raised an exception using RaiseException().
    // I put a break point in the exception handler and wrote down the values.
    // Pretty cruddy eh? Well, it seems to work - I just hope it doesn't
    // have any side effects.
    pExceptionInfo->ContextRecord->FloatSave.ControlWord = 0xffff027f;
    pExceptionInfo->ContextRecord->FloatSave.StatusWord = 0xffff0000;
    pExceptionInfo->ContextRecord->FloatSave.TagWord = 0xffffffff;
    pExceptionInfo->ContextRecord->FloatSave.ErrorOffset = (DWORD)0;
    pExceptionInfo->ContextRecord->FloatSave.ErrorSelector = (DWORD)0;
    pExceptionInfo->ContextRecord->FloatSave.DataOffset = (DWORD)0;
    pExceptionInfo->ContextRecord->FloatSave.DataSelector = 0xffff0000;

    lRet = EXCEPTION_CONTINUE_EXECUTION;
  }
  else
  {
    excReport.GenerateReportForSE(pExceptionInfo);

    if ( s_gpPrevEFilter )
      lRet = s_gpPrevEFilter( pExceptionInfo );
  }

  return lRet;
}

My question is: is there a better method to do this?
Avatar of NickRepin
NickRepin

It's only necessary to reset the certain bits in the FPU control word.
Do not touch anything else.

pExceptionInfo->ContextRecord->FloatSave.ControlWord = 0xffff027f;

bit 0 - invalid operand
1 - denormalized operand
2 - zero divide
3 - overflow
4- underflow
5 - precision

When the bit is set, then exception is blocked from being generated.

To block all exceptions, use    

pExceptionInfo->ContextRecord->FloatSave.ControlWord|=0x2F;
Sorry,
....|= 0x3F
Sorry, ingore that line in my answer:

pExceptionInfo->ContextRecord->FloatSave.ControlWord = 0xffff027f;

Avatar of abancroft

ASKER

That was my first approach. I used:
pExceptionInfo->ContextRecord->FloatSave.ControlWord = _CW_DEFAULT;

But this didn't work: The failed instruction kept throwing an exception until the stack overflowed. So then I reset the status word. This helped (no stack overflow), but on Win9x I kept getting crashes in COMCTL after a dialog was invoked & dismissed.

Finally I reset the entire FPU state & this seems to work.
You are right, I forgot about the Error Summary Status (ES) in the Status word.

It's a 7th bit. You have to clear it :

ControlWord|=0x2F;
StatusWord&=~0x8080;

B-bit (15th) must be set according ES bit (compatibility with 8087). It's not necessary for Windows I think.

6th bit hides stack fault:

ControlWord|=0x2F;
StatusWord&=~0x80C0;

Bits in the Control word hide the future exceptions.
ES bit in the Status word cancels the current exception.
Tried that. It works on NT but causes an invalid page fault in Win95.

Here's my test case: In a command handler (it's an MFC app), I unmask the FPU exceptions (using _controlfp()) & then call fmod(1.0, 0.0) which generates the exception. After the failed instruction is re-executed, the command handler displays a dialog.

When the dialog is dismissed (using either OK or Cancel), I get an invalid page fault in COMCTL32.DLL.

I'll try running it in the remote debugger to get more info.
The debugger gave no extra info (I had to run it in release build).

However, my exception handler gave the following call stack in it's report file:
Address   Frame     Logical addr  Module
BFC01BBA  006CFA2C  0001:00000BBA C:\WINDOWS\SYSTEM\COMCTL32.DLL
BFC0F6DF  006CFAA8  0001:0000E6DF C:\WINDOWS\SYSTEM\COMCTL32.DLL
BFF73663  006CFAC8  0001:00002663 C:\WINDOWS\SYSTEM\KERNEL32.DLL
BFF928E0  006CFADC  0002:0001A8E0 C:\WINDOWS\SYSTEM\KERNEL32.DLL

Is that any use? Probably not.....
Could you place your test program here?

No can do - it's not a test program per se, just an artificial condition in my actual application. Which I can't post.

I'll try to narrow things down a bit, but it'll take a few days.
This code works just fine,
even without  _clearfp() and status word, just with ControlWord|=0x2F as in my original answer. It seems that internal C++ handler clears the status word itself.


One more thing. It's better to enable asynchronous exception handling to make sure that the FPU exceptions are correctly handled.


#include <windows.h>
#include <iostream.h>
#include <float.h>
#include <math.h>

LONG ExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
  if (pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_DENORMAL_OPERAND ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_INEXACT_RESULT ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_DIVIDE_BY_ZERO ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_INVALID_OPERATION ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_OVERFLOW ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_STACK_CHECK ||
      pExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_FLT_UNDERFLOW)
  {
   _clearfp();

   pExceptionInfo->ContextRecord->FloatSave.ControlWord|=0x2F;
   pExceptionInfo->ContextRecord->FloatSave.StatusWord&=~0x8080;

    cout<<"Inside FPU exception"<<endl;
    return EXCEPTION_CONTINUE_EXECUTION;
  }

    cout<<"Inside other exception"<<endl;
   return EXCEPTION_CONTINUE_SEARCH;
}

float xx=0;
float x3=3;
void main()
{

// Enable exceptions.
int cw = _controlfp( 0, 0 );
cw &=~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT | EM_ZERODIVIDE | EM_DENORMAL);
_controlfp( cw, MCW_EM );;

__try {
cout<<"Before fmod"<<endl;
// Divide by zero.
xx=fmod(1.0/xx, 0.0) ;
cout<<"After fmod"<<endl;

// Re-enable exceptions.
cw = _controlfp( 0, 0 );
cw &=~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT | EM_ZERODIVIDE | EM_DENORMAL);
_controlfp( cw, MCW_EM );;

xx=xx/x3;
cout<<"After inexact res"<<endl;

}
__except(ExceptionHandler(GetExceptionInformation()))
{
cout<<"Inside handler"<<endl;
}

cout<<"After handler"<<endl;

}
This code works just fine in a simple test application. I had determined that before asking this question.

But I need to it work in my commercial application. Like I said, I'll try to narrow things down a bit.

>> It seems that internal C++ handler clears the status word itself.

What do you mean? I amn't using a C++ handler & neither does your example.

>>One more thing. It's better to enable asynchronous exception handling to make sure that the FPU exceptions are correctly handled.
How do I enable this?
This simple test demonstrates how it has to be. It has to work for all programs regardless of its complexity.
Otherwise the problem is somewhere else.

>> It seems that internal C++ handler clears the status word itself.

I mean that C++ run-time library does a lot of job before passing the control to your exception filter or handler.

Specify /GX /EHa flags to enable asynchronous exception handling.
According SDK (and my experience), it is a MUST to use with hardware exceptions.

<<<<<
In previous versions of Visual C++, the C++ exception handling mechanism supported asynchronous (hardware) exceptions by default. Under the asynchronous model, the compiler assumes any instruction may generate an exception.

With the new synchronous exception model, now the default, exceptions can be thrown only with a throw statement. Therefore, the compiler can assume that exceptions happen only at a throw statement or at a function call. This model allows the compiler to eliminate the mechanics of tracking the lifetime of certain unwindable objects, and to significantly reduce the code size, if the objects’ lifetimes do not overlap a function call or a throw statement. The two exception handling models, synchronous and asynchronous, are fully compatible and can be mixed in the same application.

Catching hardware exceptions is still possible with the synchronous model. However, some of the unwindable objects in the function where the exception occurs may not get unwound, if the compiler judges their lifetime tracking mechanics to be unnecessary for the synchronous model.
Probably, /GX is not necessary for you.


But /EHa is a must.
>> This simple test demonstrates how it has to be. It has to work for all programs regardless of its complexity.
Otherwise the problem is somewhere else.
I wish it were so. :-( The problem may be "somewhere else", but it is related to reseting the FPU state. (Speculating..) It may be that not fully reseting the FPU results in a subtle error which propogates and only becomes apparent much later

This isn't C++ exception handling - it's structured exception handling, which is a Win32 feature NOT a C++ one. In particular, SEH doesn't unwind the stack.

I can't use C++ exceptions - there is no way to re-execute the failed instruction.
Would clearing the floating-point exception flags, the exception summary status flag, the stack fault flag, and the busy flag in the FPU status word be enough?
<<This isn't C++ exception handling - it's structured exception handling, which is a Win32 feature NOT a C++ one. In particular, SEH doesn't unwind the stack. >>

You are wrong. See the test program below. Compile it:

   cl.exe -EHa test.cpp


Also read this:

http://msdn.microsoft.com/library/periodic/period97/pietrek.htm

May be, you'll not so confident about exceptions.



#include <windows.h>
#include <iostream.h>


class A
{
public:
   ~A() { cout<<"Stack is unwound"<<endl; }
};

void a()
{
   A a;
   char *s=0;
   *s=0;
}

void main()
{

   __try {
      a();
   }
   __except(EXCEPTION_EXECUTE_HANDLER)
   {
      cout<<"Inside handler"<<endl;
   }

}
Apparently I am wrong - with /EHa the stack was unwound. Yet the help on /EH specifically says "the C++ exception handling mechanism supported asynchronous (hardware) exceptions by default" - which implied (to me) that the /EH switch only applied to C++ exceptions. Oh well, live & learn.

I'll apply /EHa to my project build settings & see if it makes a difference.
Microsoft Visual C++ beginning from 6.0 does not support asynchronous exceptions by default.

5.0 does.
I applied the /EHa flag to all my DLL's & the EXE. It made no difference.

However, I did persevere and find what was causing the post dialog crash: the dialogs WM_KICKIDLE handler. It was calling:
AfxGetMainWnd()->SendMessageToDescendants(WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0, TRUE, TRUE);

When I removed this line, the crash stopped happending. Since this code was left over from the 16-bit port, I've deleted it. But why was it causing a crash?

With this fix in place, I changed the excption handler to:
// Mask all exceptions
pExceptionInfo->ContextRecord->FloatSave.ControlWord |= (_MCW_EM & _CW_DEFAULT);
// Reset the error bits on the status word.
pExceptionInfo->ContextRecord->FloatSave.StatusWord &= ~( _SW_DENORMAL | _SW_INEXACT |
_SW_INVALID | _SW_OVERFLOW |
_SW_UNDERFLOW | _SW_ZERODIVIDE |
_SW_STACKOVERFLOW | _SW_STACKUNDERFLOW);

The only issue is that the values of the #defines from float.h don't add up to the hex values you gave in an earlier post (the exception mask adds up to 0x8001F and the status mask to 0x8061F).
Sorry, I don't see any sense to continue discussion. The answer I gave you is the only right one, in my humble opinion. The answer to the question 'how to handle FPU exceptions'.

_SW_xxx constants have no relation to the FPU status word, at least directly.
No one of these constants correspond to the actual  bits in the Status word.

For example,

_SW_INEXACT 1   - must be 0x20
_SW_UNDERFLOW 2 - must be 0x10
_SW_OVERFLOW  4 - 0x40

etc.

There is no such bits as _SW_STACKOVERFLOW and  _SW_STACKUNDERFLOW at all, but there is the only Stack fault flag.

The same is true for _MCW_EM and CW_DEFAULT . They are just not relevant, but accidentally fit the values I show you before.

ControlWord and StatusWord are 16-bit fields. I'm talking about the real FPU registers, not about the fields in the FLOATING_SAVE_AREA. Your 0x8001F and 0x8061F takes 20 bits! Only this shows that these values have no sense.

Sorry, I'll not waste my time any more.

My answer is: to disable all exceptions, it's enough to adjust the control word in the exception handler:

pExceptionInfo->ContextRecord->FloatSave.ControlWord|=0x2F;
   
ASKER CERTIFIED SOLUTION
Avatar of NickRepin
NickRepin

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Why do you feel you are wasting your time?

Because I didn't accept your answer as is? Because I explored alternatives? Because I took time to verify your answer?

You are the expert: this may be straight forward & obvious to you. It is not to me.

Thanks for your help.

Sorry for words about wasting a time.

Thanks for your points.
But did my answer help you?
I feel that it didn't.

>>WM_KICKIDLE
>>WM_IDLEUPDATECMDUI
I don't use MFC and I don't know about it. But anyway they shouldn't affect the FPU exception handling, especially when you return EXCEPTION_CONTINUE_EXECUTION.

Probably, the problem may be in this code.

  else
  {
    excReport.GenerateReportForSE(pExceptionInfo);

    if ( s_gpPrevEFilter )
      lRet = s_gpPrevEFilter( pExceptionInfo );
  }

  return lRet;


Yes, your answer did help. I wouldn't have awarded the points otherwise.

I had (naively) assumed that since the FLOATING_SAVE_AREA fields were DWORD's, I could use the symbols from float.h (it kind of makes sense).

After you said that the control word is actualy 16 bits (and I confirmed this), I did some empircal testing which showed that the symbols didn't apply: so I had to use the real register values, as you said.

The WM_* stuff was definitely the problem - I did quite a bit more testing. I also vaguely remember an MSDN article recommending against that code.

Just to confirm:
  ControlWord |= 0x3F
and
  StatusWord &= ~0x80C0
are the answer?
Well, it depends on what do you want to do.

To just disable exceptions and continue execution, it's enough to adjust
ControlWord |= 0x3F (really 3F, not 2F).

It seems (according my test program above) that it's not necessary to adjust the Status word.
But anyway it's harmless, so you can do it as well.
Status word also contains exception bits (0-5) which are set when exception occured. But they are masked by the corresponding bits in the ControlWord.
So you can reset these bits too, but, repeat, it's not necessary:

StatusWord &= ~0x80FF;

You'd get much useful info about FPU if you downloaded the Intel Architecture Software Developer's Manual, Volume 1 Basic Architecture from the www.intel.com .