Solved

How to reset FPU in exception handler

Posted on 2000-03-14
26
1,711 Views
Last Modified: 2013-12-03
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?
0
Comment
Question by:abancroft
  • 15
  • 10
26 Comments
 
LVL 15

Expert Comment

by:NickRepin
ID: 2618047
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;
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2618051
Sorry,
....|= 0x3F
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2618055
Sorry, ingore that line in my answer:

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

0
 
LVL 4

Author Comment

by:abancroft
ID: 2619827
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.
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2621685
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;

0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2621688
Bits in the Control word hide the future exceptions.
ES bit in the Status word cancels the current exception.
0
 
LVL 4

Author Comment

by:abancroft
ID: 2624716
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.
0
 
LVL 4

Author Comment

by:abancroft
ID: 2624819
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.....
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2625832
Could you place your test program here?

0
 
LVL 4

Author Comment

by:abancroft
ID: 2625854
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.
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2626330
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;

}
0
 
LVL 4

Author Comment

by:abancroft
ID: 2628278
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?
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2629776
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.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 15

Expert Comment

by:NickRepin
ID: 2629813
Probably, /GX is not necessary for you.


But /EHa is a must.
0
 
LVL 4

Author Comment

by:abancroft
ID: 2629932
>> 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.
0
 
LVL 3

Expert Comment

by:SamHobbs
ID: 2630815
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?
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2632239
<<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;
   }

}
0
 
LVL 4

Author Comment

by:abancroft
ID: 2636374
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.
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2637921
Microsoft Visual C++ beginning from 6.0 does not support asynchronous exceptions by default.

5.0 does.
0
 
LVL 4

Author Comment

by:abancroft
ID: 2641196
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).
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2642282
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;
   
0
 
LVL 15

Accepted Solution

by:
NickRepin earned 200 total points
ID: 2642294
All constants defined in the float.h like SW_xxx are to be used in the C RTL functions. They are just portable defines and have no relation to the actual FPU registers.
0
 
LVL 4

Author Comment

by:abancroft
ID: 2644983
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.

0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2646737
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;


0
 
LVL 4

Author Comment

by:abancroft
ID: 2646783
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?
0
 
LVL 15

Expert Comment

by:NickRepin
ID: 2647721
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 .
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

This article shows how to make a Windows 7 gadget that accepts files dropped from the Windows Explorer.  It also illustrates how to give your gadget a non-rectangular shape and how to add some nifty visual effects to text displayed in a your gadget.…
With most software applications trying to cater to multiple user needs nowadays, the focus is to make them as configurable as possible. For e.g., when creating Silverlight applications which will connect to WCF services, the service end point usuall…
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now